@jxsuite/compiler 4.0.0 → 4.0.2
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/dist/cli.js +152 -151
- package/dist/compiler.js +80 -79
- package/package.json +10 -10
- package/src/shared.ts +45 -94
- package/src/site/site-build.ts +16 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/compiler",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.2",
|
|
4
4
|
"description": "Jx static HTML compiler, island detector, and site builder",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -45,20 +45,20 @@
|
|
|
45
45
|
"upgrade": "bun update --latest && bun install"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@apidevtools/json-schema-ref-parser": "^16.0.
|
|
49
|
-
"@jxsuite/create": "^1.3.
|
|
50
|
-
"@jxsuite/runtime": "^4.0.
|
|
51
|
-
"@jxsuite/schema": "^2.
|
|
52
|
-
"@jxsuite/site": "^2.0.
|
|
53
|
-
"@vue/reactivity": "3.5.
|
|
48
|
+
"@apidevtools/json-schema-ref-parser": "^16.0.3",
|
|
49
|
+
"@jxsuite/create": "^1.3.12",
|
|
50
|
+
"@jxsuite/runtime": "^4.0.2",
|
|
51
|
+
"@jxsuite/schema": "^2.3.0",
|
|
52
|
+
"@jxsuite/site": "^2.0.2",
|
|
53
|
+
"@vue/reactivity": "3.5.43",
|
|
54
54
|
"esbuild": "^0.28.2",
|
|
55
55
|
"lit-html": "^3.3.3",
|
|
56
|
-
"sharp": "^0.35.
|
|
56
|
+
"sharp": "^0.35.5"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@happy-dom/global-registrator": "^20.14.5",
|
|
60
|
-
"@jxsuite/connector": "^0.6.
|
|
61
|
-
"@jxsuite/parser": "^1.8.
|
|
60
|
+
"@jxsuite/connector": "^0.6.1",
|
|
61
|
+
"@jxsuite/parser": "^1.8.2",
|
|
62
62
|
"@types/mdast": "^4.0.4"
|
|
63
63
|
}
|
|
64
64
|
}
|
package/src/shared.ts
CHANGED
|
@@ -13,16 +13,14 @@ import {
|
|
|
13
13
|
booleanAttrValue,
|
|
14
14
|
buildStyleRules,
|
|
15
15
|
enumeratedAttrNames,
|
|
16
|
-
isDeclarationAtRule,
|
|
17
|
-
isKeyframesAtRule,
|
|
18
16
|
isNestedSelectorKey,
|
|
19
17
|
camelToKebab,
|
|
20
18
|
isSingleExpression,
|
|
21
|
-
pureSchemeOf,
|
|
22
19
|
splitSelectorList,
|
|
23
20
|
} from "@jxsuite/runtime";
|
|
24
21
|
import { evaluateExpression, isMutating } from "@jxsuite/runtime/expression";
|
|
25
22
|
import { runStatements } from "@jxsuite/runtime/statements";
|
|
23
|
+
import { buildSiteStyleCSS } from "@jxsuite/site/site-style";
|
|
26
24
|
import {
|
|
27
25
|
bodyReturnsValue,
|
|
28
26
|
childrenContainArray,
|
|
@@ -1110,17 +1108,30 @@ function pushStyleRules(
|
|
|
1110
1108
|
against a live scope, which a compiled page has only where the runtime is present, so a
|
|
1111
1109
|
static emitter has always dropped it. Silently, which is the defect: a document is correct
|
|
1112
1110
|
in Studio and simply unstyled in the built page, with nothing said. */
|
|
1113
|
-
resolveValue: (property, value) =>
|
|
1114
|
-
const source = typeof value === "string" ? value : (value as { $ref: string }).$ref;
|
|
1115
|
-
_droppedReactive.push({ property, selector, source });
|
|
1116
|
-
return null;
|
|
1117
|
-
},
|
|
1111
|
+
resolveValue: (property, value) => recordDroppedReactive(property, value, selector),
|
|
1118
1112
|
scope: selector,
|
|
1119
1113
|
})) {
|
|
1120
1114
|
rules.push(rule.text);
|
|
1121
1115
|
}
|
|
1122
1116
|
}
|
|
1123
1117
|
|
|
1118
|
+
/**
|
|
1119
|
+
* Record a reactive declaration a static build drops, and answer `null` so it is dropped.
|
|
1120
|
+
*
|
|
1121
|
+
* One recorder for both emitters the build reaches — `pushStyleRules` for element styles, and
|
|
1122
|
+
* `buildSiteStyleCSS` for the project block, which takes it as its resolver — so a `${…}` in
|
|
1123
|
+
* `project.json#/style` is reported the way one in an element's `style` is.
|
|
1124
|
+
*/
|
|
1125
|
+
function recordDroppedReactive(
|
|
1126
|
+
property: string,
|
|
1127
|
+
value: string | JxRef,
|
|
1128
|
+
selector: string | null,
|
|
1129
|
+
): null {
|
|
1130
|
+
const source = typeof value === "string" ? value : value.$ref;
|
|
1131
|
+
_droppedReactive.push({ property, selector, source });
|
|
1132
|
+
return null;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1124
1135
|
/** One entry per reactive declaration a static build dropped, since the last drain. */
|
|
1125
1136
|
const _droppedReactive: { property: string; selector: string | null; source: string }[] = [];
|
|
1126
1137
|
|
|
@@ -1188,6 +1199,21 @@ export function attrHelperSource(): string {
|
|
|
1188
1199
|
/**
|
|
1189
1200
|
* Walk the entire document tree and collect all static nested CSS rules.
|
|
1190
1201
|
*
|
|
1202
|
+
* The project block is NOT split here. `buildSiteStyleCSS` (`@jxsuite/site/site-style`) is the one
|
|
1203
|
+
* definition of how `project.json#/style` becomes a page's stylesheet — custom properties and
|
|
1204
|
+
* `color-scheme` on `:root`, other declarations on `body`, `&`-keyed blocks as states of `:root`,
|
|
1205
|
+
* selector keys as their own rules, `@`-blocks resolved and dual-emitted, then the forced-scheme
|
|
1206
|
+
* `color-scheme` triplet — and the built page calls it with an identity transposer, so the canvas
|
|
1207
|
+
* and the live preview show what the build ships by construction. This function used to carry its
|
|
1208
|
+
* own copy of that split, and the two copies disagreed on two shapes the site builder had right: a
|
|
1209
|
+
* top-level `&[data-theme="light"]` reached the page as a raw `&` selector, which no engine
|
|
1210
|
+
* matches, and a top-level `colorScheme` went to `body`, where `light-dark()` on the root reads the
|
|
1211
|
+
* UA default instead (#329, spec.md §9.5).
|
|
1212
|
+
*
|
|
1213
|
+
* The one thing the build adds is the resolver: a host drops a reactive project declaration in
|
|
1214
|
+
* silence because it has nothing to evaluate it against, and a static build drops it for the same
|
|
1215
|
+
* reason but SAYS SO, through the same recorder `pushStyleRules` uses for element styles.
|
|
1216
|
+
*
|
|
1191
1217
|
* @param {JxElement | JxMutableNode} doc
|
|
1192
1218
|
* @param {Record<string, string>} [mediaQueries]
|
|
1193
1219
|
* @param {JxStyle | null} [projectStyle]
|
|
@@ -1200,92 +1226,17 @@ export function compileStyles(
|
|
|
1200
1226
|
) {
|
|
1201
1227
|
const rules: string[] = [];
|
|
1202
1228
|
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
const conditionalBlocks: [string, JxStyle][] = [];
|
|
1215
|
-
for (const [key, val] of Object.entries(projectStyle)) {
|
|
1216
|
-
if (val !== null && typeof val === "object" && !Array.isArray(val)) {
|
|
1217
|
-
if (key.startsWith("@")) {
|
|
1218
|
-
conditionalBlocks.push([key, val]);
|
|
1219
|
-
} else if (!key.startsWith("--")) {
|
|
1220
|
-
selectorBlocks.push([key, val]);
|
|
1221
|
-
}
|
|
1222
|
-
continue;
|
|
1223
|
-
}
|
|
1224
|
-
if (isNestedSelectorKey(key) || key.startsWith("@")) {
|
|
1225
|
-
continue;
|
|
1226
|
-
}
|
|
1227
|
-
if (key.startsWith("--")) {
|
|
1228
|
-
rootProps[key] = val;
|
|
1229
|
-
} else {
|
|
1230
|
-
bodyProps[key] = val;
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
|
|
1234
|
-
// Base rules precede conditional blocks so equal-specificity overrides win by source order.
|
|
1235
|
-
pushStyleRules(rules, rootProps, ":root", mediaQueries);
|
|
1236
|
-
pushStyleRules(rules, bodyProps, "body", mediaQueries);
|
|
1237
|
-
|
|
1238
|
-
for (const [key, val] of selectorBlocks) {
|
|
1239
|
-
// A top-level selector key IS the selector — `.card` styles `.card`, not `:root .card`.
|
|
1240
|
-
pushStyleRules(rules, val, key, mediaQueries);
|
|
1241
|
-
}
|
|
1242
|
-
|
|
1243
|
-
for (const [key, val] of conditionalBlocks) {
|
|
1244
|
-
/* An unscoped at-rule has no selector to split across, and its name is global —
|
|
1245
|
-
`@font-face` at project level is one block, not one per target. `@keyframes` is here for
|
|
1246
|
-
a stronger reason than tidiness: splitting it would emit one same-named block per stop,
|
|
1247
|
-
and the last definition of a name replaces every earlier one, so the animation would keep
|
|
1248
|
-
only its final stop. */
|
|
1249
|
-
if (isDeclarationAtRule(key) || isKeyframesAtRule(key)) {
|
|
1250
|
-
pushStyleRules(rules, { [key]: val }, null, mediaQueries);
|
|
1251
|
-
continue;
|
|
1252
|
-
}
|
|
1253
|
-
// Conditional block at project top level: custom properties override :root, direct
|
|
1254
|
-
// Properties override body, selector-keyed sub-objects their own selector.
|
|
1255
|
-
const condRoot: JxStyle = {};
|
|
1256
|
-
const condBody: JxStyle = {};
|
|
1257
|
-
const condSubs: [string, JxStyle][] = [];
|
|
1258
|
-
for (const [k, v] of Object.entries(val)) {
|
|
1259
|
-
if (v !== null && typeof v === "object" && !Array.isArray(v)) {
|
|
1260
|
-
if (!k.startsWith("@")) {
|
|
1261
|
-
condSubs.push([k, v]);
|
|
1262
|
-
}
|
|
1263
|
-
continue;
|
|
1264
|
-
}
|
|
1265
|
-
if (k.startsWith("--")) {
|
|
1266
|
-
condRoot[k] = v;
|
|
1267
|
-
} else {
|
|
1268
|
-
condBody[k] = v;
|
|
1269
|
-
}
|
|
1270
|
-
}
|
|
1271
|
-
pushStyleRules(rules, { [key]: condRoot }, ":root", mediaQueries);
|
|
1272
|
-
pushStyleRules(rules, { [key]: condBody }, "body", mediaQueries);
|
|
1273
|
-
for (const [sel, sub] of condSubs) {
|
|
1274
|
-
pushStyleRules(rules, { [key]: sub }, sel, mediaQueries);
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
// Forced-scheme UA hint: native widgets follow the forced attribute, not only the OS scheme.
|
|
1280
|
-
if (
|
|
1281
|
-
Object.values(mediaQueries).some((q) => pureSchemeOf(q) !== null) &&
|
|
1282
|
-
!(projectStyle && typeof projectStyle === "object" && "colorScheme" in projectStyle)
|
|
1283
|
-
) {
|
|
1284
|
-
rules.push(
|
|
1285
|
-
":root { color-scheme: light dark }",
|
|
1286
|
-
`:root:where([${COLOR_SCHEME_ATTR}="light"]) { color-scheme: light }`,
|
|
1287
|
-
`:root:where([${COLOR_SCHEME_ATTR}="dark"]) { color-scheme: dark }`,
|
|
1288
|
-
);
|
|
1229
|
+
/* An absent project block is an empty one: the builder emits nothing for `{}` except the
|
|
1230
|
+
`color-scheme` triplet a scheme query in `$media` still calls for, which is exactly what a
|
|
1231
|
+
page with a scheme query and no project style has always carried. */
|
|
1232
|
+
const projectSheet = buildSiteStyleCSS(
|
|
1233
|
+
projectStyle && typeof projectStyle === "object" ? projectStyle : {},
|
|
1234
|
+
mediaQueries,
|
|
1235
|
+
(value) => value,
|
|
1236
|
+
recordDroppedReactive,
|
|
1237
|
+
);
|
|
1238
|
+
if (projectSheet !== "") {
|
|
1239
|
+
rules.push(projectSheet);
|
|
1289
1240
|
}
|
|
1290
1241
|
|
|
1291
1242
|
const counter = { n: 0 };
|
package/src/site/site-build.ts
CHANGED
|
@@ -456,11 +456,24 @@ export async function buildSite(
|
|
|
456
456
|
// The full drive path on Windows, so resolve() then wrote the sidecar back into the
|
|
457
457
|
// Source tree instead of dist.
|
|
458
458
|
const outName = f.tagName ? `${f.tagName}.js` : basename(f.path);
|
|
459
|
-
|
|
460
|
-
|
|
459
|
+
/*
|
|
460
|
+
* Written and recorded ONCE per tag, in first-seen order. `compileElement` returns a
|
|
461
|
+
* component's `$elements` dependencies as extra files, so a dependency two parents name
|
|
462
|
+
* arrives here once per parent and once more for its own compile — and
|
|
463
|
+
* `injectComponentScripts` inlines a tag's stylesheet and loads its module once per
|
|
464
|
+
* record: three copies of `lcb-icon { display: inline-block }` in one head on a
|
|
465
|
+
* card → button → icon page (#330). The FIRST position is the one kept, because
|
|
466
|
+
* equal-specificity component rules cascade by source order and a later duplicate must
|
|
467
|
+
* not move a sheet that already landed. The write is skipped with the record: every
|
|
468
|
+
* arrival is the same file compiled with the same options (a fresh `visited` set each
|
|
469
|
+
* call), so the bytes are identical and a second write only inflates `fileCount`, which
|
|
470
|
+
* reports files produced rather than writes attempted.
|
|
471
|
+
*/
|
|
472
|
+
if (!compiledComponentTags.includes(f.tagName)) {
|
|
473
|
+
writeFileSync(resolve(componentOutDir, outName), f.content, "utf8");
|
|
461
474
|
compiledComponentTags.push(f.tagName);
|
|
475
|
+
fileCount += 1;
|
|
462
476
|
}
|
|
463
|
-
fileCount += 1;
|
|
464
477
|
}
|
|
465
478
|
|
|
466
479
|
// Pre-render component HTML scaffold and CSS sidecar
|