@jxsuite/compiler 0.6.0 → 0.6.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/compiler.js +79 -79
- package/package.json +1 -1
- package/src/shared.js +4 -2
- package/src/site/content-loader.js +2 -2
- package/src/site/layout-resolver.js +2 -1
- package/src/site/site-build.js +73 -81
package/package.json
CHANGED
package/src/shared.js
CHANGED
|
@@ -451,8 +451,10 @@ export function buildAttrs(def, scope) {
|
|
|
451
451
|
if (!def.attributes?.decoding) out += ` decoding="async"`;
|
|
452
452
|
}
|
|
453
453
|
|
|
454
|
-
if (def.$
|
|
455
|
-
out += ` data-jx-
|
|
454
|
+
if (def.$static) {
|
|
455
|
+
out += ` data-jx-static`;
|
|
456
|
+
} else if (def.$prerendered) {
|
|
457
|
+
out += ` data-jx-prerendered`;
|
|
456
458
|
}
|
|
457
459
|
|
|
458
460
|
return out;
|
|
@@ -126,12 +126,12 @@ async function getMarkdownModule() {
|
|
|
126
126
|
async function loadMarkdownEntry(filePath, directiveOptions) {
|
|
127
127
|
const { MarkdownFile } = await getMarkdownModule();
|
|
128
128
|
const file = new MarkdownFile({ src: filePath, directiveOptions });
|
|
129
|
-
const result =
|
|
129
|
+
const result = file.resolve();
|
|
130
130
|
return {
|
|
131
131
|
id: result.slug,
|
|
132
132
|
data: result.frontmatter,
|
|
133
133
|
body: readFileSync(filePath, "utf-8"),
|
|
134
|
-
|
|
134
|
+
$children: result.$children,
|
|
135
135
|
_meta: {
|
|
136
136
|
excerpt: result.$excerpt,
|
|
137
137
|
toc: result.$toc,
|
|
@@ -54,7 +54,8 @@ export function resolveLayout(pageDoc, projectConfig, projectRoot) {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
// Distribute page children into layout slots
|
|
57
|
-
const
|
|
57
|
+
const rawChildren = pageDoc.children ?? [];
|
|
58
|
+
const pageChildren = typeof rawChildren === "string" ? [rawChildren] : rawChildren;
|
|
58
59
|
const merged = deepClone(layoutDoc);
|
|
59
60
|
|
|
60
61
|
distributeSlots(merged, pageChildren);
|
package/src/site/site-build.js
CHANGED
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
isComponentFullyStatic,
|
|
34
34
|
buildComponentCSS,
|
|
35
35
|
collectServerEntries,
|
|
36
|
+
renderStaticNode,
|
|
36
37
|
DEFAULT_REACTIVITY_SRC,
|
|
37
38
|
DEFAULT_LIT_HTML_SRC,
|
|
38
39
|
} from "../shared.js";
|
|
@@ -104,8 +105,6 @@ export async function buildSite(projectRoot, options = {}) {
|
|
|
104
105
|
/** @type {string[]} */
|
|
105
106
|
const compiledComponentTags = [];
|
|
106
107
|
/** @type {Map<string, string>} */
|
|
107
|
-
const preRendered = new Map(); // tagName → innerHTML (default state, fallback)
|
|
108
|
-
/** @type {Map<string, string>} */
|
|
109
108
|
const componentCSS = new Map(); // tagName → CSS text
|
|
110
109
|
/** @type {Map<string, any>} */
|
|
111
110
|
const componentDefs = new Map(); // tagName → parsed component definition
|
|
@@ -138,9 +137,6 @@ export async function buildSite(projectRoot, options = {}) {
|
|
|
138
137
|
: JSON.parse(readFileSync(componentPath, "utf8"));
|
|
139
138
|
if (doc.tagName) {
|
|
140
139
|
componentDefs.set(doc.tagName, doc);
|
|
141
|
-
const innerHTML = preRenderComponentHtml(doc);
|
|
142
|
-
if (innerHTML) preRendered.set(doc.tagName, innerHTML);
|
|
143
|
-
|
|
144
140
|
const css = buildComponentCSS(doc.tagName, doc.style);
|
|
145
141
|
if (css) {
|
|
146
142
|
componentCSS.set(doc.tagName, css);
|
|
@@ -186,16 +182,14 @@ export async function buildSite(projectRoot, options = {}) {
|
|
|
186
182
|
collections,
|
|
187
183
|
imageCache,
|
|
188
184
|
outDir,
|
|
185
|
+
componentDefs,
|
|
189
186
|
);
|
|
190
187
|
|
|
191
|
-
//
|
|
192
|
-
// Must happen before script injection so we know which tags are fully static
|
|
188
|
+
// Determine which component tags are fully static (for script omission)
|
|
193
189
|
/** @type {Set<string>} */
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
result.html = preResult.html;
|
|
198
|
-
staticTags = preResult.staticTags;
|
|
190
|
+
const staticTags = new Set();
|
|
191
|
+
for (const [tag, def] of componentDefs) {
|
|
192
|
+
if (isComponentFullyStatic(def)) staticTags.add(tag);
|
|
199
193
|
}
|
|
200
194
|
|
|
201
195
|
// Inject component CSS and JS scripts
|
|
@@ -332,6 +326,7 @@ async function compilePage(
|
|
|
332
326
|
collections = new Map(),
|
|
333
327
|
imageCache = null,
|
|
334
328
|
outDir = "",
|
|
329
|
+
componentDefs = new Map(),
|
|
335
330
|
) {
|
|
336
331
|
// Load the raw page document
|
|
337
332
|
let pageDoc;
|
|
@@ -377,6 +372,9 @@ async function compilePage(
|
|
|
377
372
|
// so that timing: "compiler" data is baked into the static HTML
|
|
378
373
|
resolveDocTemplates(layoutDoc, scope);
|
|
379
374
|
|
|
375
|
+
// Expand registered custom elements (apply $props, pre-render, mark static/prerendered)
|
|
376
|
+
expandComponents(layoutDoc, componentDefs);
|
|
377
|
+
|
|
380
378
|
// Strip resolved timing: "compiler" state entries — they're now baked into the tree
|
|
381
379
|
// and keeping them would cause isDynamic() to misclassify the page as dynamic
|
|
382
380
|
if (layoutDoc.state) {
|
|
@@ -551,11 +549,73 @@ function resolveDocTemplates(node, scope) {
|
|
|
551
549
|
}
|
|
552
550
|
}
|
|
553
551
|
}
|
|
552
|
+
if (typeof node.children === "string" && isTemplateString(node.children)) {
|
|
553
|
+
const resolved = evaluateStaticTemplate(node.children, scope);
|
|
554
|
+
if (Array.isArray(resolved)) {
|
|
555
|
+
node.children = resolved;
|
|
556
|
+
for (const child of node.children) {
|
|
557
|
+
resolveDocTemplates(child, scope);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
} else if (Array.isArray(node.children)) {
|
|
561
|
+
let i = 0;
|
|
562
|
+
while (i < node.children.length) {
|
|
563
|
+
const child = node.children[i];
|
|
564
|
+
if (typeof child === "string" && isTemplateString(child)) {
|
|
565
|
+
const resolved = evaluateStaticTemplate(child, scope);
|
|
566
|
+
if (Array.isArray(resolved)) {
|
|
567
|
+
node.children.splice(i, 1, ...resolved);
|
|
568
|
+
for (const spliced of resolved) {
|
|
569
|
+
resolveDocTemplates(spliced, scope);
|
|
570
|
+
}
|
|
571
|
+
i += resolved.length;
|
|
572
|
+
continue;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
resolveDocTemplates(child, scope);
|
|
576
|
+
i++;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Walk the document tree and expand registered custom elements in-place. Applies $props via
|
|
583
|
+
* preRenderComponentHtml, marks static/prerendered.
|
|
584
|
+
*
|
|
585
|
+
* @param {any} node
|
|
586
|
+
* @param {Map<string, any>} componentDefs
|
|
587
|
+
*/
|
|
588
|
+
function expandComponents(node, componentDefs) {
|
|
589
|
+
if (!node || typeof node !== "object") return;
|
|
590
|
+
if (Array.isArray(node)) {
|
|
591
|
+
node.forEach((n) => expandComponents(n, componentDefs));
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Recurse into children first (bottom-up expansion)
|
|
554
596
|
if (Array.isArray(node.children)) {
|
|
555
597
|
for (const child of node.children) {
|
|
556
|
-
|
|
598
|
+
expandComponents(child, componentDefs);
|
|
557
599
|
}
|
|
558
600
|
}
|
|
601
|
+
|
|
602
|
+
const def = componentDefs.get(node.tagName);
|
|
603
|
+
if (def) {
|
|
604
|
+
const slotContent =
|
|
605
|
+
Array.isArray(node.children) && node.children.length > 0
|
|
606
|
+
? node.children.map((/** @type {any} */ c) => renderStaticNode(c, {}, null)).join("\n")
|
|
607
|
+
: null;
|
|
608
|
+
|
|
609
|
+
const innerHTML = preRenderComponentHtml(def, node.$props || null, slotContent);
|
|
610
|
+
const isStatic = isComponentFullyStatic(def);
|
|
611
|
+
|
|
612
|
+
node.innerHTML = innerHTML;
|
|
613
|
+
delete node.children;
|
|
614
|
+
delete node.$props;
|
|
615
|
+
|
|
616
|
+
if (isStatic) node.$static = true;
|
|
617
|
+
else node.$prerendered = true;
|
|
618
|
+
}
|
|
559
619
|
}
|
|
560
620
|
|
|
561
621
|
/**
|
|
@@ -616,74 +676,6 @@ function injectComponentScripts(
|
|
|
616
676
|
return html.replace("</body>", ` ${injection}\n</body>`);
|
|
617
677
|
}
|
|
618
678
|
|
|
619
|
-
/**
|
|
620
|
-
* Inject pre-rendered HTML scaffolding into component tags, using instance-specific props.
|
|
621
|
-
*
|
|
622
|
-
* @param {string} html
|
|
623
|
-
* @param {Map<string, string>} preRendered - TagName → default innerHTML (fallback)
|
|
624
|
-
* @param {Map<string, any>} componentDefs - TagName → parsed component definition
|
|
625
|
-
* @returns {{ html: string; staticTags: Set<string> }}
|
|
626
|
-
*/
|
|
627
|
-
function injectPreRenderedComponents(html, preRendered, componentDefs) {
|
|
628
|
-
/** @type {Set<string>} */
|
|
629
|
-
const staticTags = new Set();
|
|
630
|
-
/** @type {Map<string, boolean>} */
|
|
631
|
-
const tagHasNonStaticInstance = new Map();
|
|
632
|
-
|
|
633
|
-
for (const [tag] of componentDefs) {
|
|
634
|
-
// Match both empty tags and tags with inner content (for slotted components)
|
|
635
|
-
const pattern = new RegExp(`<${tag}(\\s[^>]*?)?>([\\s\\S]*?)</${tag}>`, "g");
|
|
636
|
-
html = html.replace(pattern, (match, attrsStr, existingInner) => {
|
|
637
|
-
const attrs = attrsStr ?? "";
|
|
638
|
-
const doc = componentDefs.get(tag);
|
|
639
|
-
if (!doc) {
|
|
640
|
-
// No definition, use default pre-rendered content
|
|
641
|
-
const fallback = preRendered.get(tag) ?? "";
|
|
642
|
-
return `<${tag}${attrs}>${fallback}</${tag}>`;
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
// Extract data-jx-props from the attribute string
|
|
646
|
-
/** @type {Record<string, any> | null} */
|
|
647
|
-
let props = null;
|
|
648
|
-
const propsMatch = attrs.match(/\sdata-jx-props="([^"]*)"/);
|
|
649
|
-
if (propsMatch) {
|
|
650
|
-
try {
|
|
651
|
-
props = JSON.parse(
|
|
652
|
-
propsMatch[1]
|
|
653
|
-
.replace(/"/g, '"')
|
|
654
|
-
.replace(/&/g, "&")
|
|
655
|
-
.replace(/</g, "<")
|
|
656
|
-
.replace(/>/g, ">")
|
|
657
|
-
.replace(/'/g, "'"),
|
|
658
|
-
);
|
|
659
|
-
} catch {}
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
// Pre-render with instance-specific props
|
|
663
|
-
const slotContent = existingInner.trim() || null;
|
|
664
|
-
const innerHTML = preRenderComponentHtml(doc, props, slotContent);
|
|
665
|
-
|
|
666
|
-
const isStatic = isComponentFullyStatic(doc);
|
|
667
|
-
if (!isStatic) tagHasNonStaticInstance.set(tag, true);
|
|
668
|
-
|
|
669
|
-
if (isStatic) {
|
|
670
|
-
// Strip data-jx-props from attrs for fully static instances
|
|
671
|
-
let cleanAttrs = attrs.replace(/\s*data-jx-props="[^"]*"/, "");
|
|
672
|
-
return `<${tag}${cleanAttrs} data-jx-static>${innerHTML}</${tag}>`;
|
|
673
|
-
} else {
|
|
674
|
-
return `<${tag}${attrs} data-jx-prerendered>${innerHTML}</${tag}>`;
|
|
675
|
-
}
|
|
676
|
-
});
|
|
677
|
-
|
|
678
|
-
// Track whether ALL instances of this tag are static
|
|
679
|
-
if (componentDefs.has(tag) && !tagHasNonStaticInstance.has(tag)) {
|
|
680
|
-
staticTags.add(tag);
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
return { html, staticTags };
|
|
685
|
-
}
|
|
686
|
-
|
|
687
679
|
/**
|
|
688
680
|
* Inject <script type="module"> tags for npm package $elements (cherry-picked component imports).
|
|
689
681
|
* Bare specifiers are resolved to /node_modules/ paths.
|