@jxsuite/compiler 0.0.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 +165 -0
- package/package.json +38 -0
- package/src/cli.js +59 -0
- package/src/compiler.js +148 -0
- package/src/shared.js +690 -0
- package/src/site/content-loader.js +452 -0
- package/src/site/context-injection.js +152 -0
- package/src/site/head-merger.js +161 -0
- package/src/site/layout-resolver.js +182 -0
- package/src/site/pages-discovery.js +272 -0
- package/src/site/prototype-resolver.js +161 -0
- package/src/site/site-build.js +600 -0
- package/src/site/site-loader.js +85 -0
- package/src/targets/compile-class.js +194 -0
- package/src/targets/compile-client.js +806 -0
- package/src/targets/compile-element.js +619 -0
- package/src/targets/compile-server.js +57 -0
- package/src/targets/compile-static.js +155 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compile-static.js — Static HTML compilation
|
|
3
|
+
*
|
|
4
|
+
* Compiles fully static Jx documents to plain HTML/CSS with zero JS. Dynamic child subtrees become
|
|
5
|
+
* hydration islands (custom elements).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
isNodeDynamic,
|
|
10
|
+
createCompileContext,
|
|
11
|
+
resolveStaticValue,
|
|
12
|
+
buildAttrs,
|
|
13
|
+
compileStyles,
|
|
14
|
+
escapeHtml,
|
|
15
|
+
} from "../shared.js";
|
|
16
|
+
import { emitElementModule } from "./compile-element.js";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Compile a static document to HTML, with dynamic subtrees as islands.
|
|
20
|
+
*
|
|
21
|
+
* @param {any} raw - Raw JSON document (with $ref pointers preserved)
|
|
22
|
+
* @param {any} opts
|
|
23
|
+
* @returns {{ html: string; files: { path: string; content: string; tagName: string }[] }}
|
|
24
|
+
*/
|
|
25
|
+
export function compileStaticPage(raw, opts) {
|
|
26
|
+
const { title, reactivitySrc, litHtmlSrc } = opts;
|
|
27
|
+
|
|
28
|
+
const rootContext = createCompileContext(raw, null, raw.state ?? {}, raw.$media ?? {});
|
|
29
|
+
const styleBlock = compileStyles(raw, raw.$media ?? {});
|
|
30
|
+
/** @type {{ def: any; tagName: string; className: string }[]} */
|
|
31
|
+
const islands = [];
|
|
32
|
+
const bodyContent = compileNode(raw, false, raw, rootContext, islands);
|
|
33
|
+
|
|
34
|
+
/** @type {{ path: string; content: string; tagName: string }[]} */
|
|
35
|
+
const files = [];
|
|
36
|
+
let importMap = "";
|
|
37
|
+
let moduleScripts = "";
|
|
38
|
+
if (islands.length > 0) {
|
|
39
|
+
for (const island of islands) {
|
|
40
|
+
const moduleContent = emitElementModule(island.def, island.className, []);
|
|
41
|
+
files.push({
|
|
42
|
+
path: `_islands/${island.tagName}.js`,
|
|
43
|
+
content: moduleContent,
|
|
44
|
+
tagName: island.tagName,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
importMap = `<script type="importmap">
|
|
48
|
+
{
|
|
49
|
+
"imports": {
|
|
50
|
+
"@vue/reactivity": "${reactivitySrc}",
|
|
51
|
+
"lit-html": "${litHtmlSrc}"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
</script>`;
|
|
55
|
+
moduleScripts = files
|
|
56
|
+
.map(
|
|
57
|
+
(/** @type {{ path: string }} */ f) => `<script type="module" src="./${f.path}"></script>`,
|
|
58
|
+
)
|
|
59
|
+
.join("\n ");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const html = `<!DOCTYPE html>
|
|
63
|
+
<html lang="en">
|
|
64
|
+
<head>
|
|
65
|
+
<meta charset="utf-8">
|
|
66
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
67
|
+
<title>${escapeHtml(title)}</title>
|
|
68
|
+
${importMap}
|
|
69
|
+
${styleBlock}
|
|
70
|
+
</head>
|
|
71
|
+
<body>
|
|
72
|
+
${bodyContent}
|
|
73
|
+
${moduleScripts}
|
|
74
|
+
</body>
|
|
75
|
+
</html>`;
|
|
76
|
+
|
|
77
|
+
return { html, files };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ─── Node compilation ─────────────────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Compile a single Jx node to an HTML string. Dynamic nodes become hydration islands; static nodes
|
|
84
|
+
* become plain HTML.
|
|
85
|
+
*
|
|
86
|
+
* @param {any} def
|
|
87
|
+
* @param {boolean} dynamic
|
|
88
|
+
* @param {any} raw
|
|
89
|
+
* @param {any} context
|
|
90
|
+
* @param {{ def: any; tagName: string; className: string }[]} islands
|
|
91
|
+
* @returns {string}
|
|
92
|
+
*/
|
|
93
|
+
function compileNode(def, dynamic, raw, context, islands) {
|
|
94
|
+
// String children are text nodes
|
|
95
|
+
if (typeof def === "string") {
|
|
96
|
+
return escapeHtml(def);
|
|
97
|
+
}
|
|
98
|
+
if (typeof def === "number" || typeof def === "boolean") {
|
|
99
|
+
return escapeHtml(String(def));
|
|
100
|
+
}
|
|
101
|
+
if (!def || typeof def !== "object") return "";
|
|
102
|
+
|
|
103
|
+
const nextContext = createCompileContext(
|
|
104
|
+
raw,
|
|
105
|
+
context.scope,
|
|
106
|
+
raw?.state ?? context.scopeDefs,
|
|
107
|
+
raw?.$media ?? context.media,
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
if (dynamic) {
|
|
111
|
+
const n = islands.length;
|
|
112
|
+
const tagName = `jx-island-${n}`;
|
|
113
|
+
const className = `JxIsland${n}`;
|
|
114
|
+
const elementDef = { ...(raw ?? def), tagName };
|
|
115
|
+
islands.push({ def: elementDef, tagName, className });
|
|
116
|
+
return `<${tagName}></${tagName}>`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const tag = def.tagName ?? "div";
|
|
120
|
+
const attrs = buildAttrs(def, nextContext.scope);
|
|
121
|
+
const inner = buildInnerWithIslands(def, raw, nextContext, islands);
|
|
122
|
+
|
|
123
|
+
return `<${tag}${attrs}>${inner}</${tag}>`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Build the inner HTML (textContent or children) for a node. For children, emit islands only for
|
|
128
|
+
* those that are actually dynamic.
|
|
129
|
+
*
|
|
130
|
+
* @param {any} def
|
|
131
|
+
* @param {any} raw
|
|
132
|
+
* @param {any} context
|
|
133
|
+
* @param {{ def: any; tagName: string; className: string }[]} islands
|
|
134
|
+
* @returns {string}
|
|
135
|
+
*/
|
|
136
|
+
function buildInnerWithIslands(def, raw, context, islands) {
|
|
137
|
+
const source = raw ?? def;
|
|
138
|
+
|
|
139
|
+
if (source.textContent !== undefined) {
|
|
140
|
+
const value = resolveStaticValue(source.textContent, context.scope);
|
|
141
|
+
return value == null ? "" : escapeHtml(String(value));
|
|
142
|
+
}
|
|
143
|
+
if (source.innerHTML) return resolveStaticValue(source.innerHTML, context.scope) ?? "";
|
|
144
|
+
if (Array.isArray(source.children)) {
|
|
145
|
+
const rawChildren = raw?.children;
|
|
146
|
+
return source.children
|
|
147
|
+
.map((/** @type {any} */ c, /** @type {number} */ i) => {
|
|
148
|
+
const childDynamic = isNodeDynamic(c);
|
|
149
|
+
const childRaw = rawChildren?.[i] ?? c;
|
|
150
|
+
return compileNode(c, childDynamic, childRaw, context, islands);
|
|
151
|
+
})
|
|
152
|
+
.join("\n ");
|
|
153
|
+
}
|
|
154
|
+
return "";
|
|
155
|
+
}
|