@jxsuite/compiler 0.21.4 → 0.22.1
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 +35 -35
- package/package.json +3 -3
- package/src/shared.js +32 -23
- package/src/site/content-loader.js +2 -60
- package/src/site/context-injection.js +0 -52
- package/src/site/head-merger.js +6 -2
- package/src/site/prototype-resolver.js +19 -24
- package/src/site/site-build.js +6 -3
- package/src/targets/compile-element.js +4 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/compiler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.1",
|
|
4
4
|
"description": "Jx static HTML compiler, island detector, and site builder",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@apidevtools/json-schema-ref-parser": "^15.3.5",
|
|
41
|
-
"@jxsuite/parser": "^0.
|
|
42
|
-
"@jxsuite/runtime": "^0.
|
|
41
|
+
"@jxsuite/parser": "^0.22.1",
|
|
42
|
+
"@jxsuite/runtime": "^0.22.1",
|
|
43
43
|
"remark-gfm": "^4.0.1",
|
|
44
44
|
"remark-stringify": "^11.0.0",
|
|
45
45
|
"sharp": "^0.34.5",
|
package/src/shared.js
CHANGED
|
@@ -518,11 +518,11 @@ export function compileStyles(doc, mediaQueries = {}, projectStyle = null) {
|
|
|
518
518
|
for (const [key, val] of Object.entries(obj)) {
|
|
519
519
|
if (val === null || typeof val !== "object" || Array.isArray(val)) continue;
|
|
520
520
|
if (key.startsWith("@")) {
|
|
521
|
-
const
|
|
522
|
-
?
|
|
523
|
-
: key
|
|
521
|
+
const atRule = key.startsWith("@--")
|
|
522
|
+
? `@media ${mediaQueries[key.slice(1)] ?? key.slice(1)}`
|
|
523
|
+
: key;
|
|
524
524
|
const mProps = toCSSText(val);
|
|
525
|
-
if (mProps) rules.push(
|
|
525
|
+
if (mProps) rules.push(`${atRule} { ${selector} { ${mProps} } }`);
|
|
526
526
|
for (const [sel, sub] of Object.entries(/** @type {Record<string, unknown>} */ (val))) {
|
|
527
527
|
if (sub === null || typeof sub !== "object" || Array.isArray(sub)) continue;
|
|
528
528
|
if (sel.startsWith("@")) continue;
|
|
@@ -532,7 +532,7 @@ export function compileStyles(doc, mediaQueries = {}, projectStyle = null) {
|
|
|
532
532
|
? `${selector}${sel}`
|
|
533
533
|
: `${selector} ${sel}`;
|
|
534
534
|
const subProps = toCSSText(sub);
|
|
535
|
-
if (subProps) rules.push(
|
|
535
|
+
if (subProps) rules.push(`${atRule} { ${resolved} { ${subProps} } }`);
|
|
536
536
|
}
|
|
537
537
|
continue;
|
|
538
538
|
}
|
|
@@ -558,10 +558,12 @@ export function compileStyles(doc, mediaQueries = {}, projectStyle = null) {
|
|
|
558
558
|
emitProjectRules(key, val);
|
|
559
559
|
} else if (key.startsWith("@")) {
|
|
560
560
|
// @media block at top level
|
|
561
|
-
const
|
|
562
|
-
?
|
|
563
|
-
: key.
|
|
564
|
-
|
|
561
|
+
const atRule = key.startsWith("@--")
|
|
562
|
+
? `@media ${mediaQueries[key.slice(1)] ?? key.slice(1)}`
|
|
563
|
+
: key.startsWith("@(")
|
|
564
|
+
? `@media ${key.slice(1)}`
|
|
565
|
+
: key;
|
|
566
|
+
rules.push(`${atRule} { body { ${toCSSText(/** @type {object} */ (val))} } }`);
|
|
565
567
|
}
|
|
566
568
|
}
|
|
567
569
|
// Collect CSS custom properties into :root {}
|
|
@@ -610,11 +612,13 @@ function emitNestedElement(selector, obj, rules, mediaQueries) {
|
|
|
610
612
|
for (const [key, val] of Object.entries(obj)) {
|
|
611
613
|
if (val === null || typeof val !== "object" || Array.isArray(val)) continue;
|
|
612
614
|
if (key.startsWith("@")) {
|
|
613
|
-
const
|
|
614
|
-
?
|
|
615
|
-
: key.
|
|
615
|
+
const atRule = key.startsWith("@--")
|
|
616
|
+
? `@media ${mediaQueries[key.slice(1)] ?? key.slice(1)}`
|
|
617
|
+
: key.startsWith("@(")
|
|
618
|
+
? `@media ${key.slice(1)}`
|
|
619
|
+
: key;
|
|
616
620
|
const mProps = toCSSText(val);
|
|
617
|
-
if (mProps) rules.push(
|
|
621
|
+
if (mProps) rules.push(`${atRule} { ${selector} { ${mProps} } }`);
|
|
618
622
|
for (const [sel, sub] of Object.entries(/** @type {Record<string, unknown>} */ (val))) {
|
|
619
623
|
if (sub === null || typeof sub !== "object" || Array.isArray(sub)) continue;
|
|
620
624
|
if (sel.startsWith("@")) continue;
|
|
@@ -624,7 +628,7 @@ function emitNestedElement(selector, obj, rules, mediaQueries) {
|
|
|
624
628
|
? `${selector}${sel}`
|
|
625
629
|
: `${selector} ${sel}`;
|
|
626
630
|
const subProps = toCSSText(sub);
|
|
627
|
-
if (subProps) rules.push(
|
|
631
|
+
if (subProps) rules.push(`${atRule} { ${resolved} { ${subProps} } }`);
|
|
628
632
|
}
|
|
629
633
|
continue;
|
|
630
634
|
}
|
|
@@ -688,10 +692,13 @@ export function collectStyles(
|
|
|
688
692
|
for (const [prop, val] of Object.entries(def.style)) {
|
|
689
693
|
if (val === null || typeof val !== "object" || Array.isArray(val)) continue;
|
|
690
694
|
if (prop.startsWith("@")) {
|
|
691
|
-
const
|
|
692
|
-
?
|
|
693
|
-
: prop.
|
|
694
|
-
|
|
695
|
+
const atRule = prop.startsWith("@--")
|
|
696
|
+
? `@media ${mediaQueries[prop.slice(1)] ?? prop.slice(1)}`
|
|
697
|
+
: prop.startsWith("@(")
|
|
698
|
+
? `@media ${prop.slice(1)}`
|
|
699
|
+
: prop;
|
|
700
|
+
const atBaseProps = toCSSText(val);
|
|
701
|
+
if (atBaseProps) rules.push(`${atRule} { ${selector} { ${atBaseProps} } }`);
|
|
695
702
|
for (const [sel, nestedRules] of Object.entries(
|
|
696
703
|
/** @type {Record<string, unknown>} */ (val),
|
|
697
704
|
)) {
|
|
@@ -703,7 +710,7 @@ export function collectStyles(
|
|
|
703
710
|
: sel.startsWith(":") || sel.startsWith(".") || sel.startsWith("[")
|
|
704
711
|
? `${selector}${sel}`
|
|
705
712
|
: `${selector} ${sel}`;
|
|
706
|
-
rules.push(
|
|
713
|
+
rules.push(`${atRule} { ${resolved} { ${toCSSText(nestedRules)} } }`);
|
|
707
714
|
}
|
|
708
715
|
} else {
|
|
709
716
|
const resolved = prop.startsWith("&")
|
|
@@ -1038,10 +1045,12 @@ export function buildComponentCSS(tagName, styleDef, doc = null, mediaQueries =
|
|
|
1038
1045
|
|
|
1039
1046
|
for (const [prop, val] of Object.entries(styleDef)) {
|
|
1040
1047
|
if (prop.startsWith("@")) {
|
|
1041
|
-
const
|
|
1042
|
-
?
|
|
1043
|
-
: prop.
|
|
1044
|
-
|
|
1048
|
+
const atRule = prop.startsWith("@--")
|
|
1049
|
+
? `@media ${mediaQueries[prop.slice(1)] ?? prop.slice(1)}`
|
|
1050
|
+
: prop.startsWith("@(")
|
|
1051
|
+
? `@media ${prop.slice(1)}`
|
|
1052
|
+
: prop;
|
|
1053
|
+
rules.push(`${atRule} { ${tagName} { ${toCSSText(/** @type {object} */ (val))} } }`);
|
|
1045
1054
|
} else if (
|
|
1046
1055
|
prop.startsWith(":") ||
|
|
1047
1056
|
prop.startsWith(".") ||
|
|
@@ -381,67 +381,9 @@ function validateEntries(entries, schema, contentTypeName) {
|
|
|
381
381
|
}
|
|
382
382
|
}
|
|
383
383
|
|
|
384
|
-
// ─── Content Type Querying
|
|
384
|
+
// ─── Content Type Querying (re-exported from @jxsuite/parser/content) ────────
|
|
385
385
|
|
|
386
|
-
|
|
387
|
-
* Query a loaded content type with filter, sort, and limit. Implements the ContentCollection
|
|
388
|
-
* $prototype resolution.
|
|
389
|
-
*
|
|
390
|
-
* @param {ContentLoaderEntry[]} entries - Full content type entries
|
|
391
|
-
* @param {{
|
|
392
|
-
* filter?: Record<string, unknown>;
|
|
393
|
-
* sort?: { field: string; order?: string };
|
|
394
|
-
* limit?: number;
|
|
395
|
-
* }} [query]
|
|
396
|
-
* - Query options
|
|
397
|
-
* @returns {ContentLoaderEntry[]} Filtered, sorted, limited entries
|
|
398
|
-
*/
|
|
399
|
-
export function queryContentType(entries, query = {}) {
|
|
400
|
-
let result = [...entries];
|
|
401
|
-
|
|
402
|
-
// Filter
|
|
403
|
-
if (query.filter && typeof query.filter === "object") {
|
|
404
|
-
result = result.filter((entry) => {
|
|
405
|
-
for (const [key, expected] of Object.entries(
|
|
406
|
-
/** @type {Record<string, unknown>} */ (query.filter),
|
|
407
|
-
)) {
|
|
408
|
-
const actual = entry.data[key];
|
|
409
|
-
if (actual !== expected) return false;
|
|
410
|
-
}
|
|
411
|
-
return true;
|
|
412
|
-
});
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
// Sort
|
|
416
|
-
if (query.sort) {
|
|
417
|
-
const { field, order = "asc" } = query.sort;
|
|
418
|
-
result.sort((a, b) => {
|
|
419
|
-
const aVal = /** @type {string | number} */ (a.data[field] ?? "");
|
|
420
|
-
const bVal = /** @type {string | number} */ (b.data[field] ?? "");
|
|
421
|
-
if (aVal < bVal) return order === "asc" ? -1 : 1;
|
|
422
|
-
if (aVal > bVal) return order === "asc" ? 1 : -1;
|
|
423
|
-
return 0;
|
|
424
|
-
});
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
// Limit
|
|
428
|
-
if (query.limit && query.limit > 0) {
|
|
429
|
-
result = result.slice(0, query.limit);
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
return result;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
/**
|
|
436
|
-
* Find a single entry by ID in a content type. Implements the ContentEntry $prototype resolution.
|
|
437
|
-
*
|
|
438
|
-
* @param {ContentLoaderEntry[]} entries - Full content type entries
|
|
439
|
-
* @param {string} id - Entry ID to find
|
|
440
|
-
* @returns {ContentLoaderEntry | null} The matching entry or null
|
|
441
|
-
*/
|
|
442
|
-
export function findEntry(entries, id) {
|
|
443
|
-
return entries.find((e) => e.id === id) ?? null;
|
|
444
|
-
}
|
|
386
|
+
export { queryContentType, findEntry } from "@jxsuite/parser/content";
|
|
445
387
|
|
|
446
388
|
// ─── Content Type Reference Resolution ──────────────────────────────────────
|
|
447
389
|
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
* title $page.params — dynamic route parameters (if any)
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import { queryContentType, findEntry } from "./content-loader.js";
|
|
16
15
|
import { resolve, dirname, relative } from "node:path";
|
|
17
16
|
|
|
18
17
|
/**
|
|
@@ -48,11 +47,6 @@ export function injectContext(
|
|
|
48
47
|
params: route._pathParams ?? {},
|
|
49
48
|
};
|
|
50
49
|
|
|
51
|
-
// Resolve ContentCollection and ContentEntry $prototype entries
|
|
52
|
-
if (contentTypes.size > 0) {
|
|
53
|
-
resolveContentPrototypes(doc.state, contentTypes, route._pathParams ?? {});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
50
|
// Merge project-level state into page state (page wins on conflicts)
|
|
57
51
|
if (projectConfig.state) {
|
|
58
52
|
for (const [key, value] of Object.entries(projectConfig.state)) {
|
|
@@ -110,49 +104,3 @@ export function injectContext(
|
|
|
110
104
|
|
|
111
105
|
return doc;
|
|
112
106
|
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Resolve ContentCollection and ContentEntry $prototype state entries.
|
|
116
|
-
*
|
|
117
|
-
* Replaces state entries like: { "$prototype": "ContentCollection", "contentType": "blog", ... }
|
|
118
|
-
* with the actual resolved content type data.
|
|
119
|
-
*
|
|
120
|
-
* @param {Record<string, unknown>} state - Page state (mutated)
|
|
121
|
-
* @param {Map<string, ContentLoaderEntry[]>} contentTypes - Loaded content types
|
|
122
|
-
* @param {Record<string, string>} params - Route parameters for $ref resolution
|
|
123
|
-
*/
|
|
124
|
-
function resolveContentPrototypes(state, contentTypes, params) {
|
|
125
|
-
for (const [key, value] of Object.entries(state)) {
|
|
126
|
-
if (!value || typeof value !== "object" || !(/** @type {JxMutableNode} */ (value).$prototype))
|
|
127
|
-
continue;
|
|
128
|
-
const v = /** @type {JxPrototypeDef} */ (value);
|
|
129
|
-
|
|
130
|
-
if (v.$prototype === "ContentCollection") {
|
|
131
|
-
const entries = contentTypes.get(/** @type {string} */ (v.contentType));
|
|
132
|
-
if (!entries) {
|
|
133
|
-
console.warn(`ContentCollection: content type "${v.contentType}" not found`);
|
|
134
|
-
state[key] = [];
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
state[key] = queryContentType(entries, {
|
|
138
|
-
filter: v.filter,
|
|
139
|
-
sort: v.sort,
|
|
140
|
-
limit: v.limit,
|
|
141
|
-
});
|
|
142
|
-
} else if (v.$prototype === "ContentEntry") {
|
|
143
|
-
const entries = contentTypes.get(/** @type {string} */ (v.contentType));
|
|
144
|
-
if (!entries) {
|
|
145
|
-
console.warn(`ContentEntry: content type "${v.contentType}" not found`);
|
|
146
|
-
state[key] = null;
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
// Resolve the ID — may reference $params
|
|
150
|
-
let id = v.id;
|
|
151
|
-
if (id && typeof id === "object" && id.$ref?.startsWith("#/$params/")) {
|
|
152
|
-
const paramName = id.$ref.replace("#/$params/", "");
|
|
153
|
-
id = params[paramName];
|
|
154
|
-
}
|
|
155
|
-
state[key] = findEntry(entries, /** @type {string} */ (id));
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
package/src/site/head-merger.js
CHANGED
|
@@ -94,7 +94,9 @@ function headEntryKey(entry) {
|
|
|
94
94
|
|
|
95
95
|
// <style> — unique per content hash
|
|
96
96
|
if (tag === "style") {
|
|
97
|
-
const content = Array.isArray(entry.children)
|
|
97
|
+
const content = Array.isArray(entry.children)
|
|
98
|
+
? entry.children.join("")
|
|
99
|
+
: (entry.textContent ?? "");
|
|
98
100
|
return `style:${simpleHash(content)}`;
|
|
99
101
|
}
|
|
100
102
|
|
|
@@ -149,7 +151,9 @@ function renderHeadEntry(entry) {
|
|
|
149
151
|
if (VOID.has(tag)) return open;
|
|
150
152
|
|
|
151
153
|
// Elements with content
|
|
152
|
-
const content = Array.isArray(entry.children)
|
|
154
|
+
const content = Array.isArray(entry.children)
|
|
155
|
+
? entry.children.join("")
|
|
156
|
+
: (entry.textContent ?? "");
|
|
153
157
|
return `${open}${content}</${tag}>`;
|
|
154
158
|
}
|
|
155
159
|
|
|
@@ -23,24 +23,7 @@ import { createRequire } from "node:module";
|
|
|
23
23
|
* Prototype names handled elsewhere (builtins + legacy content system). These are skipped by the
|
|
24
24
|
* generic resolver.
|
|
25
25
|
*/
|
|
26
|
-
const SKIP_PROTOTYPES = new Set([
|
|
27
|
-
"Function",
|
|
28
|
-
"LocalStorage",
|
|
29
|
-
"SessionStorage",
|
|
30
|
-
"Array",
|
|
31
|
-
"ContentCollection",
|
|
32
|
-
"ContentEntry",
|
|
33
|
-
]);
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Built-in $prototype → .class.json mappings. These resolve automatically without requiring an
|
|
37
|
-
* explicit `imports` entry in project.json. Project-level imports take precedence on collision.
|
|
38
|
-
*/
|
|
39
|
-
/** @type {Record<string, string>} */
|
|
40
|
-
const BUILTIN_CLASS_MAPPINGS = {
|
|
41
|
-
MarkdownFile: "@jxsuite/parser/MarkdownFile.class.json",
|
|
42
|
-
MarkdownCollection: "@jxsuite/parser/MarkdownCollection.class.json",
|
|
43
|
-
};
|
|
26
|
+
const SKIP_PROTOTYPES = new Set(["Function", "LocalStorage", "SessionStorage", "Array"]);
|
|
44
27
|
|
|
45
28
|
/**
|
|
46
29
|
* Keys reserved by the Jx prototype system — stripped before passing config to the external class
|
|
@@ -66,10 +49,12 @@ const RESERVED_KEYS = new Set([
|
|
|
66
49
|
* result
|
|
67
50
|
*
|
|
68
51
|
* @param {JxMutableNode | JxDocument} doc - The page document (mutated in place)
|
|
69
|
-
* @param {{ sourcePath?: string }} route - Route info
|
|
52
|
+
* @param {{ sourcePath?: string; _pathParams?: Record<string, string> }} route - Route info
|
|
70
53
|
* @param {string} projectRoot - Absolute path to the project root
|
|
54
|
+
* @param {{ config?: Record<string, unknown>; contentTypes?: Map<string, unknown[]> }} [projectContext] -
|
|
55
|
+
* Project-level context injected into all classes
|
|
71
56
|
*/
|
|
72
|
-
export async function resolvePrototypes(doc, route, projectRoot) {
|
|
57
|
+
export async function resolvePrototypes(doc, route, projectRoot, projectContext = {}) {
|
|
73
58
|
const imports = doc.imports ?? {};
|
|
74
59
|
const state = doc.state;
|
|
75
60
|
if (!state) return;
|
|
@@ -83,13 +68,13 @@ export async function resolvePrototypes(doc, route, projectRoot) {
|
|
|
83
68
|
|
|
84
69
|
// Look up in imports if no $src already set
|
|
85
70
|
if (!def.$src) {
|
|
86
|
-
const mapped = imports[def.$prototype]
|
|
71
|
+
const mapped = imports[def.$prototype];
|
|
87
72
|
if (!mapped) continue;
|
|
88
73
|
def.$src = mapped;
|
|
89
74
|
}
|
|
90
75
|
|
|
91
76
|
try {
|
|
92
|
-
const resolved = await resolveClassPrototype(def, route, projectRoot);
|
|
77
|
+
const resolved = await resolveClassPrototype(def, route, projectRoot, state, projectContext);
|
|
93
78
|
// Preserve timing metadata on the resolved value so compilePage() can strip it
|
|
94
79
|
if (def.timing && resolved && typeof resolved === "object" && !Array.isArray(resolved)) {
|
|
95
80
|
resolved.timing = def.timing;
|
|
@@ -108,11 +93,13 @@ export async function resolvePrototypes(doc, route, projectRoot) {
|
|
|
108
93
|
* Resolve a single $prototype entry via its .class.json.
|
|
109
94
|
*
|
|
110
95
|
* @param {Record<string, any>} def - The state entry definition
|
|
111
|
-
* @param {{ sourcePath?: string }} route
|
|
96
|
+
* @param {{ sourcePath?: string; _pathParams?: Record<string, string> }} route
|
|
112
97
|
* @param {string} projectRoot
|
|
98
|
+
* @param {Record<string, unknown>} state - The full page state object
|
|
99
|
+
* @param {{ config?: Record<string, unknown>; contentTypes?: Map<string, unknown[]> }} projectContext
|
|
113
100
|
* @returns {Promise<any>} The resolved value
|
|
114
101
|
*/
|
|
115
|
-
async function resolveClassPrototype(def, route, projectRoot) {
|
|
102
|
+
async function resolveClassPrototype(def, route, projectRoot, state, projectContext) {
|
|
116
103
|
const src = def.$src;
|
|
117
104
|
|
|
118
105
|
// 1. Resolve .class.json path — handles both npm specifiers and relative paths
|
|
@@ -162,6 +149,14 @@ async function resolveClassPrototype(def, route, projectRoot) {
|
|
|
162
149
|
config.basePath = dirname(route.sourcePath);
|
|
163
150
|
}
|
|
164
151
|
|
|
152
|
+
// Inject universal context — available to all classes
|
|
153
|
+
config._project = {
|
|
154
|
+
config: projectContext.config ?? null,
|
|
155
|
+
contentTypes: projectContext.contentTypes ?? null,
|
|
156
|
+
root: projectRoot,
|
|
157
|
+
};
|
|
158
|
+
config._document = { route, state };
|
|
159
|
+
|
|
165
160
|
// 7. Instantiate and resolve
|
|
166
161
|
const instance = new ExportedClass(config);
|
|
167
162
|
if (typeof instance.resolve === "function") {
|
package/src/site/site-build.js
CHANGED
|
@@ -134,7 +134,7 @@ export async function buildSite(projectRoot, options = {}) {
|
|
|
134
134
|
for (const file of componentFiles) {
|
|
135
135
|
try {
|
|
136
136
|
const componentPath = resolve(componentsDir, file);
|
|
137
|
-
const result = await compileElement(componentPath);
|
|
137
|
+
const result = await compileElement(componentPath, { $media: projectConfig.$media });
|
|
138
138
|
for (const f of result.files) {
|
|
139
139
|
const outName = f.path.includes("/")
|
|
140
140
|
? /** @type {string} */ (f.path.split("/").pop())
|
|
@@ -405,11 +405,14 @@ async function compilePage(
|
|
|
405
405
|
delete layoutDoc._pageHead;
|
|
406
406
|
delete layoutDoc._pageTitle;
|
|
407
407
|
|
|
408
|
-
// Inject $site and $page context
|
|
408
|
+
// Inject $site and $page context
|
|
409
409
|
injectContext(layoutDoc, projectConfig, route, contentTypes, projectRoot);
|
|
410
410
|
|
|
411
411
|
// Resolve generic $prototype entries via .class.json imports
|
|
412
|
-
await resolvePrototypes(layoutDoc, route, projectRoot
|
|
412
|
+
await resolvePrototypes(layoutDoc, route, projectRoot, {
|
|
413
|
+
config: projectConfig,
|
|
414
|
+
contentTypes,
|
|
415
|
+
});
|
|
413
416
|
|
|
414
417
|
// Build scope from resolved state so template strings in title/$head can be evaluated
|
|
415
418
|
const scope = buildInitialScope(layoutDoc.state ?? {});
|
|
@@ -16,7 +16,7 @@ import { escapeHtml, tagNameToClassName, isSchemaOnly, collectStyles } from "../
|
|
|
16
16
|
* @returns {Promise<{ files: { path: string; content: string; tagName: string }[] }>}
|
|
17
17
|
*/
|
|
18
18
|
export async function compileElement(sourcePath, opts = {}) {
|
|
19
|
-
const { resolveElementPath } = /** @type {JxMutableNode} */ (opts);
|
|
19
|
+
const { resolveElementPath, $media: optsMedia } = /** @type {JxMutableNode} */ (opts);
|
|
20
20
|
/** @type {{ path: string; content: string; tagName: string }[]} */
|
|
21
21
|
const files = [];
|
|
22
22
|
/** @type {Set<string>} */
|
|
@@ -82,6 +82,9 @@ export async function compileElement(sourcePath, opts = {}) {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
const className = tagNameToClassName(tagName);
|
|
85
|
+
if (optsMedia) {
|
|
86
|
+
doc.$media = { ...optsMedia, ...doc.$media };
|
|
87
|
+
}
|
|
85
88
|
const jsContent = emitElementModule(doc, className, elementImports);
|
|
86
89
|
const outputPath = filePath ? filePath.replace(/\.json$/, ".js") : `${tagName}.js`;
|
|
87
90
|
files.push({ path: outputPath, content: jsContent, tagName });
|