@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,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prototype-resolver.js — Generic $prototype resolution at build time
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the runtime's import-map → .class.json → $implementation → resolve() pipeline, but runs
|
|
5
|
+
* at compile time using filesystem APIs.
|
|
6
|
+
*
|
|
7
|
+
* Any state entry with a $prototype that maps to a .class.json via doc.imports gets resolved: the
|
|
8
|
+
* class is instantiated, .resolve() is called, and the state entry is replaced with the resolved
|
|
9
|
+
* value.
|
|
10
|
+
*
|
|
11
|
+
* This is the extension point for content sources (Markdown, CSV, etc.) — each provides a
|
|
12
|
+
* .class.json + JS implementation, and the compiler resolves them generically.
|
|
13
|
+
*
|
|
14
|
+
* @module prototype-resolver
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { readFileSync } from "node:fs";
|
|
18
|
+
import { dirname, resolve } from "node:path";
|
|
19
|
+
import { pathToFileURL } from "node:url";
|
|
20
|
+
import { createRequire } from "node:module";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Prototype names handled elsewhere (builtins + legacy content system). These are skipped by the
|
|
24
|
+
* generic resolver.
|
|
25
|
+
*/
|
|
26
|
+
const SKIP_PROTOTYPES = new Set([
|
|
27
|
+
"Function",
|
|
28
|
+
"LocalStorage",
|
|
29
|
+
"SessionStorage",
|
|
30
|
+
"Array",
|
|
31
|
+
"ContentCollection",
|
|
32
|
+
"ContentEntry",
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Keys reserved by the Jx prototype system — stripped before passing config to the external class
|
|
37
|
+
* constructor. Mirrors runtime's EXTERNAL_RESERVED.
|
|
38
|
+
*/
|
|
39
|
+
const RESERVED_KEYS = new Set([
|
|
40
|
+
"$prototype",
|
|
41
|
+
"$src",
|
|
42
|
+
"$export",
|
|
43
|
+
"timing",
|
|
44
|
+
"default",
|
|
45
|
+
"description",
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Resolve all generic $prototype entries in a document's state at build time.
|
|
50
|
+
*
|
|
51
|
+
* For each state entry with a $prototype that: 1. Is not a builtin (Function, Array, etc.) 2. Maps
|
|
52
|
+
* to a .class.json path via doc.imports
|
|
53
|
+
*
|
|
54
|
+
* The resolver: - Reads the .class.json from disk - Follows $implementation to import the JS module
|
|
55
|
+
* - Instantiates the class with the config - Calls .resolve() and replaces the state entry with the
|
|
56
|
+
* result
|
|
57
|
+
*
|
|
58
|
+
* @param {any} doc - The page document (mutated in place)
|
|
59
|
+
* @param {{ sourcePath?: string }} route - Route info (sourcePath = absolute path to page .json)
|
|
60
|
+
* @param {string} projectRoot - Absolute path to the project root
|
|
61
|
+
*/
|
|
62
|
+
export async function resolvePrototypes(doc, route, projectRoot) {
|
|
63
|
+
const imports = doc.imports ?? {};
|
|
64
|
+
const state = doc.state;
|
|
65
|
+
if (!state) return;
|
|
66
|
+
|
|
67
|
+
for (const [key, def] of Object.entries(state)) {
|
|
68
|
+
if (!def || typeof def !== "object" || !def.$prototype) continue;
|
|
69
|
+
if (SKIP_PROTOTYPES.has(def.$prototype)) continue;
|
|
70
|
+
// Only resolve timing: "compiler" (or unset timing with a .class.json mapping).
|
|
71
|
+
// Leave timing: "server" and timing: "client" for their respective pipelines.
|
|
72
|
+
if (def.timing && def.timing !== "compiler") continue;
|
|
73
|
+
|
|
74
|
+
// Look up in imports if no $src already set
|
|
75
|
+
if (!def.$src) {
|
|
76
|
+
const mapped = imports[def.$prototype];
|
|
77
|
+
if (!mapped) continue; // not in imports — leave for runtime
|
|
78
|
+
def.$src = mapped;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const resolved = await resolveClassPrototype(def, route, projectRoot);
|
|
83
|
+
// Preserve timing metadata on the resolved value so compilePage() can strip it
|
|
84
|
+
if (def.timing && resolved && typeof resolved === "object" && !Array.isArray(resolved)) {
|
|
85
|
+
resolved.timing = def.timing;
|
|
86
|
+
}
|
|
87
|
+
state[key] = resolved;
|
|
88
|
+
} catch (err) {
|
|
89
|
+
console.warn(
|
|
90
|
+
`prototype-resolver: failed to resolve "${key}" ($prototype: "${def.$prototype}"):`,
|
|
91
|
+
/** @type {Error} */ (err).message,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Resolve a single $prototype entry via its .class.json.
|
|
99
|
+
*
|
|
100
|
+
* @param {Record<string, any>} def - The state entry definition
|
|
101
|
+
* @param {{ sourcePath?: string }} route
|
|
102
|
+
* @param {string} projectRoot
|
|
103
|
+
* @returns {Promise<any>} The resolved value
|
|
104
|
+
*/
|
|
105
|
+
async function resolveClassPrototype(def, route, projectRoot) {
|
|
106
|
+
const src = def.$src;
|
|
107
|
+
|
|
108
|
+
// 1. Resolve .class.json path — handles both npm specifiers and relative paths
|
|
109
|
+
let classJsonPath;
|
|
110
|
+
if (src.startsWith("./") || src.startsWith("../")) {
|
|
111
|
+
// Relative path — resolve from page directory
|
|
112
|
+
classJsonPath = route.sourcePath
|
|
113
|
+
? resolve(dirname(route.sourcePath), src)
|
|
114
|
+
: resolve(projectRoot, src);
|
|
115
|
+
} else {
|
|
116
|
+
// npm/bare specifier — use createRequire from the project root to walk node_modules
|
|
117
|
+
const require = createRequire(resolve(projectRoot, "package.json"));
|
|
118
|
+
classJsonPath = require.resolve(src);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// 2. Read and parse .class.json
|
|
122
|
+
const classJsonText = readFileSync(classJsonPath, "utf-8");
|
|
123
|
+
const classDef = JSON.parse(classJsonText);
|
|
124
|
+
|
|
125
|
+
if (!classDef.$implementation) {
|
|
126
|
+
throw new Error(`${src} has no $implementation field`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 3. Resolve $implementation relative to .class.json location
|
|
130
|
+
const classJsonURL = pathToFileURL(classJsonPath).href;
|
|
131
|
+
const implURL = new URL(classDef.$implementation, classJsonURL).href;
|
|
132
|
+
|
|
133
|
+
// 4. Import the module
|
|
134
|
+
const mod = await import(implURL);
|
|
135
|
+
|
|
136
|
+
// 5. Find the exported class
|
|
137
|
+
const exportName = def.$export ?? classDef.title ?? def.$prototype;
|
|
138
|
+
const ExportedClass = mod[exportName] ?? mod.default?.[exportName];
|
|
139
|
+
if (!ExportedClass) {
|
|
140
|
+
throw new Error(`Module ${classDef.$implementation} does not export "${exportName}"`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 6. Build config — filter out reserved keys
|
|
144
|
+
/** @type {Record<string, any>} */
|
|
145
|
+
const config = {};
|
|
146
|
+
for (const [k, v] of Object.entries(def)) {
|
|
147
|
+
if (!RESERVED_KEYS.has(k)) config[k] = v;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Auto-set basePath from the page's directory if the config has `src` but no `basePath`
|
|
151
|
+
if (config.src && !config.basePath && route.sourcePath) {
|
|
152
|
+
config.basePath = dirname(route.sourcePath);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 7. Instantiate and resolve
|
|
156
|
+
const instance = new ExportedClass(config);
|
|
157
|
+
if (typeof instance.resolve === "function") {
|
|
158
|
+
return await instance.resolve();
|
|
159
|
+
}
|
|
160
|
+
return instance;
|
|
161
|
+
}
|