@jxsuite/site 2.7.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +3 -3
- package/src/routes.ts +3 -3
- package/src/site-style.ts +40 -6
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**What a Jx project's page at a route consists of:** routing, layout, `<head>`, context and site style, as pure functions with their IO injected.
|
|
4
4
|
|
|
5
|
-
Every one of these rules has to be answered identically by four things that cannot share code any other way: the compiler's static build, the Studio canvas, a Bun dev/desktop server, and a Cloudflare Worker serving a live preview. None of them can import `@jxsuite/compiler`, whose dependency graph carries
|
|
5
|
+
Every one of these rules has to be answered identically by four things that cannot share code any other way: the compiler's static build, the Studio canvas, a Bun dev/desktop server, and a Cloudflare Worker serving a live preview. None of them can import `@jxsuite/compiler`, whose dependency graph carries a native addon and `esbuild`, so neither a browser bundle nor a Worker can load it. And a route is exactly the kind of rule where a silent disagreement is a page that 404s in one surface and renders in another.
|
|
6
6
|
|
|
7
7
|
So the rules live here, and this package has **no platform imports at all**: no `node:`, no DOM, no filesystem. Where a rule genuinely needs to read something, the reader is a parameter (`LayoutLoader`, `DocumentParser`) and is simply absent where there are no directories.
|
|
8
8
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/site",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.0",
|
|
4
4
|
"description": "What a Jx project's page at a route consists of — routing, layout, context, head and the document that boots it",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"test:coverage": "bun test --isolate --coverage"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@jxsuite/runtime": "^7.
|
|
38
|
-
"@jxsuite/schema": "^6.0.
|
|
37
|
+
"@jxsuite/runtime": "^7.1.0",
|
|
38
|
+
"@jxsuite/schema": "^6.0.2"
|
|
39
39
|
}
|
|
40
40
|
}
|
package/src/routes.ts
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
* another.
|
|
10
10
|
*
|
|
11
11
|
* It lives HERE rather than in `@jxsuite/compiler` for the reason `resolveI18n` does (`i18n.ts`):
|
|
12
|
-
* the compiler's dependency graph carries
|
|
13
|
-
* Cloudflare Worker can import it. This module is pure string math with no imports at all, so
|
|
14
|
-
* host that has to know what `/blog/:slug` means can share one answer.
|
|
12
|
+
* the compiler's dependency graph carries a native addon and `esbuild`, so neither a browser bundle
|
|
13
|
+
* nor a Cloudflare Worker can import it. This module is pure string math with no imports at all, so
|
|
14
|
+
* every host that has to know what `/blog/:slug` means can share one answer.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
/** Bracket route segments: `[param]` (named) and `[...param]` (catch-all). */
|
package/src/site-style.ts
CHANGED
|
@@ -29,6 +29,10 @@
|
|
|
29
29
|
* to differ was a nested element key, on the belief that "the document's own style pass covers page
|
|
30
30
|
* content": nothing else reads `project.json#/style`, so the canvas simply dropped a site's
|
|
31
31
|
* typography and link rules while the build kept them (#296).
|
|
32
|
+
*
|
|
33
|
+
* A block's `$description` is the one thing only the build renders: a static build writes it as a
|
|
34
|
+
* comment above the rule (`{ comments: true }`, spec.md §9.2), and a host never does, because every
|
|
35
|
+
* consumer but a static build ignores the description.
|
|
32
36
|
*/
|
|
33
37
|
|
|
34
38
|
import {
|
|
@@ -36,11 +40,12 @@ import {
|
|
|
36
40
|
buildStyleRules,
|
|
37
41
|
isDeclarationAtRule,
|
|
38
42
|
isKeyframesAtRule,
|
|
43
|
+
commentedRuleText,
|
|
39
44
|
isNestedSelectorKey,
|
|
40
45
|
pureSchemeOf,
|
|
41
46
|
} from "@jxsuite/runtime/css";
|
|
42
47
|
import type { CssBuildOptions } from "@jxsuite/runtime/css";
|
|
43
|
-
import { isNonNullObject } from "@jxsuite/schema/guards";
|
|
48
|
+
import { isNonNullObject, isString } from "@jxsuite/schema/guards";
|
|
44
49
|
import type { JxRef, JxStyle } from "@jxsuite/schema/types";
|
|
45
50
|
|
|
46
51
|
/** Id of the injected site-style tag (replace-in-place, never accumulate). */
|
|
@@ -76,9 +81,29 @@ export interface SiteStyleRoots {
|
|
|
76
81
|
body: string;
|
|
77
82
|
}
|
|
78
83
|
|
|
84
|
+
/** What a caller may ask of the sheet beyond its rules. */
|
|
85
|
+
export interface SiteStyleOptions {
|
|
86
|
+
/**
|
|
87
|
+
* Write each described rule's `$description` as a comment on the line above it. The static
|
|
88
|
+
* build's switch: the canvas and the live preview leave it off and emit the rule text alone.
|
|
89
|
+
*/
|
|
90
|
+
comments?: boolean;
|
|
91
|
+
}
|
|
92
|
+
|
|
79
93
|
/** The page's own root and body. */
|
|
80
94
|
export const PAGE_ROOTS: SiteStyleRoots = { body: "body", root: ":root" };
|
|
81
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Hand a block's own `$description` to the first rule its split writes: the root's when it holds a
|
|
98
|
+
* declaration, the body's otherwise. A block that writes neither rule carries no comment.
|
|
99
|
+
*/
|
|
100
|
+
function describeFirstRule(block: JxStyle, root: JxStyle, body: JxStyle): void {
|
|
101
|
+
const description = block["$description"];
|
|
102
|
+
if (isString(description)) {
|
|
103
|
+
(Object.keys(root).length > 0 ? root : body)["$description"] = description;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
82
107
|
/**
|
|
83
108
|
* Build the site-style sheet text: custom properties on `:root`, plain properties on `body`,
|
|
84
109
|
* selector-keyed blocks (`"h1, h2"`, `a`, `.card`) as rules of their own, `&`-keyed blocks as
|
|
@@ -104,6 +129,7 @@ export const PAGE_ROOTS: SiteStyleRoots = { body: "body", root: ":root" };
|
|
|
104
129
|
* is dropped, which is what every host does
|
|
105
130
|
* @param {SiteStyleRoots} [roots] - The selectors that stand for the page's root and body; the
|
|
106
131
|
* page's own `:root` and `body` unless the sheet styles a page drawn inside something else
|
|
132
|
+
* @param {SiteStyleOptions} [options] - `comments: true` is the static build's form
|
|
107
133
|
* @returns {string}
|
|
108
134
|
* @docs studio/interface/canvas
|
|
109
135
|
*/
|
|
@@ -113,19 +139,20 @@ export function buildSiteStyleCSS(
|
|
|
113
139
|
transpose: (value: string) => string,
|
|
114
140
|
resolveValue?: SiteStyleValueResolver,
|
|
115
141
|
roots: SiteStyleRoots = PAGE_ROOTS,
|
|
142
|
+
options: SiteStyleOptions = {},
|
|
116
143
|
): string {
|
|
117
144
|
const rules: string[] = [];
|
|
118
145
|
const push = (style: JxStyle, selector: string | null) => {
|
|
119
|
-
const
|
|
146
|
+
const build: CssBuildOptions = { mediaQueries, scope: selector, transposeValue: transpose };
|
|
120
147
|
/* The selector is closed over here rather than threaded through `buildStyleRules`, whose own
|
|
121
148
|
hook sees a rule TARGET (self / descendant / unscoped) and not the selector it hangs off;
|
|
122
149
|
the compiler's report names the selector, so this is where it is known. Set only when given:
|
|
123
150
|
`exactOptionalPropertyTypes` refuses an explicit `undefined` for an optional hook. */
|
|
124
151
|
if (resolveValue) {
|
|
125
|
-
|
|
152
|
+
build.resolveValue = (property, value) => resolveValue(property, value, selector);
|
|
126
153
|
}
|
|
127
|
-
for (const rule of buildStyleRules(style,
|
|
128
|
-
rules.push(rule.text);
|
|
154
|
+
for (const rule of buildStyleRules(style, build)) {
|
|
155
|
+
rules.push(options.comments === true ? commentedRuleText(rule) : rule.text);
|
|
129
156
|
}
|
|
130
157
|
};
|
|
131
158
|
|
|
@@ -157,7 +184,8 @@ export function buildSiteStyleCSS(
|
|
|
157
184
|
}
|
|
158
185
|
continue;
|
|
159
186
|
}
|
|
160
|
-
|
|
187
|
+
// Metadata is no declaration; the block's own `$description` is placed after the split.
|
|
188
|
+
if (isNestedSelectorKey(key) || key.startsWith("@") || key.startsWith("$")) {
|
|
161
189
|
continue;
|
|
162
190
|
}
|
|
163
191
|
/* Custom properties go to `:root`; so does `color-scheme`, which is the one non-custom
|
|
@@ -172,6 +200,8 @@ export function buildSiteStyleCSS(
|
|
|
172
200
|
}
|
|
173
201
|
}
|
|
174
202
|
|
|
203
|
+
describeFirstRule(siteStyle, rootProps, bodyProps);
|
|
204
|
+
|
|
175
205
|
// Base rules precede conditional blocks so equal-specificity overrides win by source order.
|
|
176
206
|
push(rootProps, roots.root);
|
|
177
207
|
push(bodyProps, roots.body);
|
|
@@ -206,12 +236,16 @@ export function buildSiteStyleCSS(
|
|
|
206
236
|
}
|
|
207
237
|
continue;
|
|
208
238
|
}
|
|
239
|
+
if (k.startsWith("$")) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
209
242
|
if (k.startsWith("--")) {
|
|
210
243
|
condRoot[k] = v;
|
|
211
244
|
} else {
|
|
212
245
|
condBody[k] = v;
|
|
213
246
|
}
|
|
214
247
|
}
|
|
248
|
+
describeFirstRule(block, condRoot, condBody);
|
|
215
249
|
push({ [atKey]: condRoot }, roots.root);
|
|
216
250
|
push({ [atKey]: condBody }, roots.body);
|
|
217
251
|
for (const [sel, sub] of condSubs) {
|