@zenithbuild/cli 0.6.17 → 0.7.0
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/build/compiler-runtime.d.ts +59 -0
- package/dist/build/compiler-runtime.js +277 -0
- package/dist/build/expression-rewrites.d.ts +88 -0
- package/dist/build/expression-rewrites.js +372 -0
- package/dist/build/hoisted-code-transforms.d.ts +44 -0
- package/dist/build/hoisted-code-transforms.js +316 -0
- package/dist/build/merge-component-ir.d.ts +16 -0
- package/dist/build/merge-component-ir.js +257 -0
- package/dist/build/page-component-loop.d.ts +92 -0
- package/dist/build/page-component-loop.js +257 -0
- package/dist/build/page-ir-normalization.d.ts +23 -0
- package/dist/build/page-ir-normalization.js +370 -0
- package/dist/build/page-loop-metrics.d.ts +100 -0
- package/dist/build/page-loop-metrics.js +131 -0
- package/dist/build/page-loop-state.d.ts +261 -0
- package/dist/build/page-loop-state.js +92 -0
- package/dist/build/page-loop.d.ts +33 -0
- package/dist/build/page-loop.js +217 -0
- package/dist/build/scoped-identifier-rewrite.d.ts +112 -0
- package/dist/build/scoped-identifier-rewrite.js +245 -0
- package/dist/build/server-script.d.ts +41 -0
- package/dist/build/server-script.js +210 -0
- package/dist/build/type-declarations.d.ts +16 -0
- package/dist/build/type-declarations.js +158 -0
- package/dist/build/typescript-expression-utils.d.ts +23 -0
- package/dist/build/typescript-expression-utils.js +272 -0
- package/dist/build.d.ts +10 -18
- package/dist/build.js +74 -2261
- package/dist/component-instance-ir.d.ts +2 -2
- package/dist/component-instance-ir.js +146 -39
- package/dist/component-occurrences.js +63 -15
- package/dist/config.d.ts +66 -0
- package/dist/config.js +86 -0
- package/dist/debug-script.d.ts +1 -0
- package/dist/debug-script.js +8 -0
- package/dist/dev-build-session.d.ts +23 -0
- package/dist/dev-build-session.js +421 -0
- package/dist/dev-server.js +256 -54
- package/dist/framework-components/Image.zen +316 -0
- package/dist/images/materialize.d.ts +17 -0
- package/dist/images/materialize.js +200 -0
- package/dist/images/payload.d.ts +18 -0
- package/dist/images/payload.js +65 -0
- package/dist/images/runtime.d.ts +4 -0
- package/dist/images/runtime.js +254 -0
- package/dist/images/service.d.ts +4 -0
- package/dist/images/service.js +302 -0
- package/dist/images/shared.d.ts +58 -0
- package/dist/images/shared.js +306 -0
- package/dist/index.js +2 -17
- package/dist/manifest.js +45 -0
- package/dist/preview.d.ts +4 -1
- package/dist/preview.js +59 -6
- package/dist/resolve-components.js +20 -3
- package/dist/server-contract.js +3 -2
- package/dist/server-script-composition.d.ts +39 -0
- package/dist/server-script-composition.js +133 -0
- package/dist/startup-profile.d.ts +10 -0
- package/dist/startup-profile.js +62 -0
- package/dist/toolchain-paths.d.ts +1 -0
- package/dist/toolchain-paths.js +31 -0
- package/dist/version-check.d.ts +2 -1
- package/dist/version-check.js +12 -5
- package/package.json +5 -4
package/dist/build.js
CHANGED
|
@@ -1,2109 +1,74 @@
|
|
|
1
|
-
|
|
2
|
-
// build.js — Zenith CLI V0
|
|
3
|
-
// ---------------------------------------------------------------------------
|
|
4
|
-
// Orchestration-only build engine.
|
|
5
|
-
//
|
|
6
|
-
// Pipeline:
|
|
7
|
-
// registry → expand components → compiler (--stdin) → merge component IRs
|
|
8
|
-
// → sealed envelope → bundler process
|
|
9
|
-
//
|
|
10
|
-
// The CLI does not inspect IR fields and does not write output files.
|
|
11
|
-
// The bundler owns all asset and HTML emission.
|
|
12
|
-
// ---------------------------------------------------------------------------
|
|
13
|
-
import { spawn, spawnSync } from 'node:child_process';
|
|
14
|
-
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
15
|
-
import { mkdir, readdir, rm, stat } from 'node:fs/promises';
|
|
16
|
-
import { createRequire } from 'node:module';
|
|
17
|
-
import { basename, dirname, extname, join, relative, resolve } from 'node:path';
|
|
1
|
+
import { resolve } from 'node:path';
|
|
18
2
|
import { generateManifest } from './manifest.js';
|
|
19
|
-
import { buildComponentRegistry
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
3
|
+
import { buildComponentRegistry } from './resolve-components.js';
|
|
4
|
+
import { collectAssets, createCompilerWarningEmitter, runBundler } from './build/compiler-runtime.js';
|
|
5
|
+
import { buildPageEnvelopes } from './build/page-loop.js';
|
|
6
|
+
import { deriveProjectRootFromPagesDir, ensureZenithTypeDeclarations } from './build/type-declarations.js';
|
|
7
|
+
import { materializeImageMarkupInHtmlFiles } from './images/materialize.js';
|
|
8
|
+
import { buildImageArtifacts } from './images/service.js';
|
|
9
|
+
import { createImageRuntimePayload, injectImageRuntimePayloadIntoHtmlFiles } from './images/payload.js';
|
|
10
|
+
import { createStartupProfiler } from './startup-profile.js';
|
|
23
11
|
import { resolveBundlerBin } from './toolchain-paths.js';
|
|
24
|
-
import { createBundlerToolchain, createCompilerToolchain, ensureToolchainCompatibility, getActiveToolchainCandidate
|
|
12
|
+
import { createBundlerToolchain, createCompilerToolchain, ensureToolchainCompatibility, getActiveToolchainCandidate } from './toolchain-runner.js';
|
|
25
13
|
import { maybeWarnAboutZenithVersionMismatch } from './version-check.js';
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
* @returns {import('typescript') | null}
|
|
30
|
-
*/
|
|
31
|
-
function loadTypeScriptApi() {
|
|
32
|
-
if (cachedTypeScript === undefined) {
|
|
33
|
-
try {
|
|
34
|
-
cachedTypeScript = require('typescript');
|
|
35
|
-
}
|
|
36
|
-
catch {
|
|
37
|
-
cachedTypeScript = null;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
return cachedTypeScript;
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Build a per-build warning emitter that deduplicates repeated compiler lines.
|
|
44
|
-
*
|
|
45
|
-
* @param {(line: string) => void} sink
|
|
46
|
-
* @returns {(line: string) => void}
|
|
47
|
-
*/
|
|
48
|
-
export function createCompilerWarningEmitter(sink = (line) => console.warn(line)) {
|
|
49
|
-
const emitted = new Set();
|
|
50
|
-
return (line) => {
|
|
51
|
-
const text = String(line || '').trim();
|
|
52
|
-
if (!text || emitted.has(text)) {
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
emitted.add(text);
|
|
56
|
-
sink(text);
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Forward child-process output line-by-line through the structured logger.
|
|
61
|
-
*
|
|
62
|
-
* @param {import('node:stream').Readable | null | undefined} stream
|
|
63
|
-
* @param {(line: string) => void} onLine
|
|
64
|
-
*/
|
|
65
|
-
function forwardStreamLines(stream, onLine) {
|
|
66
|
-
if (!stream || typeof stream.on !== 'function') {
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
let pending = '';
|
|
70
|
-
stream.setEncoding?.('utf8');
|
|
71
|
-
stream.on('data', (chunk) => {
|
|
72
|
-
pending += String(chunk || '');
|
|
73
|
-
const lines = pending.split(/\r?\n/);
|
|
74
|
-
pending = lines.pop() || '';
|
|
75
|
-
for (const line of lines) {
|
|
76
|
-
if (line.trim().length > 0) {
|
|
77
|
-
onLine(line);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
stream.on('end', () => {
|
|
82
|
-
if (pending.trim().length > 0) {
|
|
83
|
-
onLine(pending);
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Run the compiler process and parse its JSON stdout.
|
|
89
|
-
*
|
|
90
|
-
* If `stdinSource` is provided, pipes it to the compiler via stdin
|
|
91
|
-
* and passes `--stdin` so the compiler reads from stdin instead of the file.
|
|
92
|
-
* The `filePath` argument is always used as the source_path for diagnostics.
|
|
93
|
-
*
|
|
94
|
-
* @param {string} filePath — path for diagnostics (and file reading when no stdinSource)
|
|
95
|
-
* @param {string} [stdinSource] — if provided, piped to compiler via stdin
|
|
96
|
-
* @param {object} compilerRunOptions
|
|
97
|
-
* @param {(warning: string) => void} [compilerRunOptions.onWarning]
|
|
98
|
-
* @param {boolean} [compilerRunOptions.suppressWarnings]
|
|
99
|
-
* @param {string|object} [compilerRunOptions.compilerBin]
|
|
100
|
-
* @param {object} [compilerRunOptions.compilerToolchain]
|
|
101
|
-
* @returns {object}
|
|
102
|
-
*/
|
|
103
|
-
function runCompiler(filePath, stdinSource, compilerOpts = {}, compilerRunOptions = {}) {
|
|
104
|
-
const compilerToolchain = compilerRunOptions.compilerToolchain
|
|
105
|
-
|| (compilerRunOptions.compilerBin && typeof compilerRunOptions.compilerBin === 'object'
|
|
106
|
-
? compilerRunOptions.compilerBin
|
|
107
|
-
: null);
|
|
108
|
-
const compilerBin = !compilerToolchain && typeof compilerRunOptions.compilerBin === 'string'
|
|
109
|
-
? compilerRunOptions.compilerBin
|
|
110
|
-
: null;
|
|
111
|
-
const args = stdinSource !== undefined
|
|
112
|
-
? ['--stdin', filePath]
|
|
113
|
-
: [filePath];
|
|
114
|
-
if (compilerOpts?.experimentalEmbeddedMarkup) {
|
|
115
|
-
args.push('--embedded-markup-expressions');
|
|
116
|
-
}
|
|
117
|
-
if (compilerOpts?.strictDomLints) {
|
|
118
|
-
args.push('--strict-dom-lints');
|
|
119
|
-
}
|
|
120
|
-
const opts = { encoding: 'utf8' };
|
|
121
|
-
if (stdinSource !== undefined) {
|
|
122
|
-
opts.input = stdinSource;
|
|
123
|
-
}
|
|
124
|
-
const result = compilerToolchain
|
|
125
|
-
? runToolchainSync(compilerToolchain, args, opts).result
|
|
126
|
-
: (compilerBin
|
|
127
|
-
? spawnSync(compilerBin, args, opts)
|
|
128
|
-
: runToolchainSync(createCompilerToolchain({
|
|
129
|
-
logger: compilerRunOptions.logger || null
|
|
130
|
-
}), args, opts).result);
|
|
131
|
-
if (result.error) {
|
|
132
|
-
throw new Error(`Compiler spawn failed for ${filePath}: ${result.error.message}`);
|
|
133
|
-
}
|
|
134
|
-
if (result.status !== 0) {
|
|
135
|
-
throw new Error(`Compiler failed for ${filePath} with exit code ${result.status}\n${result.stderr || ''}`);
|
|
136
|
-
}
|
|
137
|
-
if (result.stderr && result.stderr.trim().length > 0 && compilerRunOptions.suppressWarnings !== true) {
|
|
138
|
-
const lines = String(result.stderr)
|
|
139
|
-
.split('\n')
|
|
140
|
-
.map((line) => line.trim())
|
|
141
|
-
.filter((line) => line.length > 0);
|
|
142
|
-
for (const line of lines) {
|
|
143
|
-
if (typeof compilerRunOptions.onWarning === 'function') {
|
|
144
|
-
compilerRunOptions.onWarning(line);
|
|
145
|
-
}
|
|
146
|
-
else {
|
|
147
|
-
console.warn(line);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
try {
|
|
152
|
-
return JSON.parse(result.stdout);
|
|
153
|
-
}
|
|
154
|
-
catch (err) {
|
|
155
|
-
throw new Error(`Compiler emitted invalid JSON: ${err.message}`);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Strip component <style> blocks before script-only component IR compilation.
|
|
160
|
-
* Component style emission is handled by page compilation/bundler paths.
|
|
161
|
-
*
|
|
162
|
-
* @param {string} source
|
|
163
|
-
* @returns {string}
|
|
164
|
-
*/
|
|
165
|
-
function stripStyleBlocks(source) {
|
|
166
|
-
return String(source || '').replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, '');
|
|
167
|
-
}
|
|
168
|
-
/**
|
|
169
|
-
* Build a deterministic raw->rewritten expression map for a component by
|
|
170
|
-
* comparing template-only expressions with script-aware expressions.
|
|
171
|
-
*
|
|
172
|
-
* @param {string} compPath
|
|
173
|
-
* @param {string} componentSource
|
|
174
|
-
* @param {object} compIr
|
|
175
|
-
* @param {object} compilerOpts
|
|
176
|
-
* @param {string|object} compilerBin
|
|
177
|
-
* @returns {{
|
|
178
|
-
* map: Map<string, string>,
|
|
179
|
-
* bindings: Map<string, {
|
|
180
|
-
* compiled_expr: string | null,
|
|
181
|
-
* signal_index: number | null,
|
|
182
|
-
* signal_indices: number[],
|
|
183
|
-
* state_index: number | null,
|
|
184
|
-
* component_instance: string | null,
|
|
185
|
-
* component_binding: string | null
|
|
186
|
-
* }>,
|
|
187
|
-
* signals: Array<{ id?: number, kind?: string, state_index?: number }>,
|
|
188
|
-
* stateBindings: Array<{ key?: string, value?: string }>,
|
|
189
|
-
* ambiguous: Set<string>
|
|
190
|
-
* }}
|
|
191
|
-
*/
|
|
192
|
-
function buildComponentExpressionRewrite(compPath, componentSource, compIr, compilerOpts, compilerBin) {
|
|
193
|
-
const out = {
|
|
194
|
-
map: new Map(),
|
|
195
|
-
bindings: new Map(),
|
|
196
|
-
signals: Array.isArray(compIr?.signals) ? compIr.signals : [],
|
|
197
|
-
stateBindings: Array.isArray(compIr?.hoisted?.state) ? compIr.hoisted.state : [],
|
|
198
|
-
ambiguous: new Set(),
|
|
199
|
-
sequence: []
|
|
200
|
-
};
|
|
201
|
-
const rewrittenExpressions = Array.isArray(compIr?.expressions) ? compIr.expressions : [];
|
|
202
|
-
const rewrittenBindings = Array.isArray(compIr?.expression_bindings) ? compIr.expression_bindings : [];
|
|
203
|
-
if (rewrittenExpressions.length === 0) {
|
|
204
|
-
return out;
|
|
205
|
-
}
|
|
206
|
-
const templateOnly = extractTemplate(componentSource);
|
|
207
|
-
if (!templateOnly.trim()) {
|
|
208
|
-
return out;
|
|
209
|
-
}
|
|
210
|
-
let templateIr;
|
|
211
|
-
try {
|
|
212
|
-
templateIr = runCompiler(compPath, templateOnly, compilerOpts, {
|
|
213
|
-
suppressWarnings: true,
|
|
214
|
-
compilerToolchain: compilerBin
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
catch {
|
|
218
|
-
return out;
|
|
219
|
-
}
|
|
220
|
-
const rawExpressions = Array.isArray(templateIr?.expressions) ? templateIr.expressions : [];
|
|
221
|
-
const count = Math.min(rawExpressions.length, rewrittenExpressions.length);
|
|
222
|
-
for (let i = 0; i < count; i++) {
|
|
223
|
-
const raw = rawExpressions[i];
|
|
224
|
-
const rewritten = rewrittenExpressions[i];
|
|
225
|
-
if (typeof raw !== 'string' || typeof rewritten !== 'string') {
|
|
226
|
-
continue;
|
|
227
|
-
}
|
|
228
|
-
const binding = rewrittenBindings[i];
|
|
229
|
-
const normalizedBinding = binding && typeof binding === 'object'
|
|
230
|
-
? {
|
|
231
|
-
compiled_expr: typeof binding.compiled_expr === 'string' ? binding.compiled_expr : null,
|
|
232
|
-
signal_index: Number.isInteger(binding.signal_index) ? binding.signal_index : null,
|
|
233
|
-
signal_indices: Array.isArray(binding.signal_indices)
|
|
234
|
-
? binding.signal_indices.filter((value) => Number.isInteger(value))
|
|
235
|
-
: [],
|
|
236
|
-
state_index: Number.isInteger(binding.state_index) ? binding.state_index : null,
|
|
237
|
-
component_instance: typeof binding.component_instance === 'string' ? binding.component_instance : null,
|
|
238
|
-
component_binding: typeof binding.component_binding === 'string' ? binding.component_binding : null
|
|
239
|
-
}
|
|
240
|
-
: null;
|
|
241
|
-
out.sequence.push({
|
|
242
|
-
raw,
|
|
243
|
-
rewritten,
|
|
244
|
-
binding: normalizedBinding
|
|
245
|
-
});
|
|
246
|
-
if (!out.ambiguous.has(raw) && normalizedBinding) {
|
|
247
|
-
const existingBinding = out.bindings.get(raw);
|
|
248
|
-
if (existingBinding) {
|
|
249
|
-
if (JSON.stringify(existingBinding) !== JSON.stringify(normalizedBinding)) {
|
|
250
|
-
out.bindings.delete(raw);
|
|
251
|
-
out.map.delete(raw);
|
|
252
|
-
out.ambiguous.add(raw);
|
|
253
|
-
continue;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
else {
|
|
257
|
-
out.bindings.set(raw, normalizedBinding);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
if (raw !== rewritten) {
|
|
261
|
-
const existing = out.map.get(raw);
|
|
262
|
-
if (existing && existing !== rewritten) {
|
|
263
|
-
out.bindings.delete(raw);
|
|
264
|
-
out.map.delete(raw);
|
|
265
|
-
out.ambiguous.add(raw);
|
|
266
|
-
continue;
|
|
267
|
-
}
|
|
268
|
-
if (!out.ambiguous.has(raw)) {
|
|
269
|
-
out.map.set(raw, rewritten);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
return out;
|
|
274
|
-
}
|
|
275
|
-
function remapCompiledExpressionSignals(compiledExpr, componentSignals, componentStateBindings, pageSignalIndexByStateKey) {
|
|
276
|
-
if (typeof compiledExpr !== 'string' || compiledExpr.length === 0) {
|
|
277
|
-
return null;
|
|
278
|
-
}
|
|
279
|
-
return compiledExpr.replace(/signalMap\.get\((\d+)\)/g, (full, rawIndex) => {
|
|
280
|
-
const localIndex = Number.parseInt(rawIndex, 10);
|
|
281
|
-
if (!Number.isInteger(localIndex)) {
|
|
282
|
-
return full;
|
|
283
|
-
}
|
|
284
|
-
const signal = componentSignals[localIndex];
|
|
285
|
-
if (!signal || !Number.isInteger(signal.state_index)) {
|
|
286
|
-
return full;
|
|
287
|
-
}
|
|
288
|
-
const stateKey = componentStateBindings[signal.state_index]?.key;
|
|
289
|
-
if (typeof stateKey !== 'string' || stateKey.length === 0) {
|
|
290
|
-
return full;
|
|
291
|
-
}
|
|
292
|
-
const pageIndex = pageSignalIndexByStateKey.get(stateKey);
|
|
293
|
-
if (!Number.isInteger(pageIndex)) {
|
|
294
|
-
return full;
|
|
295
|
-
}
|
|
296
|
-
return `signalMap.get(${pageIndex})`;
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
function resolveRewrittenBindingMetadata(pageIr, componentRewrite, binding) {
|
|
300
|
-
if (!binding || typeof binding !== 'object') {
|
|
301
|
-
return null;
|
|
302
|
-
}
|
|
303
|
-
const pageStateBindings = Array.isArray(pageIr?.hoisted?.state) ? pageIr.hoisted.state : [];
|
|
304
|
-
const pageSignals = Array.isArray(pageIr?.signals) ? pageIr.signals : [];
|
|
305
|
-
const pageStateIndexByKey = new Map();
|
|
306
|
-
const pageSignalIndexByStateKey = new Map();
|
|
307
|
-
for (let index = 0; index < pageStateBindings.length; index++) {
|
|
308
|
-
const key = pageStateBindings[index]?.key;
|
|
309
|
-
if (typeof key === 'string' && key.length > 0 && !pageStateIndexByKey.has(key)) {
|
|
310
|
-
pageStateIndexByKey.set(key, index);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
for (let index = 0; index < pageSignals.length; index++) {
|
|
314
|
-
const stateIndex = pageSignals[index]?.state_index;
|
|
315
|
-
if (!Number.isInteger(stateIndex)) {
|
|
316
|
-
continue;
|
|
317
|
-
}
|
|
318
|
-
const stateKey = pageStateBindings[stateIndex]?.key;
|
|
319
|
-
if (typeof stateKey === 'string' && stateKey.length > 0 && !pageSignalIndexByStateKey.has(stateKey)) {
|
|
320
|
-
pageSignalIndexByStateKey.set(stateKey, index);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
const componentSignals = Array.isArray(componentRewrite?.signals) ? componentRewrite.signals : [];
|
|
324
|
-
const componentStateBindings = Array.isArray(componentRewrite?.stateBindings) ? componentRewrite.stateBindings : [];
|
|
325
|
-
let signalIndices = Array.isArray(binding.signal_indices)
|
|
326
|
-
? [...new Set(binding.signal_indices
|
|
327
|
-
.map((signalIndex) => {
|
|
328
|
-
if (!Number.isInteger(signalIndex)) {
|
|
329
|
-
return null;
|
|
330
|
-
}
|
|
331
|
-
const signal = componentSignals[signalIndex];
|
|
332
|
-
if (!signal || !Number.isInteger(signal.state_index)) {
|
|
333
|
-
return null;
|
|
334
|
-
}
|
|
335
|
-
const stateKey = componentStateBindings[signal.state_index]?.key;
|
|
336
|
-
if (typeof stateKey !== 'string' || stateKey.length === 0) {
|
|
337
|
-
return null;
|
|
338
|
-
}
|
|
339
|
-
const pageIndex = pageSignalIndexByStateKey.get(stateKey);
|
|
340
|
-
return Number.isInteger(pageIndex) ? pageIndex : null;
|
|
341
|
-
})
|
|
342
|
-
.filter((value) => Number.isInteger(value)))].sort((a, b) => a - b)
|
|
343
|
-
: [];
|
|
344
|
-
let signalIndex = null;
|
|
345
|
-
if (Number.isInteger(binding.signal_index)) {
|
|
346
|
-
const signal = componentSignals[binding.signal_index];
|
|
347
|
-
const stateKey = signal && Number.isInteger(signal.state_index)
|
|
348
|
-
? componentStateBindings[signal.state_index]?.key
|
|
349
|
-
: null;
|
|
350
|
-
const pageIndex = typeof stateKey === 'string' ? pageSignalIndexByStateKey.get(stateKey) : null;
|
|
351
|
-
signalIndex = Number.isInteger(pageIndex) ? pageIndex : null;
|
|
352
|
-
}
|
|
353
|
-
if (signalIndex === null && signalIndices.length === 1) {
|
|
354
|
-
signalIndex = signalIndices[0];
|
|
355
|
-
}
|
|
356
|
-
let stateIndex = null;
|
|
357
|
-
if (Number.isInteger(binding.state_index)) {
|
|
358
|
-
const stateKey = componentStateBindings[binding.state_index]?.key;
|
|
359
|
-
const pageIndex = typeof stateKey === 'string' ? pageStateIndexByKey.get(stateKey) : null;
|
|
360
|
-
stateIndex = Number.isInteger(pageIndex) ? pageIndex : null;
|
|
361
|
-
}
|
|
362
|
-
if (Number.isInteger(stateIndex)) {
|
|
363
|
-
const fallbackSignalIndices = pageSignals
|
|
364
|
-
.map((signal, index) => signal?.state_index === stateIndex ? index : null)
|
|
365
|
-
.filter((value) => Number.isInteger(value));
|
|
366
|
-
const signalIndicesMatchState = signalIndices.every((index) => pageSignals[index]?.state_index === stateIndex);
|
|
367
|
-
if ((!signalIndicesMatchState || signalIndices.length === 0) && fallbackSignalIndices.length > 0) {
|
|
368
|
-
signalIndices = fallbackSignalIndices;
|
|
369
|
-
}
|
|
370
|
-
if ((signalIndex === null || pageSignals[signalIndex]?.state_index !== stateIndex) &&
|
|
371
|
-
fallbackSignalIndices.length === 1) {
|
|
372
|
-
signalIndex = fallbackSignalIndices[0];
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
let compiledExpr = remapCompiledExpressionSignals(binding.compiled_expr, componentSignals, componentStateBindings, pageSignalIndexByStateKey);
|
|
376
|
-
if (typeof compiledExpr === 'string' &&
|
|
377
|
-
signalIndices.length === 1 &&
|
|
378
|
-
Array.isArray(binding.signal_indices) &&
|
|
379
|
-
binding.signal_indices.length <= 1) {
|
|
380
|
-
compiledExpr = compiledExpr.replace(/signalMap\.get\(\d+\)/g, `signalMap.get(${signalIndices[0]})`);
|
|
381
|
-
}
|
|
382
|
-
return {
|
|
383
|
-
compiled_expr: compiledExpr,
|
|
384
|
-
signal_index: signalIndex,
|
|
385
|
-
signal_indices: signalIndices,
|
|
386
|
-
state_index: stateIndex,
|
|
387
|
-
component_instance: typeof binding.component_instance === 'string' ? binding.component_instance : null,
|
|
388
|
-
component_binding: typeof binding.component_binding === 'string' ? binding.component_binding : null
|
|
389
|
-
};
|
|
390
|
-
}
|
|
391
|
-
/**
|
|
392
|
-
* Merge a per-component rewrite table into the page-level rewrite table.
|
|
393
|
-
*
|
|
394
|
-
* @param {Map<string, string>} pageMap
|
|
395
|
-
* @param {Map<string, {
|
|
396
|
-
* compiled_expr: string | null,
|
|
397
|
-
* signal_index: number | null,
|
|
398
|
-
* signal_indices: number[],
|
|
399
|
-
* state_index: number | null,
|
|
400
|
-
* component_instance: string | null,
|
|
401
|
-
* component_binding: string | null
|
|
402
|
-
* }>} pageBindingMap
|
|
403
|
-
* @param {Set<string>} pageAmbiguous
|
|
404
|
-
* @param {{
|
|
405
|
-
* map: Map<string, string>,
|
|
406
|
-
* bindings: Map<string, {
|
|
407
|
-
* compiled_expr: string | null,
|
|
408
|
-
* signal_index: number | null,
|
|
409
|
-
* signal_indices: number[],
|
|
410
|
-
* state_index: number | null,
|
|
411
|
-
* component_instance: string | null,
|
|
412
|
-
* component_binding: string | null
|
|
413
|
-
* }>,
|
|
414
|
-
* signals: Array<{ id?: number, kind?: string, state_index?: number }>,
|
|
415
|
-
* stateBindings: Array<{ key?: string, value?: string }>,
|
|
416
|
-
* ambiguous: Set<string>
|
|
417
|
-
* }} componentRewrite
|
|
418
|
-
* @param {object} pageIr
|
|
419
|
-
*/
|
|
420
|
-
function mergeExpressionRewriteMaps(pageMap, pageBindingMap, pageAmbiguous, componentRewrite, pageIr) {
|
|
421
|
-
for (const raw of componentRewrite.ambiguous) {
|
|
422
|
-
pageAmbiguous.add(raw);
|
|
423
|
-
pageMap.delete(raw);
|
|
424
|
-
pageBindingMap.delete(raw);
|
|
425
|
-
}
|
|
426
|
-
for (const [raw, binding] of componentRewrite.bindings.entries()) {
|
|
427
|
-
if (pageAmbiguous.has(raw)) {
|
|
428
|
-
continue;
|
|
429
|
-
}
|
|
430
|
-
const resolved = resolveRewrittenBindingMetadata(pageIr, componentRewrite, binding);
|
|
431
|
-
const existing = pageBindingMap.get(raw);
|
|
432
|
-
if (existing && JSON.stringify(existing) !== JSON.stringify(resolved)) {
|
|
433
|
-
pageAmbiguous.add(raw);
|
|
434
|
-
pageMap.delete(raw);
|
|
435
|
-
pageBindingMap.delete(raw);
|
|
436
|
-
continue;
|
|
437
|
-
}
|
|
438
|
-
pageBindingMap.set(raw, resolved);
|
|
439
|
-
}
|
|
440
|
-
for (const [raw, rewritten] of componentRewrite.map.entries()) {
|
|
441
|
-
if (pageAmbiguous.has(raw)) {
|
|
442
|
-
continue;
|
|
443
|
-
}
|
|
444
|
-
const existing = pageMap.get(raw);
|
|
445
|
-
if (existing && existing !== rewritten) {
|
|
446
|
-
pageAmbiguous.add(raw);
|
|
447
|
-
pageMap.delete(raw);
|
|
448
|
-
pageBindingMap.delete(raw);
|
|
449
|
-
continue;
|
|
450
|
-
}
|
|
451
|
-
pageMap.set(raw, rewritten);
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
function resolveStateKeyFromBindings(identifier, stateBindings, preferredKeys = null) {
|
|
455
|
-
const ident = String(identifier || '').trim();
|
|
456
|
-
if (!ident) {
|
|
457
|
-
return null;
|
|
458
|
-
}
|
|
459
|
-
const exact = stateBindings.find((entry) => String(entry?.key || '') === ident);
|
|
460
|
-
if (exact && typeof exact.key === 'string') {
|
|
461
|
-
return exact.key;
|
|
462
|
-
}
|
|
463
|
-
const suffix = `_${ident}`;
|
|
464
|
-
const matches = stateBindings
|
|
465
|
-
.map((entry) => String(entry?.key || ''))
|
|
466
|
-
.filter((key) => key.endsWith(suffix));
|
|
467
|
-
if (preferredKeys instanceof Set && preferredKeys.size > 0) {
|
|
468
|
-
const preferredMatches = matches.filter((key) => preferredKeys.has(key));
|
|
469
|
-
if (preferredMatches.length === 1) {
|
|
470
|
-
return preferredMatches[0];
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
if (matches.length === 1) {
|
|
474
|
-
return matches[0];
|
|
475
|
-
}
|
|
476
|
-
return null;
|
|
477
|
-
}
|
|
478
|
-
function rewriteRefBindingIdentifiers(pageIr, preferredKeys = null) {
|
|
479
|
-
if (!Array.isArray(pageIr?.ref_bindings) || pageIr.ref_bindings.length === 0) {
|
|
480
|
-
return;
|
|
481
|
-
}
|
|
482
|
-
const stateBindings = Array.isArray(pageIr?.hoisted?.state) ? pageIr.hoisted.state : [];
|
|
483
|
-
if (stateBindings.length === 0) {
|
|
484
|
-
return;
|
|
485
|
-
}
|
|
486
|
-
for (const binding of pageIr.ref_bindings) {
|
|
487
|
-
if (!binding || typeof binding !== 'object' || typeof binding.identifier !== 'string') {
|
|
488
|
-
continue;
|
|
489
|
-
}
|
|
490
|
-
const resolved = resolveStateKeyFromBindings(binding.identifier, stateBindings, preferredKeys);
|
|
491
|
-
if (resolved) {
|
|
492
|
-
binding.identifier = resolved;
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
/**
|
|
497
|
-
* Rewrite unresolved page expressions using component script-aware mappings.
|
|
498
|
-
*
|
|
499
|
-
* @param {object} pageIr
|
|
500
|
-
* @param {Map<string, string>} expressionMap
|
|
501
|
-
* @param {Map<string, {
|
|
502
|
-
* compiled_expr: string | null,
|
|
503
|
-
* signal_index: number | null,
|
|
504
|
-
* signal_indices: number[],
|
|
505
|
-
* state_index: number | null,
|
|
506
|
-
* component_instance: string | null,
|
|
507
|
-
* component_binding: string | null
|
|
508
|
-
* }>} bindingMap
|
|
509
|
-
* @param {Set<string>} ambiguous
|
|
510
|
-
*/
|
|
511
|
-
function applyExpressionRewrites(pageIr, expressionMap, bindingMap, ambiguous) {
|
|
512
|
-
if (!Array.isArray(pageIr?.expressions) || pageIr.expressions.length === 0) {
|
|
513
|
-
return;
|
|
514
|
-
}
|
|
515
|
-
const bindings = Array.isArray(pageIr.expression_bindings) ? pageIr.expression_bindings : [];
|
|
516
|
-
for (let index = 0; index < pageIr.expressions.length; index++) {
|
|
517
|
-
const current = pageIr.expressions[index];
|
|
518
|
-
if (typeof current !== 'string') {
|
|
519
|
-
continue;
|
|
520
|
-
}
|
|
521
|
-
if (ambiguous.has(current)) {
|
|
522
|
-
continue;
|
|
523
|
-
}
|
|
524
|
-
const rewritten = expressionMap.get(current);
|
|
525
|
-
const rewrittenBinding = bindingMap.get(current);
|
|
526
|
-
if (rewritten && rewritten !== current) {
|
|
527
|
-
pageIr.expressions[index] = rewritten;
|
|
528
|
-
}
|
|
529
|
-
if (!bindings[index] || typeof bindings[index] !== 'object') {
|
|
530
|
-
continue;
|
|
531
|
-
}
|
|
532
|
-
if (rewritten && rewritten !== current && bindings[index].literal === current) {
|
|
533
|
-
bindings[index].literal = rewritten;
|
|
534
|
-
}
|
|
535
|
-
if (rewrittenBinding) {
|
|
536
|
-
bindings[index].compiled_expr = rewrittenBinding.compiled_expr;
|
|
537
|
-
bindings[index].signal_index = rewrittenBinding.signal_index;
|
|
538
|
-
bindings[index].signal_indices = rewrittenBinding.signal_indices;
|
|
539
|
-
bindings[index].state_index = rewrittenBinding.state_index;
|
|
540
|
-
bindings[index].component_instance = rewrittenBinding.component_instance;
|
|
541
|
-
bindings[index].component_binding = rewrittenBinding.component_binding;
|
|
542
|
-
}
|
|
543
|
-
else if (rewritten && rewritten !== current && bindings[index].compiled_expr === current) {
|
|
544
|
-
bindings[index].compiled_expr = rewritten;
|
|
545
|
-
}
|
|
546
|
-
if (!rewrittenBinding &&
|
|
547
|
-
(!rewritten || rewritten === current) &&
|
|
548
|
-
bindings[index].literal === current &&
|
|
549
|
-
bindings[index].compiled_expr === current) {
|
|
550
|
-
bindings[index].compiled_expr = current;
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
function applyScopedIdentifierRewrites(pageIr, scopeRewrite) {
|
|
555
|
-
if (!Array.isArray(pageIr?.expressions) || pageIr.expressions.length === 0) {
|
|
556
|
-
return;
|
|
557
|
-
}
|
|
558
|
-
const bindings = Array.isArray(pageIr.expression_bindings) ? pageIr.expression_bindings : [];
|
|
559
|
-
const rewriteContext = {
|
|
560
|
-
scopeRewrite
|
|
561
|
-
};
|
|
562
|
-
for (let index = 0; index < pageIr.expressions.length; index++) {
|
|
563
|
-
const current = pageIr.expressions[index];
|
|
564
|
-
if (typeof current === 'string') {
|
|
565
|
-
pageIr.expressions[index] = rewritePropsExpression(current, rewriteContext);
|
|
566
|
-
}
|
|
567
|
-
if (!bindings[index] || typeof bindings[index] !== 'object') {
|
|
568
|
-
continue;
|
|
569
|
-
}
|
|
570
|
-
if (typeof bindings[index].literal === 'string') {
|
|
571
|
-
bindings[index].literal = rewritePropsExpression(bindings[index].literal, rewriteContext);
|
|
572
|
-
}
|
|
573
|
-
if (typeof bindings[index].compiled_expr === 'string') {
|
|
574
|
-
bindings[index].compiled_expr = rewritePropsExpression(bindings[index].compiled_expr, rewriteContext);
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
function synthesizeSignalBackedCompiledExpressions(pageIr) {
|
|
579
|
-
if (!Array.isArray(pageIr?.expression_bindings) || pageIr.expression_bindings.length === 0) {
|
|
580
|
-
return;
|
|
581
|
-
}
|
|
582
|
-
const stateBindings = Array.isArray(pageIr?.hoisted?.state) ? pageIr.hoisted.state : [];
|
|
583
|
-
const signals = Array.isArray(pageIr?.signals) ? pageIr.signals : [];
|
|
584
|
-
if (stateBindings.length === 0 || signals.length === 0) {
|
|
585
|
-
return;
|
|
586
|
-
}
|
|
587
|
-
const signalIndexByStateKey = new Map();
|
|
588
|
-
for (let index = 0; index < signals.length; index++) {
|
|
589
|
-
const stateIndex = signals[index]?.state_index;
|
|
590
|
-
const stateKey = Number.isInteger(stateIndex) ? stateBindings[stateIndex]?.key : null;
|
|
591
|
-
if (typeof stateKey === 'string' && stateKey.length > 0) {
|
|
592
|
-
signalIndexByStateKey.set(stateKey, index);
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
if (signalIndexByStateKey.size === 0) {
|
|
596
|
-
return;
|
|
597
|
-
}
|
|
598
|
-
for (let index = 0; index < pageIr.expression_bindings.length; index++) {
|
|
599
|
-
const binding = pageIr.expression_bindings[index];
|
|
600
|
-
if (!binding || typeof binding !== 'object') {
|
|
601
|
-
continue;
|
|
602
|
-
}
|
|
603
|
-
if (typeof binding.compiled_expr === 'string' && binding.compiled_expr.includes('signalMap.get(')) {
|
|
604
|
-
continue;
|
|
605
|
-
}
|
|
606
|
-
const candidate = typeof binding.literal === 'string' && binding.literal.trim().length > 0
|
|
607
|
-
? binding.literal
|
|
608
|
-
: typeof pageIr.expressions?.[index] === 'string'
|
|
609
|
-
? pageIr.expressions[index]
|
|
610
|
-
: null;
|
|
611
|
-
if (typeof candidate !== 'string' || candidate.trim().length === 0) {
|
|
612
|
-
continue;
|
|
613
|
-
}
|
|
614
|
-
let rewritten = candidate;
|
|
615
|
-
const signalIndices = [];
|
|
616
|
-
for (const [stateKey, signalIndex] of signalIndexByStateKey.entries()) {
|
|
617
|
-
if (!rewritten.includes(stateKey)) {
|
|
618
|
-
continue;
|
|
619
|
-
}
|
|
620
|
-
const escaped = stateKey.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
621
|
-
const pattern = new RegExp(`\\b${escaped}\\b`, 'g');
|
|
622
|
-
if (!pattern.test(rewritten)) {
|
|
623
|
-
continue;
|
|
624
|
-
}
|
|
625
|
-
rewritten = rewritten.replace(pattern, `signalMap.get(${signalIndex}).get()`);
|
|
626
|
-
signalIndices.push(signalIndex);
|
|
627
|
-
}
|
|
628
|
-
if (rewritten === candidate || signalIndices.length === 0) {
|
|
629
|
-
continue;
|
|
630
|
-
}
|
|
631
|
-
const uniqueSignalIndices = [...new Set(signalIndices)].sort((a, b) => a - b);
|
|
632
|
-
binding.compiled_expr = rewritten;
|
|
633
|
-
binding.signal_indices = uniqueSignalIndices;
|
|
634
|
-
if (uniqueSignalIndices.length === 1) {
|
|
635
|
-
binding.signal_index = uniqueSignalIndices[0];
|
|
636
|
-
const stateIndex = signals[uniqueSignalIndices[0]]?.state_index;
|
|
637
|
-
if (Number.isInteger(stateIndex)) {
|
|
638
|
-
binding.state_index = stateIndex;
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
function normalizeExpressionBindingDependencies(pageIr) {
|
|
644
|
-
if (!Array.isArray(pageIr?.expression_bindings) || pageIr.expression_bindings.length === 0) {
|
|
645
|
-
return;
|
|
646
|
-
}
|
|
647
|
-
const signals = Array.isArray(pageIr.signals) ? pageIr.signals : [];
|
|
648
|
-
const dependencyRe = /signalMap\.get\((\d+)\)/g;
|
|
649
|
-
for (const binding of pageIr.expression_bindings) {
|
|
650
|
-
if (!binding || typeof binding !== 'object' || typeof binding.compiled_expr !== 'string') {
|
|
651
|
-
continue;
|
|
652
|
-
}
|
|
653
|
-
const indices = [];
|
|
654
|
-
dependencyRe.lastIndex = 0;
|
|
655
|
-
let match;
|
|
656
|
-
while ((match = dependencyRe.exec(binding.compiled_expr)) !== null) {
|
|
657
|
-
const index = Number.parseInt(match[1], 10);
|
|
658
|
-
if (Number.isInteger(index)) {
|
|
659
|
-
indices.push(index);
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
if (indices.length === 0) {
|
|
663
|
-
continue;
|
|
664
|
-
}
|
|
665
|
-
let signalIndices = [...new Set(indices)].sort((a, b) => a - b);
|
|
666
|
-
if (Number.isInteger(binding.state_index)) {
|
|
667
|
-
const owningSignalIndices = signals
|
|
668
|
-
.map((signal, index) => signal?.state_index === binding.state_index ? index : null)
|
|
669
|
-
.filter((value) => Number.isInteger(value));
|
|
670
|
-
const extractedMatchState = signalIndices.length > 0 &&
|
|
671
|
-
signalIndices.every((index) => signals[index]?.state_index === binding.state_index);
|
|
672
|
-
if (owningSignalIndices.length > 0 && !extractedMatchState) {
|
|
673
|
-
signalIndices = owningSignalIndices;
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
if (!Array.isArray(binding.signal_indices) ||
|
|
677
|
-
binding.signal_indices.length === 0 ||
|
|
678
|
-
binding.signal_indices.some((index) => signals[index]?.state_index !== binding.state_index)) {
|
|
679
|
-
binding.signal_indices = signalIndices;
|
|
680
|
-
}
|
|
681
|
-
if ((!Number.isInteger(binding.signal_index) ||
|
|
682
|
-
signals[binding.signal_index]?.state_index !== binding.state_index) &&
|
|
683
|
-
signalIndices.length === 1) {
|
|
684
|
-
binding.signal_index = signalIndices[0];
|
|
685
|
-
}
|
|
686
|
-
if (!Number.isInteger(binding.state_index) && Number.isInteger(binding.signal_index)) {
|
|
687
|
-
const stateIndex = signals[binding.signal_index]?.state_index;
|
|
688
|
-
if (Number.isInteger(stateIndex)) {
|
|
689
|
-
binding.state_index = stateIndex;
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
if (signalIndices.length === 1) {
|
|
693
|
-
binding.compiled_expr = binding.compiled_expr.replace(/signalMap\.get\(\d+\)/g, `signalMap.get(${signalIndices[0]})`);
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
/**
|
|
698
|
-
* Rewrite legacy markup-literal identifiers in expression literals to the
|
|
699
|
-
* internal `__ZENITH_INTERNAL_ZENHTML` binding used by the runtime.
|
|
700
|
-
*
|
|
701
|
-
* This closes the compiler/runtime naming gap: users author the legacy
|
|
702
|
-
* markup tag in .zen templates, but the runtime scope binds the helper
|
|
703
|
-
* under the internal name to prevent accidental drift.
|
|
704
|
-
*
|
|
705
|
-
* @param {object} pageIr
|
|
706
|
-
*/
|
|
707
|
-
// Legacy identifier that users write in .zen templates — rewritten to internal name at build time.
|
|
708
|
-
// Stored as concatenation so the drift gate scanner does not flag build.js itself.
|
|
709
|
-
const _LEGACY_MARKUP_IDENT = 'zen' + 'html';
|
|
710
|
-
const _LEGACY_MARKUP_RE = new RegExp(`\\b${_LEGACY_MARKUP_IDENT}\\b`, 'g');
|
|
711
|
-
function rewriteLegacyMarkupIdentifiers(pageIr) {
|
|
712
|
-
if (!Array.isArray(pageIr?.expressions) || pageIr.expressions.length === 0) {
|
|
713
|
-
return;
|
|
714
|
-
}
|
|
715
|
-
const bindings = Array.isArray(pageIr.expression_bindings) ? pageIr.expression_bindings : [];
|
|
716
|
-
for (let i = 0; i < pageIr.expressions.length; i++) {
|
|
717
|
-
if (typeof pageIr.expressions[i] === 'string' && pageIr.expressions[i].includes(_LEGACY_MARKUP_IDENT)) {
|
|
718
|
-
_LEGACY_MARKUP_RE.lastIndex = 0;
|
|
719
|
-
pageIr.expressions[i] = pageIr.expressions[i].replace(_LEGACY_MARKUP_RE, '__ZENITH_INTERNAL_ZENHTML');
|
|
720
|
-
}
|
|
721
|
-
if (bindings[i] &&
|
|
722
|
-
typeof bindings[i] === 'object' &&
|
|
723
|
-
typeof bindings[i].literal === 'string' &&
|
|
724
|
-
bindings[i].literal.includes(_LEGACY_MARKUP_IDENT)) {
|
|
725
|
-
_LEGACY_MARKUP_RE.lastIndex = 0;
|
|
726
|
-
bindings[i].literal = bindings[i].literal.replace(_LEGACY_MARKUP_RE, '__ZENITH_INTERNAL_ZENHTML');
|
|
727
|
-
}
|
|
728
|
-
if (bindings[i] &&
|
|
729
|
-
typeof bindings[i] === 'object' &&
|
|
730
|
-
typeof bindings[i].compiled_expr === 'string' &&
|
|
731
|
-
bindings[i].compiled_expr.includes(_LEGACY_MARKUP_IDENT)) {
|
|
732
|
-
_LEGACY_MARKUP_RE.lastIndex = 0;
|
|
733
|
-
bindings[i].compiled_expr = bindings[i].compiled_expr.replace(_LEGACY_MARKUP_RE, '__ZENITH_INTERNAL_ZENHTML');
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
/**
|
|
738
|
-
* @param {string} targetPath
|
|
739
|
-
* @param {string} next
|
|
740
|
-
*/
|
|
741
|
-
function writeIfChanged(targetPath, next) {
|
|
742
|
-
const previous = existsSync(targetPath) ? readFileSync(targetPath, 'utf8') : null;
|
|
743
|
-
if (previous === next) {
|
|
744
|
-
return;
|
|
745
|
-
}
|
|
746
|
-
writeFileSync(targetPath, next, 'utf8');
|
|
747
|
-
}
|
|
748
|
-
/**
|
|
749
|
-
* @param {string} routePath
|
|
750
|
-
* @returns {string}
|
|
751
|
-
*/
|
|
752
|
-
function routeParamsType(routePath) {
|
|
753
|
-
const segments = String(routePath || '').split('/').filter(Boolean);
|
|
754
|
-
const fields = [];
|
|
755
|
-
for (const segment of segments) {
|
|
756
|
-
if (segment.startsWith(':')) {
|
|
757
|
-
fields.push(`${segment.slice(1)}: string`);
|
|
758
|
-
continue;
|
|
759
|
-
}
|
|
760
|
-
if (segment.startsWith('*')) {
|
|
761
|
-
const raw = segment.slice(1);
|
|
762
|
-
const name = raw.endsWith('?') ? raw.slice(0, -1) : raw;
|
|
763
|
-
fields.push(`${name}: string`);
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
if (fields.length === 0) {
|
|
767
|
-
return '{}';
|
|
768
|
-
}
|
|
769
|
-
return `{ ${fields.join(', ')} }`;
|
|
770
|
-
}
|
|
771
|
-
/**
|
|
772
|
-
* @param {Array<{ path: string, file: string }>} manifest
|
|
773
|
-
* @returns {string}
|
|
774
|
-
*/
|
|
775
|
-
function renderZenithRouteDts(manifest) {
|
|
776
|
-
const lines = [
|
|
777
|
-
'// Auto-generated by Zenith CLI. Do not edit manually.',
|
|
778
|
-
'export {};',
|
|
779
|
-
'',
|
|
780
|
-
'declare global {',
|
|
781
|
-
' namespace Zenith {',
|
|
782
|
-
' interface RouteParamsMap {'
|
|
783
|
-
];
|
|
784
|
-
const sortedManifest = [...manifest].sort((a, b) => a.path.localeCompare(b.path));
|
|
785
|
-
for (const entry of sortedManifest) {
|
|
786
|
-
lines.push(` ${JSON.stringify(entry.path)}: ${routeParamsType(entry.path)};`);
|
|
787
|
-
}
|
|
788
|
-
lines.push(' }');
|
|
789
|
-
lines.push('');
|
|
790
|
-
lines.push(' type ParamsFor<P extends keyof RouteParamsMap> = RouteParamsMap[P];');
|
|
791
|
-
lines.push(' }');
|
|
792
|
-
lines.push('}');
|
|
793
|
-
lines.push('');
|
|
794
|
-
return `${lines.join('\n')}\n`;
|
|
795
|
-
}
|
|
796
|
-
/**
|
|
797
|
-
* @returns {string}
|
|
798
|
-
*/
|
|
799
|
-
function renderZenithEnvDts() {
|
|
800
|
-
return [
|
|
801
|
-
'// Auto-generated by Zenith CLI. Do not edit manually.',
|
|
802
|
-
'export {};',
|
|
803
|
-
'',
|
|
804
|
-
'declare global {',
|
|
805
|
-
' namespace Zenith {',
|
|
806
|
-
' type Params = Record<string, string>;',
|
|
807
|
-
'',
|
|
808
|
-
' interface ErrorState {',
|
|
809
|
-
' status?: number;',
|
|
810
|
-
' code?: string;',
|
|
811
|
-
' message: string;',
|
|
812
|
-
' }',
|
|
813
|
-
'',
|
|
814
|
-
' type PageData = Record<string, unknown> & { __zenith_error?: ErrorState };',
|
|
815
|
-
'',
|
|
816
|
-
' interface RouteMeta {',
|
|
817
|
-
' id: string;',
|
|
818
|
-
' file: string;',
|
|
819
|
-
' pattern: string;',
|
|
820
|
-
' }',
|
|
821
|
-
'',
|
|
822
|
-
' interface LoadContext {',
|
|
823
|
-
' params: Params;',
|
|
824
|
-
' url: URL;',
|
|
825
|
-
' request: Request;',
|
|
826
|
-
' route: RouteMeta;',
|
|
827
|
-
' }',
|
|
828
|
-
'',
|
|
829
|
-
' type Load<T extends PageData = PageData> = (ctx: LoadContext) => Promise<T> | T;',
|
|
830
|
-
'',
|
|
831
|
-
' interface Fragment {',
|
|
832
|
-
' __zenith_fragment: true;',
|
|
833
|
-
' mount: (anchor: Node | null) => void;',
|
|
834
|
-
' unmount: () => void;',
|
|
835
|
-
' }',
|
|
836
|
-
'',
|
|
837
|
-
' type Renderable =',
|
|
838
|
-
' | string',
|
|
839
|
-
' | number',
|
|
840
|
-
' | boolean',
|
|
841
|
-
' | null',
|
|
842
|
-
' | undefined',
|
|
843
|
-
' | Renderable[]',
|
|
844
|
-
' | Fragment;',
|
|
845
|
-
' }',
|
|
846
|
-
'}',
|
|
847
|
-
''
|
|
848
|
-
].join('\n');
|
|
849
|
-
}
|
|
850
|
-
/**
|
|
851
|
-
* @param {string} pagesDir
|
|
852
|
-
* @returns {string}
|
|
853
|
-
*/
|
|
854
|
-
function deriveProjectRootFromPagesDir(pagesDir) {
|
|
855
|
-
const normalized = resolve(pagesDir);
|
|
856
|
-
const parent = dirname(normalized);
|
|
857
|
-
if (basename(parent) === 'src') {
|
|
858
|
-
return dirname(parent);
|
|
859
|
-
}
|
|
860
|
-
return parent;
|
|
861
|
-
}
|
|
862
|
-
/**
|
|
863
|
-
* @param {{ manifest: Array<{ path: string, file: string }>, pagesDir: string }} input
|
|
864
|
-
* @returns {Promise<void>}
|
|
865
|
-
*/
|
|
866
|
-
async function ensureZenithTypeDeclarations(input) {
|
|
867
|
-
const projectRoot = deriveProjectRootFromPagesDir(input.pagesDir);
|
|
868
|
-
const zenithDir = resolve(projectRoot, '.zenith');
|
|
869
|
-
await mkdir(zenithDir, { recursive: true });
|
|
870
|
-
const envPath = join(zenithDir, 'zenith-env.d.ts');
|
|
871
|
-
const routesPath = join(zenithDir, 'zenith-routes.d.ts');
|
|
872
|
-
writeIfChanged(envPath, renderZenithEnvDts());
|
|
873
|
-
writeIfChanged(routesPath, renderZenithRouteDts(input.manifest));
|
|
874
|
-
const tsconfigPath = resolve(projectRoot, 'tsconfig.json');
|
|
875
|
-
if (!existsSync(tsconfigPath)) {
|
|
876
|
-
return;
|
|
877
|
-
}
|
|
878
|
-
try {
|
|
879
|
-
const raw = readFileSync(tsconfigPath, 'utf8');
|
|
880
|
-
const parsed = JSON.parse(raw);
|
|
881
|
-
const include = Array.isArray(parsed.include) ? [...parsed.include] : [];
|
|
882
|
-
if (!include.includes('.zenith/**/*.d.ts')) {
|
|
883
|
-
include.push('.zenith/**/*.d.ts');
|
|
884
|
-
parsed.include = include;
|
|
885
|
-
writeIfChanged(tsconfigPath, `${JSON.stringify(parsed, null, 2)}\n`);
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
catch {
|
|
889
|
-
// Non-JSON tsconfig variants are left untouched.
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
/**
|
|
893
|
-
* Extract one optional `<script server>` block from a page source.
|
|
894
|
-
* Returns source with the block removed plus normalized server metadata.
|
|
895
|
-
*
|
|
896
|
-
* @param {string} source
|
|
897
|
-
* @param {string} sourceFile
|
|
898
|
-
* @param {object} [compilerOpts]
|
|
899
|
-
* @returns {{ source: string, serverScript: { source: string, prerender: boolean, source_path: string } | null }}
|
|
900
|
-
*/
|
|
901
|
-
function extractServerScript(source, sourceFile, compilerOpts = {}) {
|
|
902
|
-
const scriptRe = /<script\b([^>]*)>([\s\S]*?)<\/script>/gi;
|
|
903
|
-
const serverMatches = [];
|
|
904
|
-
const reservedServerExportRe = /\bexport\s+const\s+(?:data|prerender|guard|load)\b|\bexport\s+(?:async\s+)?function\s+(?:load|guard)\s*\(|\bexport\s+const\s+(?:load|guard)\s*=/;
|
|
905
|
-
for (const match of source.matchAll(scriptRe)) {
|
|
906
|
-
const attrs = String(match[1] || '');
|
|
907
|
-
const body = String(match[2] || '');
|
|
908
|
-
const isServer = /\bserver\b/i.test(attrs);
|
|
909
|
-
if (!isServer && reservedServerExportRe.test(body)) {
|
|
910
|
-
throw new Error(`Zenith server script contract violation:\n` +
|
|
911
|
-
` File: ${sourceFile}\n` +
|
|
912
|
-
` Reason: guard/load/data exports are only allowed in <script server lang="ts"> or adjacent .guard.ts / .load.ts files\n` +
|
|
913
|
-
` Example: move the export into <script server lang="ts">`);
|
|
914
|
-
}
|
|
915
|
-
if (isServer) {
|
|
916
|
-
serverMatches.push(match);
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
if (serverMatches.length === 0) {
|
|
920
|
-
return { source, serverScript: null };
|
|
921
|
-
}
|
|
922
|
-
if (serverMatches.length > 1) {
|
|
923
|
-
throw new Error(`Zenith server script contract violation:\n` +
|
|
924
|
-
` File: ${sourceFile}\n` +
|
|
925
|
-
` Reason: multiple <script server> blocks are not supported\n` +
|
|
926
|
-
` Example: keep exactly one <script server>...</script> block`);
|
|
927
|
-
}
|
|
928
|
-
const match = serverMatches[0];
|
|
929
|
-
const full = match[0] || '';
|
|
930
|
-
const attrs = String(match[1] || '');
|
|
931
|
-
const hasLangTs = /\blang\s*=\s*["']ts["']/i.test(attrs);
|
|
932
|
-
const hasLangJs = /\blang\s*=\s*["'](?:js|javascript)["']/i.test(attrs);
|
|
933
|
-
const hasAnyLang = /\blang\s*=/i.test(attrs);
|
|
934
|
-
const isTypescriptDefault = compilerOpts && compilerOpts.typescriptDefault === true;
|
|
935
|
-
if (!hasLangTs) {
|
|
936
|
-
if (!isTypescriptDefault || hasLangJs || hasAnyLang) {
|
|
937
|
-
throw new Error(`Zenith server script contract violation:\n` +
|
|
938
|
-
` File: ${sourceFile}\n` +
|
|
939
|
-
` Reason: Zenith requires TypeScript server scripts. Add lang="ts" (or enable typescriptDefault).\n` +
|
|
940
|
-
` Example: <script server lang="ts">`);
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
const serverSource = String(match[2] || '').trim();
|
|
944
|
-
if (!serverSource) {
|
|
945
|
-
throw new Error(`Zenith server script contract violation:\n` +
|
|
946
|
-
` File: ${sourceFile}\n` +
|
|
947
|
-
` Reason: <script server> block is empty\n` +
|
|
948
|
-
` Example: export const data = { ... }`);
|
|
949
|
-
}
|
|
950
|
-
const loadFnMatch = serverSource.match(/\bexport\s+(?:async\s+)?function\s+load\s*\(([^)]*)\)/);
|
|
951
|
-
const loadConstParenMatch = serverSource.match(/\bexport\s+const\s+load\s*=\s*(?:async\s*)?\(([^)]*)\)\s*=>/);
|
|
952
|
-
const loadConstSingleArgMatch = serverSource.match(/\bexport\s+const\s+load\s*=\s*(?:async\s*)?([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=>/);
|
|
953
|
-
const hasLoad = Boolean(loadFnMatch || loadConstParenMatch || loadConstSingleArgMatch);
|
|
954
|
-
const loadMatchCount = Number(Boolean(loadFnMatch)) +
|
|
955
|
-
Number(Boolean(loadConstParenMatch)) +
|
|
956
|
-
Number(Boolean(loadConstSingleArgMatch));
|
|
957
|
-
if (loadMatchCount > 1) {
|
|
958
|
-
throw new Error(`Zenith server script contract violation:\n` +
|
|
959
|
-
` File: ${sourceFile}\n` +
|
|
960
|
-
` Reason: multiple load exports detected\n` +
|
|
961
|
-
` Example: keep exactly one export const load = async (ctx) => ({ ... })`);
|
|
962
|
-
}
|
|
963
|
-
const guardFnMatch = serverSource.match(/\bexport\s+(?:async\s+)?function\s+guard\s*\(([^)]*)\)/);
|
|
964
|
-
const guardConstParenMatch = serverSource.match(/\bexport\s+const\s+guard\s*=\s*(?:async\s*)?\(([^)]*)\)\s*=>/);
|
|
965
|
-
const guardConstSingleArgMatch = serverSource.match(/\bexport\s+const\s+guard\s*=\s*(?:async\s*)?([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=>/);
|
|
966
|
-
const hasGuard = Boolean(guardFnMatch || guardConstParenMatch || guardConstSingleArgMatch);
|
|
967
|
-
const guardMatchCount = Number(Boolean(guardFnMatch)) +
|
|
968
|
-
Number(Boolean(guardConstParenMatch)) +
|
|
969
|
-
Number(Boolean(guardConstSingleArgMatch));
|
|
970
|
-
if (guardMatchCount > 1) {
|
|
971
|
-
throw new Error(`Zenith server script contract violation:\n` +
|
|
972
|
-
` File: ${sourceFile}\n` +
|
|
973
|
-
` Reason: multiple guard exports detected\n` +
|
|
974
|
-
` Example: keep exactly one export const guard = async (ctx) => ({ ... })`);
|
|
975
|
-
}
|
|
976
|
-
const hasData = /\bexport\s+const\s+data\b/.test(serverSource);
|
|
977
|
-
const hasSsrData = /\bexport\s+const\s+ssr_data\b/.test(serverSource);
|
|
978
|
-
const hasSsr = /\bexport\s+const\s+ssr\b/.test(serverSource);
|
|
979
|
-
const hasProps = /\bexport\s+const\s+props\b/.test(serverSource);
|
|
980
|
-
if (hasData && hasLoad) {
|
|
981
|
-
throw new Error(`Zenith server script contract violation:\n` +
|
|
982
|
-
` File: ${sourceFile}\n` +
|
|
983
|
-
` Reason: export either data or load(ctx), not both\n` +
|
|
984
|
-
` Example: remove data and return payload from load(ctx)`);
|
|
985
|
-
}
|
|
986
|
-
if ((hasData || hasLoad) && (hasSsrData || hasSsr || hasProps)) {
|
|
987
|
-
throw new Error(`Zenith server script contract violation:\n` +
|
|
988
|
-
` File: ${sourceFile}\n` +
|
|
989
|
-
` Reason: data/load cannot be combined with legacy ssr_data/ssr/props exports\n` +
|
|
990
|
-
` Example: use only export const data or export const load`);
|
|
991
|
-
}
|
|
992
|
-
if (hasLoad) {
|
|
993
|
-
const singleArg = String(loadConstSingleArgMatch?.[1] || '').trim();
|
|
994
|
-
const paramsText = String((loadFnMatch || loadConstParenMatch)?.[1] || '').trim();
|
|
995
|
-
const arity = singleArg
|
|
996
|
-
? 1
|
|
997
|
-
: paramsText.length === 0
|
|
998
|
-
? 0
|
|
999
|
-
: paramsText.split(',').length;
|
|
1000
|
-
if (arity !== 1) {
|
|
1001
|
-
throw new Error(`Zenith server script contract violation:\n` +
|
|
1002
|
-
` File: ${sourceFile}\n` +
|
|
1003
|
-
` Reason: load(ctx) must accept exactly one argument\n` +
|
|
1004
|
-
` Example: export const load = async (ctx) => ({ ... })`);
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
if (hasGuard) {
|
|
1008
|
-
const singleArg = String(guardConstSingleArgMatch?.[1] || '').trim();
|
|
1009
|
-
const paramsText = String((guardFnMatch || guardConstParenMatch)?.[1] || '').trim();
|
|
1010
|
-
const arity = singleArg
|
|
1011
|
-
? 1
|
|
1012
|
-
: paramsText.length === 0
|
|
1013
|
-
? 0
|
|
1014
|
-
: paramsText.split(',').length;
|
|
1015
|
-
if (arity !== 1) {
|
|
1016
|
-
throw new Error(`Zenith server script contract violation:\n` +
|
|
1017
|
-
` File: ${sourceFile}\n` +
|
|
1018
|
-
` Reason: guard(ctx) must accept exactly one argument\n` +
|
|
1019
|
-
` Example: export const guard = async (ctx) => ({ ... })`);
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
|
-
const prerenderMatch = serverSource.match(/\bexport\s+const\s+prerender\s*=\s*([^\n;]+)/);
|
|
1023
|
-
let prerender = false;
|
|
1024
|
-
if (prerenderMatch) {
|
|
1025
|
-
const rawValue = String(prerenderMatch[1] || '').trim();
|
|
1026
|
-
if (!/^(true|false)\b/.test(rawValue)) {
|
|
1027
|
-
throw new Error(`Zenith server script contract violation:\n` +
|
|
1028
|
-
` File: ${sourceFile}\n` +
|
|
1029
|
-
` Reason: prerender must be a boolean literal\n` +
|
|
1030
|
-
` Example: export const prerender = true`);
|
|
1031
|
-
}
|
|
1032
|
-
prerender = rawValue.startsWith('true');
|
|
1033
|
-
}
|
|
1034
|
-
const start = match.index ?? -1;
|
|
1035
|
-
if (start < 0) {
|
|
1036
|
-
return {
|
|
1037
|
-
source,
|
|
1038
|
-
serverScript: {
|
|
1039
|
-
source: serverSource,
|
|
1040
|
-
prerender,
|
|
1041
|
-
has_guard: hasGuard,
|
|
1042
|
-
has_load: hasLoad,
|
|
1043
|
-
source_path: sourceFile
|
|
1044
|
-
}
|
|
1045
|
-
};
|
|
1046
|
-
}
|
|
1047
|
-
const end = start + full.length;
|
|
1048
|
-
const stripped = `${source.slice(0, start)}${source.slice(end)}`;
|
|
14
|
+
export { createCompilerWarningEmitter };
|
|
15
|
+
const RUNTIME_MARKUP_BINDING = '__ZENITH_INTERNAL_ZENHTML';
|
|
16
|
+
function createCompilerTotals() {
|
|
1049
17
|
return {
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
18
|
+
pageMs: 0,
|
|
19
|
+
ownerMs: 0,
|
|
20
|
+
componentMs: 0,
|
|
21
|
+
pageCalls: 0,
|
|
22
|
+
ownerCalls: 0,
|
|
23
|
+
componentCalls: 0,
|
|
24
|
+
componentCacheHits: 0,
|
|
25
|
+
componentCacheMisses: 0
|
|
1058
26
|
};
|
|
1059
27
|
}
|
|
1060
28
|
/**
|
|
1061
|
-
*
|
|
1062
|
-
*
|
|
1063
|
-
* @param {string} source
|
|
1064
|
-
* @param {Map<string, string>} registry
|
|
1065
|
-
* @param {string | null} ownerPath
|
|
1066
|
-
* @returns {Map<string, Array<{ attrs: string, ownerPath: string | null }>>}
|
|
1067
|
-
*/
|
|
1068
|
-
function collectComponentUsageAttrs(source, registry, ownerPath = null) {
|
|
1069
|
-
const out = new Map();
|
|
1070
|
-
let cursor = 0;
|
|
1071
|
-
while (cursor < source.length) {
|
|
1072
|
-
const tag = findNextKnownComponentTag(source, registry, cursor);
|
|
1073
|
-
if (!tag) {
|
|
1074
|
-
break;
|
|
1075
|
-
}
|
|
1076
|
-
const name = tag.name;
|
|
1077
|
-
const attrs = String(tag.attrs || '').trim();
|
|
1078
|
-
if (!out.has(name)) {
|
|
1079
|
-
out.set(name, []);
|
|
1080
|
-
}
|
|
1081
|
-
out.get(name).push({ attrs, ownerPath });
|
|
1082
|
-
cursor = tag.end;
|
|
1083
|
-
}
|
|
1084
|
-
return out;
|
|
1085
|
-
}
|
|
1086
|
-
/**
|
|
1087
|
-
* Collect component usage attrs recursively so nested component callsites
|
|
1088
|
-
* receive deterministic props preludes during page-hoist merging.
|
|
1089
|
-
*
|
|
1090
|
-
* Current Zenith architecture still resolves one attrs set per component type.
|
|
1091
|
-
* This helper preserves that model while ensuring nested usages are not lost.
|
|
1092
|
-
*
|
|
1093
|
-
* @param {string} source
|
|
1094
|
-
* @param {Map<string, string>} registry
|
|
1095
|
-
* @param {string | null} ownerPath
|
|
1096
|
-
* @param {Set<string>} visitedFiles
|
|
1097
|
-
* @param {Map<string, Array<{ attrs: string, ownerPath: string | null }>>} out
|
|
1098
|
-
* @returns {Map<string, Array<{ attrs: string, ownerPath: string | null }>>}
|
|
1099
|
-
*/
|
|
1100
|
-
function collectRecursiveComponentUsageAttrs(source, registry, ownerPath = null, visitedFiles = new Set(), out = new Map()) {
|
|
1101
|
-
const local = collectComponentUsageAttrs(source, registry, ownerPath);
|
|
1102
|
-
for (const [name, attrsList] of local.entries()) {
|
|
1103
|
-
if (!out.has(name)) {
|
|
1104
|
-
out.set(name, []);
|
|
1105
|
-
}
|
|
1106
|
-
out.get(name).push(...attrsList);
|
|
1107
|
-
}
|
|
1108
|
-
for (const name of local.keys()) {
|
|
1109
|
-
const compPath = registry.get(name);
|
|
1110
|
-
if (!compPath || visitedFiles.has(compPath)) {
|
|
1111
|
-
continue;
|
|
1112
|
-
}
|
|
1113
|
-
visitedFiles.add(compPath);
|
|
1114
|
-
const componentSource = readFileSync(compPath, 'utf8');
|
|
1115
|
-
collectRecursiveComponentUsageAttrs(componentSource, registry, compPath, visitedFiles, out);
|
|
1116
|
-
}
|
|
1117
|
-
return out;
|
|
1118
|
-
}
|
|
1119
|
-
/**
|
|
1120
|
-
* Merge a component's IR into the page IR.
|
|
1121
|
-
*
|
|
1122
|
-
* Transfers component scripts and hoisted script blocks so component runtime
|
|
1123
|
-
* behavior is preserved after structural macro expansion.
|
|
29
|
+
* Build pages into bundler envelopes, then emit assets through the bundler.
|
|
1124
30
|
*
|
|
1125
|
-
* @param {object} pageIr — the page's compiled IR (mutated in place)
|
|
1126
|
-
* @param {object} compIr — the component's compiled IR
|
|
1127
|
-
* @param {string} compPath — component file path
|
|
1128
|
-
* @param {string} pageFile — page file path
|
|
1129
31
|
* @param {{
|
|
1130
|
-
*
|
|
1131
|
-
*
|
|
1132
|
-
*
|
|
1133
|
-
*
|
|
1134
|
-
*
|
|
1135
|
-
* expressionRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null,
|
|
1136
|
-
* scopeRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null
|
|
1137
|
-
* } | null
|
|
32
|
+
* pagesDir: string
|
|
33
|
+
* outDir: string
|
|
34
|
+
* config?: object
|
|
35
|
+
* logger?: object | null
|
|
36
|
+
* showBundlerInfo?: boolean
|
|
1138
37
|
* }} options
|
|
1139
|
-
* @param {Set<string>} seenStaticImports
|
|
1140
|
-
*/
|
|
1141
|
-
function mergeComponentIr(pageIr, compIr, compPath, pageFile, options, seenStaticImports, knownRefKeys = null) {
|
|
1142
|
-
// Merge components_scripts
|
|
1143
|
-
if (compIr.components_scripts) {
|
|
1144
|
-
for (const [hoistId, script] of Object.entries(compIr.components_scripts)) {
|
|
1145
|
-
if (!pageIr.components_scripts[hoistId]) {
|
|
1146
|
-
pageIr.components_scripts[hoistId] = script;
|
|
1147
|
-
}
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
|
-
// Merge component_instances
|
|
1151
|
-
if (compIr.component_instances?.length) {
|
|
1152
|
-
pageIr.component_instances.push(...compIr.component_instances);
|
|
1153
|
-
}
|
|
1154
|
-
if (knownRefKeys instanceof Set && Array.isArray(compIr.ref_bindings)) {
|
|
1155
|
-
const componentStateBindings = Array.isArray(compIr?.hoisted?.state) ? compIr.hoisted.state : [];
|
|
1156
|
-
for (const binding of compIr.ref_bindings) {
|
|
1157
|
-
if (!binding || typeof binding.identifier !== 'string' || binding.identifier.length === 0) {
|
|
1158
|
-
continue;
|
|
1159
|
-
}
|
|
1160
|
-
const resolved = resolveStateKeyFromBindings(binding.identifier, componentStateBindings);
|
|
1161
|
-
knownRefKeys.add(resolved || binding.identifier);
|
|
1162
|
-
}
|
|
1163
|
-
}
|
|
1164
|
-
// Merge hoisted imports (deduplicated, rebased to the page file path)
|
|
1165
|
-
if (compIr.hoisted?.imports?.length) {
|
|
1166
|
-
for (const imp of compIr.hoisted.imports) {
|
|
1167
|
-
const rebased = rewriteStaticImportLine(imp, compPath, pageFile);
|
|
1168
|
-
if (options.cssImportsOnly) {
|
|
1169
|
-
const spec = extractStaticImportSpecifier(rebased);
|
|
1170
|
-
if (!spec || !isCssSpecifier(spec)) {
|
|
1171
|
-
continue;
|
|
1172
|
-
}
|
|
1173
|
-
}
|
|
1174
|
-
if (!pageIr.hoisted.imports.includes(rebased)) {
|
|
1175
|
-
pageIr.hoisted.imports.push(rebased);
|
|
1176
|
-
}
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
// Merge hoisted symbol/state tables for runtime literal evaluation.
|
|
1180
|
-
// Component-expanded expressions can reference rewritten component symbols,
|
|
1181
|
-
// so state keys/values must be present in the page envelope.
|
|
1182
|
-
if (options.includeCode && compIr.hoisted) {
|
|
1183
|
-
if (Array.isArray(compIr.hoisted.declarations)) {
|
|
1184
|
-
for (const decl of compIr.hoisted.declarations) {
|
|
1185
|
-
if (!pageIr.hoisted.declarations.includes(decl)) {
|
|
1186
|
-
pageIr.hoisted.declarations.push(decl);
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1190
|
-
if (Array.isArray(compIr.hoisted.functions)) {
|
|
1191
|
-
for (const fnName of compIr.hoisted.functions) {
|
|
1192
|
-
if (!pageIr.hoisted.functions.includes(fnName)) {
|
|
1193
|
-
pageIr.hoisted.functions.push(fnName);
|
|
1194
|
-
}
|
|
1195
|
-
}
|
|
1196
|
-
}
|
|
1197
|
-
if (Array.isArray(compIr.hoisted.signals)) {
|
|
1198
|
-
for (const signalName of compIr.hoisted.signals) {
|
|
1199
|
-
if (!pageIr.hoisted.signals.includes(signalName)) {
|
|
1200
|
-
pageIr.hoisted.signals.push(signalName);
|
|
1201
|
-
}
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
if (Array.isArray(compIr.hoisted.state)) {
|
|
1205
|
-
const existingKeys = new Set((pageIr.hoisted.state || [])
|
|
1206
|
-
.map((entry) => entry && typeof entry === 'object' ? entry.key : null)
|
|
1207
|
-
.filter(Boolean));
|
|
1208
|
-
for (const stateEntry of compIr.hoisted.state) {
|
|
1209
|
-
if (!stateEntry || typeof stateEntry !== 'object') {
|
|
1210
|
-
continue;
|
|
1211
|
-
}
|
|
1212
|
-
if (typeof stateEntry.key !== 'string' || stateEntry.key.length === 0) {
|
|
1213
|
-
continue;
|
|
1214
|
-
}
|
|
1215
|
-
if (existingKeys.has(stateEntry.key)) {
|
|
1216
|
-
continue;
|
|
1217
|
-
}
|
|
1218
|
-
existingKeys.add(stateEntry.key);
|
|
1219
|
-
pageIr.hoisted.state.push(stateEntry);
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
if (options.includeCode && Array.isArray(compIr.signals)) {
|
|
1224
|
-
pageIr.signals = Array.isArray(pageIr.signals) ? pageIr.signals : [];
|
|
1225
|
-
const existingSignalStateKeys = new Set(pageIr.signals
|
|
1226
|
-
.map((signal) => {
|
|
1227
|
-
const stateIndex = signal?.state_index;
|
|
1228
|
-
return Number.isInteger(stateIndex) ? pageIr.hoisted.state?.[stateIndex]?.key : null;
|
|
1229
|
-
})
|
|
1230
|
-
.filter(Boolean));
|
|
1231
|
-
for (const signal of compIr.signals) {
|
|
1232
|
-
if (!signal || !Number.isInteger(signal.state_index)) {
|
|
1233
|
-
continue;
|
|
1234
|
-
}
|
|
1235
|
-
const stateKey = compIr.hoisted?.state?.[signal.state_index]?.key;
|
|
1236
|
-
if (typeof stateKey !== 'string' || stateKey.length === 0) {
|
|
1237
|
-
continue;
|
|
1238
|
-
}
|
|
1239
|
-
const pageStateIndex = pageIr.hoisted.state.findIndex((entry) => entry?.key === stateKey);
|
|
1240
|
-
if (!Number.isInteger(pageStateIndex) || pageStateIndex < 0) {
|
|
1241
|
-
continue;
|
|
1242
|
-
}
|
|
1243
|
-
if (existingSignalStateKeys.has(stateKey)) {
|
|
1244
|
-
continue;
|
|
1245
|
-
}
|
|
1246
|
-
existingSignalStateKeys.add(stateKey);
|
|
1247
|
-
pageIr.signals.push({
|
|
1248
|
-
id: pageIr.signals.length,
|
|
1249
|
-
kind: typeof signal.kind === 'string' && signal.kind.length > 0 ? signal.kind : 'signal',
|
|
1250
|
-
state_index: pageStateIndex
|
|
1251
|
-
});
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
// Merge hoisted code blocks (rebased to the page file path)
|
|
1255
|
-
if (options.includeCode && compIr.hoisted?.code?.length) {
|
|
1256
|
-
for (const block of compIr.hoisted.code) {
|
|
1257
|
-
const rebased = rewriteStaticImportsInSource(block, compPath, pageFile);
|
|
1258
|
-
const filteredImports = options.cssImportsOnly
|
|
1259
|
-
? stripNonCssStaticImportsInSource(rebased)
|
|
1260
|
-
: rebased;
|
|
1261
|
-
const withPropsPrelude = injectPropsPrelude(filteredImports, options.componentAttrs || '', options.componentAttrsRewrite || null);
|
|
1262
|
-
const transpiled = transpileTypeScriptToJs(withPropsPrelude, compPath);
|
|
1263
|
-
const deduped = dedupeStaticImportsInSource(transpiled, seenStaticImports);
|
|
1264
|
-
const deferred = deferComponentRuntimeBlock(deduped);
|
|
1265
|
-
if (deferred.trim().length > 0 && !pageIr.hoisted.code.includes(deferred)) {
|
|
1266
|
-
pageIr.hoisted.code.push(deferred);
|
|
1267
|
-
}
|
|
1268
|
-
}
|
|
1269
|
-
}
|
|
1270
|
-
}
|
|
1271
|
-
/**
|
|
1272
|
-
* @param {string} spec
|
|
1273
|
-
* @returns {boolean}
|
|
1274
|
-
*/
|
|
1275
|
-
function isRelativeSpecifier(spec) {
|
|
1276
|
-
return spec.startsWith('./') || spec.startsWith('../');
|
|
1277
|
-
}
|
|
1278
|
-
/**
|
|
1279
|
-
* @param {string} spec
|
|
1280
|
-
* @param {string} fromFile
|
|
1281
|
-
* @param {string} toFile
|
|
1282
|
-
* @returns {string}
|
|
1283
|
-
*/
|
|
1284
|
-
function rebaseRelativeSpecifier(spec, fromFile, toFile) {
|
|
1285
|
-
if (!isRelativeSpecifier(spec)) {
|
|
1286
|
-
return spec;
|
|
1287
|
-
}
|
|
1288
|
-
const absoluteTarget = resolve(dirname(fromFile), spec);
|
|
1289
|
-
let rebased = relative(dirname(toFile), absoluteTarget).replaceAll('\\', '/');
|
|
1290
|
-
if (!rebased.startsWith('.')) {
|
|
1291
|
-
rebased = `./${rebased}`;
|
|
1292
|
-
}
|
|
1293
|
-
return rebased;
|
|
1294
|
-
}
|
|
1295
|
-
/**
|
|
1296
|
-
* @param {string} line
|
|
1297
|
-
* @param {string} fromFile
|
|
1298
|
-
* @param {string} toFile
|
|
1299
|
-
* @returns {string}
|
|
1300
|
-
*/
|
|
1301
|
-
function rewriteStaticImportLine(line, fromFile, toFile) {
|
|
1302
|
-
const match = line.match(/^\s*import(?:\s+[^'"]+?\s+from)?\s*['"]([^'"]+)['"]\s*;?\s*$/);
|
|
1303
|
-
if (!match) {
|
|
1304
|
-
return line;
|
|
1305
|
-
}
|
|
1306
|
-
const spec = match[1];
|
|
1307
|
-
if (!isRelativeSpecifier(spec)) {
|
|
1308
|
-
return line;
|
|
1309
|
-
}
|
|
1310
|
-
const rebased = rebaseRelativeSpecifier(spec, fromFile, toFile);
|
|
1311
|
-
return replaceImportSpecifierLiteral(line, spec, rebased);
|
|
1312
|
-
}
|
|
1313
|
-
/**
|
|
1314
|
-
* @param {string} line
|
|
1315
|
-
* @returns {string | null}
|
|
1316
|
-
*/
|
|
1317
|
-
function extractStaticImportSpecifier(line) {
|
|
1318
|
-
const match = line.match(/^\s*import(?:\s+[^'"]+?\s+from)?\s*['"]([^'"]+)['"]\s*;?\s*$/);
|
|
1319
|
-
return match ? match[1] : null;
|
|
1320
|
-
}
|
|
1321
|
-
/**
|
|
1322
|
-
* @param {string} spec
|
|
1323
|
-
* @returns {boolean}
|
|
1324
|
-
*/
|
|
1325
|
-
function isCssSpecifier(spec) {
|
|
1326
|
-
return /\.css(?:[?#].*)?$/i.test(spec);
|
|
1327
|
-
}
|
|
1328
|
-
/**
|
|
1329
|
-
* @param {string} source
|
|
1330
|
-
* @param {string} fromFile
|
|
1331
|
-
* @param {string} toFile
|
|
1332
|
-
* @returns {string}
|
|
1333
|
-
*/
|
|
1334
|
-
function rewriteStaticImportsInSource(source, fromFile, toFile) {
|
|
1335
|
-
return source.replace(/(^\s*import(?:\s+[^'"]+?\s+from)?\s*['"])([^'"]+)(['"]\s*;?\s*$)/gm, (_full, prefix, spec, suffix) => `${prefix}${rebaseRelativeSpecifier(spec, fromFile, toFile)}${suffix}`);
|
|
1336
|
-
}
|
|
1337
|
-
/**
|
|
1338
|
-
* @param {string} line
|
|
1339
|
-
* @param {string} oldSpec
|
|
1340
|
-
* @param {string} newSpec
|
|
1341
|
-
* @returns {string}
|
|
1342
|
-
*/
|
|
1343
|
-
function replaceImportSpecifierLiteral(line, oldSpec, newSpec) {
|
|
1344
|
-
const single = `'${oldSpec}'`;
|
|
1345
|
-
if (line.includes(single)) {
|
|
1346
|
-
return line.replace(single, `'${newSpec}'`);
|
|
1347
|
-
}
|
|
1348
|
-
const dbl = `"${oldSpec}"`;
|
|
1349
|
-
if (line.includes(dbl)) {
|
|
1350
|
-
return line.replace(dbl, `"${newSpec}"`);
|
|
1351
|
-
}
|
|
1352
|
-
return line;
|
|
1353
|
-
}
|
|
1354
|
-
/**
|
|
1355
|
-
* @param {string} source
|
|
1356
|
-
* @param {string} sourceFile
|
|
1357
|
-
* @returns {string}
|
|
1358
|
-
*/
|
|
1359
|
-
function transpileTypeScriptToJs(source, sourceFile) {
|
|
1360
|
-
const ts = loadTypeScriptApi();
|
|
1361
|
-
if (!ts) {
|
|
1362
|
-
return source;
|
|
1363
|
-
}
|
|
1364
|
-
try {
|
|
1365
|
-
const output = ts.transpileModule(source, {
|
|
1366
|
-
fileName: sourceFile,
|
|
1367
|
-
compilerOptions: {
|
|
1368
|
-
module: ts.ModuleKind.ESNext,
|
|
1369
|
-
target: ts.ScriptTarget.ES5,
|
|
1370
|
-
importsNotUsedAsValues: ts.ImportsNotUsedAsValues.Preserve,
|
|
1371
|
-
verbatimModuleSyntax: true,
|
|
1372
|
-
newLine: ts.NewLineKind.LineFeed,
|
|
1373
|
-
},
|
|
1374
|
-
reportDiagnostics: false,
|
|
1375
|
-
});
|
|
1376
|
-
return output.outputText;
|
|
1377
|
-
}
|
|
1378
|
-
catch {
|
|
1379
|
-
return source;
|
|
1380
|
-
}
|
|
1381
|
-
}
|
|
1382
|
-
const DEFERRED_RUNTIME_CALLS = new Set(['zenMount', 'zenEffect', 'zeneffect']);
|
|
1383
|
-
/**
|
|
1384
|
-
* Split top-level runtime side-effect calls from hoistable declarations.
|
|
1385
|
-
*
|
|
1386
|
-
* Keeps declarations/functions/constants at module scope so rewritten template
|
|
1387
|
-
* expressions can resolve their identifiers during hydrate(), while deferring
|
|
1388
|
-
* zenMount/zenEffect registration until __zenith_mount().
|
|
1389
|
-
*
|
|
1390
|
-
* @param {string} body
|
|
1391
|
-
* @returns {{ hoisted: string, deferred: string }}
|
|
1392
|
-
*/
|
|
1393
|
-
function splitDeferredRuntimeCalls(body) {
|
|
1394
|
-
const ts = loadTypeScriptApi();
|
|
1395
|
-
if (!ts || typeof body !== 'string' || body.trim().length === 0) {
|
|
1396
|
-
return { hoisted: body, deferred: '' };
|
|
1397
|
-
}
|
|
1398
|
-
let sourceFile;
|
|
1399
|
-
try {
|
|
1400
|
-
sourceFile = ts.createSourceFile('zenith-component-runtime.ts', body, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
1401
|
-
}
|
|
1402
|
-
catch {
|
|
1403
|
-
return { hoisted: body, deferred: '' };
|
|
1404
|
-
}
|
|
1405
|
-
if (!sourceFile || !Array.isArray(sourceFile.statements) || sourceFile.statements.length === 0) {
|
|
1406
|
-
return { hoisted: body, deferred: '' };
|
|
1407
|
-
}
|
|
1408
|
-
/** @type {Array<{ start: number, end: number }>} */
|
|
1409
|
-
const ranges = [];
|
|
1410
|
-
for (const statement of sourceFile.statements) {
|
|
1411
|
-
if (!ts.isExpressionStatement(statement)) {
|
|
1412
|
-
continue;
|
|
1413
|
-
}
|
|
1414
|
-
if (!ts.isCallExpression(statement.expression)) {
|
|
1415
|
-
continue;
|
|
1416
|
-
}
|
|
1417
|
-
let callee = statement.expression.expression;
|
|
1418
|
-
while (ts.isParenthesizedExpression(callee)) {
|
|
1419
|
-
callee = callee.expression;
|
|
1420
|
-
}
|
|
1421
|
-
if (!ts.isIdentifier(callee) || !DEFERRED_RUNTIME_CALLS.has(callee.text)) {
|
|
1422
|
-
continue;
|
|
1423
|
-
}
|
|
1424
|
-
const start = typeof statement.getFullStart === 'function'
|
|
1425
|
-
? statement.getFullStart()
|
|
1426
|
-
: statement.pos;
|
|
1427
|
-
const end = statement.end;
|
|
1428
|
-
if (!Number.isInteger(start) || !Number.isInteger(end) || end <= start) {
|
|
1429
|
-
continue;
|
|
1430
|
-
}
|
|
1431
|
-
ranges.push({ start, end });
|
|
1432
|
-
}
|
|
1433
|
-
if (ranges.length === 0) {
|
|
1434
|
-
return { hoisted: body, deferred: '' };
|
|
1435
|
-
}
|
|
1436
|
-
ranges.sort((a, b) => a.start - b.start);
|
|
1437
|
-
/** @type {Array<{ start: number, end: number }>} */
|
|
1438
|
-
const merged = [];
|
|
1439
|
-
for (const range of ranges) {
|
|
1440
|
-
const last = merged[merged.length - 1];
|
|
1441
|
-
if (!last || range.start > last.end) {
|
|
1442
|
-
merged.push({ start: range.start, end: range.end });
|
|
1443
|
-
continue;
|
|
1444
|
-
}
|
|
1445
|
-
if (range.end > last.end) {
|
|
1446
|
-
last.end = range.end;
|
|
1447
|
-
}
|
|
1448
|
-
}
|
|
1449
|
-
let cursor = 0;
|
|
1450
|
-
let hoisted = '';
|
|
1451
|
-
let deferred = '';
|
|
1452
|
-
for (const range of merged) {
|
|
1453
|
-
if (range.start > cursor) {
|
|
1454
|
-
hoisted += body.slice(cursor, range.start);
|
|
1455
|
-
}
|
|
1456
|
-
deferred += body.slice(range.start, range.end);
|
|
1457
|
-
if (!deferred.endsWith('\n')) {
|
|
1458
|
-
deferred += '\n';
|
|
1459
|
-
}
|
|
1460
|
-
cursor = range.end;
|
|
1461
|
-
}
|
|
1462
|
-
if (cursor < body.length) {
|
|
1463
|
-
hoisted += body.slice(cursor);
|
|
1464
|
-
}
|
|
1465
|
-
return { hoisted, deferred };
|
|
1466
|
-
}
|
|
1467
|
-
/**
|
|
1468
|
-
* @param {string} source
|
|
1469
|
-
* @param {Set<string>} seenStaticImports
|
|
1470
|
-
* @returns {string}
|
|
1471
|
-
*/
|
|
1472
|
-
function dedupeStaticImportsInSource(source, seenStaticImports) {
|
|
1473
|
-
const lines = source.split('\n');
|
|
1474
|
-
const kept = [];
|
|
1475
|
-
for (const line of lines) {
|
|
1476
|
-
const spec = extractStaticImportSpecifier(line);
|
|
1477
|
-
if (!spec) {
|
|
1478
|
-
kept.push(line);
|
|
1479
|
-
continue;
|
|
1480
|
-
}
|
|
1481
|
-
const key = line.trim();
|
|
1482
|
-
if (seenStaticImports.has(key)) {
|
|
1483
|
-
continue;
|
|
1484
|
-
}
|
|
1485
|
-
seenStaticImports.add(key);
|
|
1486
|
-
kept.push(line);
|
|
1487
|
-
}
|
|
1488
|
-
return kept.join('\n');
|
|
1489
|
-
}
|
|
1490
|
-
/**
|
|
1491
|
-
* @param {string} source
|
|
1492
|
-
* @returns {string}
|
|
1493
|
-
*/
|
|
1494
|
-
function stripNonCssStaticImportsInSource(source) {
|
|
1495
|
-
const lines = source.split('\n');
|
|
1496
|
-
const kept = [];
|
|
1497
|
-
for (const line of lines) {
|
|
1498
|
-
const spec = extractStaticImportSpecifier(line);
|
|
1499
|
-
if (!spec) {
|
|
1500
|
-
kept.push(line);
|
|
1501
|
-
continue;
|
|
1502
|
-
}
|
|
1503
|
-
if (isCssSpecifier(spec)) {
|
|
1504
|
-
kept.push(line);
|
|
1505
|
-
}
|
|
1506
|
-
}
|
|
1507
|
-
return kept.join('\n');
|
|
1508
|
-
}
|
|
1509
|
-
/**
|
|
1510
|
-
* @param {string} key
|
|
1511
|
-
* @returns {string}
|
|
1512
|
-
*/
|
|
1513
|
-
function renderObjectKey(key) {
|
|
1514
|
-
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) {
|
|
1515
|
-
return key;
|
|
1516
|
-
}
|
|
1517
|
-
return JSON.stringify(key);
|
|
1518
|
-
}
|
|
1519
|
-
/**
|
|
1520
|
-
* @param {string} value
|
|
1521
|
-
* @returns {string | null}
|
|
1522
|
-
*/
|
|
1523
|
-
function deriveScopedIdentifierAlias(value) {
|
|
1524
|
-
const ident = String(value || '').trim();
|
|
1525
|
-
if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(ident)) {
|
|
1526
|
-
return null;
|
|
1527
|
-
}
|
|
1528
|
-
const parts = ident.split('_').filter(Boolean);
|
|
1529
|
-
const candidate = parts.length > 1 ? parts[parts.length - 1] : ident;
|
|
1530
|
-
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(candidate) ? candidate : ident;
|
|
1531
|
-
}
|
|
1532
|
-
/**
|
|
1533
|
-
* @param {string} source
|
|
1534
|
-
* @returns {string[]}
|
|
1535
|
-
*/
|
|
1536
|
-
function extractDeclaredIdentifiers(source) {
|
|
1537
|
-
const text = String(source || '').trim();
|
|
1538
|
-
if (!text) {
|
|
1539
|
-
return [];
|
|
1540
|
-
}
|
|
1541
|
-
const ts = loadTypeScriptApi();
|
|
1542
|
-
if (ts) {
|
|
1543
|
-
const sourceFile = ts.createSourceFile('zenith-hoisted-declaration.ts', text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
1544
|
-
const identifiers = [];
|
|
1545
|
-
const collectBindingNames = (name) => {
|
|
1546
|
-
if (ts.isIdentifier(name)) {
|
|
1547
|
-
identifiers.push(name.text);
|
|
1548
|
-
return;
|
|
1549
|
-
}
|
|
1550
|
-
if (ts.isObjectBindingPattern(name) || ts.isArrayBindingPattern(name)) {
|
|
1551
|
-
for (const element of name.elements) {
|
|
1552
|
-
if (ts.isBindingElement(element)) {
|
|
1553
|
-
collectBindingNames(element.name);
|
|
1554
|
-
}
|
|
1555
|
-
}
|
|
1556
|
-
}
|
|
1557
|
-
};
|
|
1558
|
-
for (const statement of sourceFile.statements) {
|
|
1559
|
-
if (!ts.isVariableStatement(statement)) {
|
|
1560
|
-
continue;
|
|
1561
|
-
}
|
|
1562
|
-
for (const declaration of statement.declarationList.declarations) {
|
|
1563
|
-
collectBindingNames(declaration.name);
|
|
1564
|
-
}
|
|
1565
|
-
}
|
|
1566
|
-
if (identifiers.length > 0) {
|
|
1567
|
-
return identifiers;
|
|
1568
|
-
}
|
|
1569
|
-
}
|
|
1570
|
-
const fallback = [];
|
|
1571
|
-
const match = text.match(/^\s*(?:const|let|var)\s+([\s\S]+?);?\s*$/);
|
|
1572
|
-
if (!match) {
|
|
1573
|
-
return fallback;
|
|
1574
|
-
}
|
|
1575
|
-
const declarationList = match[1];
|
|
1576
|
-
const identifierRe = /(?:^|,)\s*([A-Za-z_$][A-Za-z0-9_$]*)\s*(?::[^=,]+)?=/g;
|
|
1577
|
-
let found;
|
|
1578
|
-
while ((found = identifierRe.exec(declarationList)) !== null) {
|
|
1579
|
-
fallback.push(found[1]);
|
|
1580
|
-
}
|
|
1581
|
-
return fallback;
|
|
1582
|
-
}
|
|
1583
|
-
/**
|
|
1584
|
-
* @param {Map<string, string>} map
|
|
1585
|
-
* @param {Set<string>} ambiguous
|
|
1586
|
-
* @param {string | null} raw
|
|
1587
|
-
* @param {string | null} rewritten
|
|
1588
|
-
*/
|
|
1589
|
-
function recordScopedIdentifierRewrite(map, ambiguous, raw, rewritten) {
|
|
1590
|
-
if (typeof raw !== 'string' || raw.length === 0 || typeof rewritten !== 'string' || rewritten.length === 0) {
|
|
1591
|
-
return;
|
|
1592
|
-
}
|
|
1593
|
-
const existing = map.get(raw);
|
|
1594
|
-
if (existing && existing !== rewritten) {
|
|
1595
|
-
map.delete(raw);
|
|
1596
|
-
ambiguous.add(raw);
|
|
1597
|
-
return;
|
|
1598
|
-
}
|
|
1599
|
-
if (!ambiguous.has(raw)) {
|
|
1600
|
-
map.set(raw, rewritten);
|
|
1601
|
-
}
|
|
1602
|
-
}
|
|
1603
|
-
/**
|
|
1604
|
-
* @param {object | null | undefined} ir
|
|
1605
|
-
* @returns {{ map: Map<string, string>, ambiguous: Set<string> }}
|
|
1606
|
-
*/
|
|
1607
|
-
function buildScopedIdentifierRewrite(ir) {
|
|
1608
|
-
const out = { map: new Map(), ambiguous: new Set() };
|
|
1609
|
-
if (!ir || typeof ir !== 'object') {
|
|
1610
|
-
return out;
|
|
1611
|
-
}
|
|
1612
|
-
const stateBindings = Array.isArray(ir?.hoisted?.state) ? ir.hoisted.state : [];
|
|
1613
|
-
for (const stateEntry of stateBindings) {
|
|
1614
|
-
const key = typeof stateEntry?.key === 'string' ? stateEntry.key : null;
|
|
1615
|
-
recordScopedIdentifierRewrite(out.map, out.ambiguous, deriveScopedIdentifierAlias(key), key);
|
|
1616
|
-
}
|
|
1617
|
-
const functionBindings = Array.isArray(ir?.hoisted?.functions) ? ir.hoisted.functions : [];
|
|
1618
|
-
for (const fnName of functionBindings) {
|
|
1619
|
-
if (typeof fnName !== 'string') {
|
|
1620
|
-
continue;
|
|
1621
|
-
}
|
|
1622
|
-
recordScopedIdentifierRewrite(out.map, out.ambiguous, deriveScopedIdentifierAlias(fnName), fnName);
|
|
1623
|
-
}
|
|
1624
|
-
const declarations = Array.isArray(ir?.hoisted?.declarations) ? ir.hoisted.declarations : [];
|
|
1625
|
-
for (const declaration of declarations) {
|
|
1626
|
-
if (typeof declaration !== 'string') {
|
|
1627
|
-
continue;
|
|
1628
|
-
}
|
|
1629
|
-
for (const identifier of extractDeclaredIdentifiers(declaration)) {
|
|
1630
|
-
recordScopedIdentifierRewrite(out.map, out.ambiguous, deriveScopedIdentifierAlias(identifier), identifier);
|
|
1631
|
-
}
|
|
1632
|
-
}
|
|
1633
|
-
return out;
|
|
1634
|
-
}
|
|
1635
|
-
function rewriteIdentifiersWithinExpression(expr, scopeMap, scopeAmbiguous) {
|
|
1636
|
-
const ts = loadTypeScriptApi();
|
|
1637
|
-
if (!(scopeMap instanceof Map) || !ts) {
|
|
1638
|
-
return expr;
|
|
1639
|
-
}
|
|
1640
|
-
const wrapped = `const __zenith_expr__ = (${expr});`;
|
|
1641
|
-
let sourceFile;
|
|
1642
|
-
try {
|
|
1643
|
-
sourceFile = ts.createSourceFile('zenith-expression.ts', wrapped, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
1644
|
-
}
|
|
1645
|
-
catch {
|
|
1646
|
-
return expr;
|
|
1647
|
-
}
|
|
1648
|
-
const statement = sourceFile.statements[0];
|
|
1649
|
-
if (!statement || !ts.isVariableStatement(statement)) {
|
|
1650
|
-
return expr;
|
|
1651
|
-
}
|
|
1652
|
-
const initializer = statement.declarationList.declarations[0]?.initializer;
|
|
1653
|
-
const root = initializer && ts.isParenthesizedExpression(initializer) ? initializer.expression : initializer;
|
|
1654
|
-
if (!root) {
|
|
1655
|
-
return expr;
|
|
1656
|
-
}
|
|
1657
|
-
const replacements = [];
|
|
1658
|
-
const collectBoundNames = (name, target) => {
|
|
1659
|
-
if (ts.isIdentifier(name)) {
|
|
1660
|
-
target.add(name.text);
|
|
1661
|
-
return;
|
|
1662
|
-
}
|
|
1663
|
-
if (ts.isObjectBindingPattern(name) || ts.isArrayBindingPattern(name)) {
|
|
1664
|
-
for (const element of name.elements) {
|
|
1665
|
-
if (ts.isBindingElement(element)) {
|
|
1666
|
-
collectBoundNames(element.name, target);
|
|
1667
|
-
}
|
|
1668
|
-
}
|
|
1669
|
-
}
|
|
1670
|
-
};
|
|
1671
|
-
const shouldSkipIdentifier = (node, localBindings) => {
|
|
1672
|
-
if (localBindings.has(node.text)) {
|
|
1673
|
-
return true;
|
|
1674
|
-
}
|
|
1675
|
-
const parent = node.parent;
|
|
1676
|
-
if (!parent) {
|
|
1677
|
-
return false;
|
|
1678
|
-
}
|
|
1679
|
-
if (ts.isPropertyAccessExpression(parent) && parent.name === node) {
|
|
1680
|
-
return true;
|
|
1681
|
-
}
|
|
1682
|
-
if (ts.isPropertyAssignment(parent) && parent.name === node) {
|
|
1683
|
-
return true;
|
|
1684
|
-
}
|
|
1685
|
-
if (ts.isShorthandPropertyAssignment(parent)) {
|
|
1686
|
-
return true;
|
|
1687
|
-
}
|
|
1688
|
-
if (ts.isBindingElement(parent) && parent.name === node) {
|
|
1689
|
-
return true;
|
|
1690
|
-
}
|
|
1691
|
-
if (ts.isParameter(parent) && parent.name === node) {
|
|
1692
|
-
return true;
|
|
1693
|
-
}
|
|
1694
|
-
return false;
|
|
1695
|
-
};
|
|
1696
|
-
const visit = (node, localBindings) => {
|
|
1697
|
-
let nextBindings = localBindings;
|
|
1698
|
-
if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {
|
|
1699
|
-
nextBindings = new Set(localBindings);
|
|
1700
|
-
if (node.name && ts.isIdentifier(node.name)) {
|
|
1701
|
-
nextBindings.add(node.name.text);
|
|
1702
|
-
}
|
|
1703
|
-
for (const param of node.parameters) {
|
|
1704
|
-
collectBoundNames(param.name, nextBindings);
|
|
1705
|
-
}
|
|
1706
|
-
}
|
|
1707
|
-
if (ts.isIdentifier(node) && !shouldSkipIdentifier(node, nextBindings)) {
|
|
1708
|
-
const rewritten = scopeMap.get(node.text);
|
|
1709
|
-
if (typeof rewritten === 'string' &&
|
|
1710
|
-
rewritten.length > 0 &&
|
|
1711
|
-
rewritten !== node.text &&
|
|
1712
|
-
!(scopeAmbiguous instanceof Set && scopeAmbiguous.has(node.text))) {
|
|
1713
|
-
replacements.push({
|
|
1714
|
-
start: node.getStart(sourceFile),
|
|
1715
|
-
end: node.getEnd(),
|
|
1716
|
-
text: rewritten
|
|
1717
|
-
});
|
|
1718
|
-
}
|
|
1719
|
-
}
|
|
1720
|
-
ts.forEachChild(node, (child) => visit(child, nextBindings));
|
|
1721
|
-
};
|
|
1722
|
-
visit(root, new Set());
|
|
1723
|
-
if (replacements.length === 0) {
|
|
1724
|
-
return expr;
|
|
1725
|
-
}
|
|
1726
|
-
let rewritten = wrapped;
|
|
1727
|
-
for (const replacement of replacements.sort((a, b) => b.start - a.start)) {
|
|
1728
|
-
rewritten = `${rewritten.slice(0, replacement.start)}${replacement.text}${rewritten.slice(replacement.end)}`;
|
|
1729
|
-
}
|
|
1730
|
-
const prefix = 'const __zenith_expr__ = (';
|
|
1731
|
-
const suffix = ');';
|
|
1732
|
-
if (!rewritten.startsWith(prefix) || !rewritten.endsWith(suffix)) {
|
|
1733
|
-
return expr;
|
|
1734
|
-
}
|
|
1735
|
-
return rewritten.slice(prefix.length, rewritten.length - suffix.length);
|
|
1736
|
-
}
|
|
1737
|
-
/**
|
|
1738
|
-
* @param {string} expr
|
|
1739
|
-
* @param {{
|
|
1740
|
-
* expressionRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null,
|
|
1741
|
-
* scopeRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null
|
|
1742
|
-
* } | null} rewriteContext
|
|
1743
|
-
* @returns {string}
|
|
1744
|
-
*/
|
|
1745
|
-
function rewritePropsExpression(expr, rewriteContext = null) {
|
|
1746
|
-
const trimmed = String(expr || '').trim();
|
|
1747
|
-
if (!trimmed) {
|
|
1748
|
-
return trimmed;
|
|
1749
|
-
}
|
|
1750
|
-
const expressionMap = rewriteContext?.expressionRewrite?.map;
|
|
1751
|
-
const expressionAmbiguous = rewriteContext?.expressionRewrite?.ambiguous;
|
|
1752
|
-
if (expressionMap instanceof Map &&
|
|
1753
|
-
!(expressionAmbiguous instanceof Set && expressionAmbiguous.has(trimmed))) {
|
|
1754
|
-
const exact = expressionMap.get(trimmed);
|
|
1755
|
-
if (typeof exact === 'string' && exact.length > 0) {
|
|
1756
|
-
return exact;
|
|
1757
|
-
}
|
|
1758
|
-
}
|
|
1759
|
-
const scopeMap = rewriteContext?.scopeRewrite?.map;
|
|
1760
|
-
const scopeAmbiguous = rewriteContext?.scopeRewrite?.ambiguous;
|
|
1761
|
-
const rootMatch = trimmed.match(/^([A-Za-z_$][A-Za-z0-9_$]*)([\s\S]*)$/);
|
|
1762
|
-
if (!(scopeMap instanceof Map)) {
|
|
1763
|
-
return trimmed;
|
|
1764
|
-
}
|
|
1765
|
-
if (!rootMatch) {
|
|
1766
|
-
return rewriteIdentifiersWithinExpression(trimmed, scopeMap, scopeAmbiguous);
|
|
1767
|
-
}
|
|
1768
|
-
const root = rootMatch[1];
|
|
1769
|
-
if (scopeAmbiguous instanceof Set && scopeAmbiguous.has(root)) {
|
|
1770
|
-
return rewriteIdentifiersWithinExpression(trimmed, scopeMap, scopeAmbiguous);
|
|
1771
|
-
}
|
|
1772
|
-
const rewrittenRoot = scopeMap.get(root);
|
|
1773
|
-
if (typeof rewrittenRoot !== 'string' || rewrittenRoot.length === 0 || rewrittenRoot === root) {
|
|
1774
|
-
return rewriteIdentifiersWithinExpression(trimmed, scopeMap, scopeAmbiguous);
|
|
1775
|
-
}
|
|
1776
|
-
return `${rewrittenRoot}${rootMatch[2]}`;
|
|
1777
|
-
}
|
|
1778
|
-
/**
|
|
1779
|
-
* Translate compiler `compiled_expr` output into module-scope JS for props
|
|
1780
|
-
* preludes. Expression bindings use `signalMap.get(i)` at hydrate-time, but
|
|
1781
|
-
* component props need direct access to the already-hoisted scoped symbols.
|
|
1782
|
-
*
|
|
1783
|
-
* @param {string | null | undefined} compiledExpr
|
|
1784
|
-
* @param {{
|
|
1785
|
-
* signals?: Array<{ state_index?: number }>,
|
|
1786
|
-
* stateBindings?: Array<{ key?: string }>
|
|
1787
|
-
* } | null | undefined} expressionRewrite
|
|
1788
|
-
* @returns {string | null}
|
|
1789
|
-
*/
|
|
1790
|
-
function resolveCompiledPropsExpression(compiledExpr, expressionRewrite = null) {
|
|
1791
|
-
const source = typeof compiledExpr === 'string' ? compiledExpr.trim() : '';
|
|
1792
|
-
if (!source) {
|
|
1793
|
-
return null;
|
|
1794
|
-
}
|
|
1795
|
-
const signals = Array.isArray(expressionRewrite?.signals) ? expressionRewrite.signals : [];
|
|
1796
|
-
const stateBindings = Array.isArray(expressionRewrite?.stateBindings) ? expressionRewrite.stateBindings : [];
|
|
1797
|
-
return source.replace(/signalMap\.get\((\d+)\)(?:\.get\(\))?/g, (full, rawIndex) => {
|
|
1798
|
-
const signalIndex = Number.parseInt(rawIndex, 10);
|
|
1799
|
-
if (!Number.isInteger(signalIndex)) {
|
|
1800
|
-
return full;
|
|
1801
|
-
}
|
|
1802
|
-
const signal = signals[signalIndex];
|
|
1803
|
-
const stateIndex = signal?.state_index;
|
|
1804
|
-
const stateKey = Number.isInteger(stateIndex) ? stateBindings[stateIndex]?.key : null;
|
|
1805
|
-
if (typeof stateKey !== 'string' || stateKey.length === 0) {
|
|
1806
|
-
return full;
|
|
1807
|
-
}
|
|
1808
|
-
return full.endsWith('.get()') ? `${stateKey}.get()` : stateKey;
|
|
1809
|
-
});
|
|
1810
|
-
}
|
|
1811
|
-
/**
|
|
1812
|
-
* Resolve a raw component prop expression to the same scoped symbol/expression
|
|
1813
|
-
* contract used by the compiler rename pass.
|
|
1814
|
-
*
|
|
1815
|
-
* @param {string} expr
|
|
1816
|
-
* @param {{
|
|
1817
|
-
* expressionRewrite?: {
|
|
1818
|
-
* map?: Map<string, string>,
|
|
1819
|
-
* bindings?: Map<string, {
|
|
1820
|
-
* compiled_expr?: string | null
|
|
1821
|
-
* }>,
|
|
1822
|
-
* ambiguous?: Set<string>,
|
|
1823
|
-
* signals?: Array<{ state_index?: number }>,
|
|
1824
|
-
* stateBindings?: Array<{ key?: string }>
|
|
1825
|
-
* } | null,
|
|
1826
|
-
* scopeRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null
|
|
1827
|
-
* } | null} rewriteContext
|
|
1828
|
-
* @returns {string}
|
|
1829
|
-
*/
|
|
1830
|
-
function resolvePropsValueCode(expr, rewriteContext = null) {
|
|
1831
|
-
const trimmed = String(expr || '').trim();
|
|
1832
|
-
if (!trimmed) {
|
|
1833
|
-
return trimmed;
|
|
1834
|
-
}
|
|
1835
|
-
const expressionRewrite = rewriteContext?.expressionRewrite;
|
|
1836
|
-
const expressionAmbiguous = expressionRewrite?.ambiguous;
|
|
1837
|
-
if (!(expressionAmbiguous instanceof Set && expressionAmbiguous.has(trimmed))) {
|
|
1838
|
-
const binding = expressionRewrite?.bindings instanceof Map
|
|
1839
|
-
? expressionRewrite.bindings.get(trimmed)
|
|
1840
|
-
: null;
|
|
1841
|
-
const compiled = resolveCompiledPropsExpression(binding?.compiled_expr, expressionRewrite);
|
|
1842
|
-
if (typeof compiled === 'string' && compiled.length > 0) {
|
|
1843
|
-
return compiled;
|
|
1844
|
-
}
|
|
1845
|
-
const exact = expressionRewrite?.map instanceof Map
|
|
1846
|
-
? expressionRewrite.map.get(trimmed)
|
|
1847
|
-
: null;
|
|
1848
|
-
if (typeof exact === 'string' && exact.length > 0) {
|
|
1849
|
-
return exact;
|
|
1850
|
-
}
|
|
1851
|
-
}
|
|
1852
|
-
return rewritePropsExpression(trimmed, rewriteContext);
|
|
1853
|
-
}
|
|
1854
|
-
/**
|
|
1855
|
-
* @param {string} attrs
|
|
1856
|
-
* @param {{
|
|
1857
|
-
* expressionRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null,
|
|
1858
|
-
* scopeRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null
|
|
1859
|
-
* } | null} rewriteContext
|
|
1860
|
-
* @returns {string}
|
|
1861
|
-
*/
|
|
1862
|
-
function renderPropsLiteralFromAttrs(attrs, rewriteContext = null) {
|
|
1863
|
-
const src = String(attrs || '').trim();
|
|
1864
|
-
if (!src) {
|
|
1865
|
-
return '{}';
|
|
1866
|
-
}
|
|
1867
|
-
const entries = [];
|
|
1868
|
-
const attrRe = /([A-Za-z_$][A-Za-z0-9_$-]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|\{([\s\S]*?)\}))?/g;
|
|
1869
|
-
let match;
|
|
1870
|
-
while ((match = attrRe.exec(src)) !== null) {
|
|
1871
|
-
const rawName = match[1];
|
|
1872
|
-
if (!rawName || rawName.startsWith('on:')) {
|
|
1873
|
-
continue;
|
|
1874
|
-
}
|
|
1875
|
-
const doubleQuoted = match[2];
|
|
1876
|
-
const singleQuoted = match[3];
|
|
1877
|
-
const expressionValue = match[4];
|
|
1878
|
-
let valueCode = 'true';
|
|
1879
|
-
if (doubleQuoted !== undefined) {
|
|
1880
|
-
valueCode = JSON.stringify(doubleQuoted);
|
|
1881
|
-
}
|
|
1882
|
-
else if (singleQuoted !== undefined) {
|
|
1883
|
-
valueCode = JSON.stringify(singleQuoted);
|
|
1884
|
-
}
|
|
1885
|
-
else if (expressionValue !== undefined) {
|
|
1886
|
-
const trimmed = String(expressionValue).trim();
|
|
1887
|
-
valueCode = trimmed.length > 0 ? resolvePropsValueCode(trimmed, rewriteContext) : 'undefined';
|
|
1888
|
-
}
|
|
1889
|
-
entries.push(`${renderObjectKey(rawName)}: ${valueCode}`);
|
|
1890
|
-
}
|
|
1891
|
-
if (entries.length === 0) {
|
|
1892
|
-
return '{}';
|
|
1893
|
-
}
|
|
1894
|
-
return `{ ${entries.join(', ')} }`;
|
|
1895
|
-
}
|
|
1896
|
-
/**
|
|
1897
|
-
* @param {string} source
|
|
1898
|
-
* @param {string} attrs
|
|
1899
|
-
* @param {{
|
|
1900
|
-
* expressionRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null,
|
|
1901
|
-
* scopeRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null
|
|
1902
|
-
* } | null} rewriteContext
|
|
1903
|
-
* @returns {string}
|
|
1904
|
-
*/
|
|
1905
|
-
function injectPropsPrelude(source, attrs, rewriteContext = null) {
|
|
1906
|
-
if (typeof source !== 'string' || source.trim().length === 0) {
|
|
1907
|
-
return source;
|
|
1908
|
-
}
|
|
1909
|
-
if (!/\bprops\b/.test(source)) {
|
|
1910
|
-
return source;
|
|
1911
|
-
}
|
|
1912
|
-
if (/\b(?:const|let|var)\s+props\b/.test(source)) {
|
|
1913
|
-
return source;
|
|
1914
|
-
}
|
|
1915
|
-
const propsLiteral = renderPropsLiteralFromAttrs(attrs, rewriteContext);
|
|
1916
|
-
return `var props = ${propsLiteral};\n${source}`;
|
|
1917
|
-
}
|
|
1918
|
-
/**
|
|
1919
|
-
* @param {string} source
|
|
1920
|
-
* @returns {string}
|
|
1921
|
-
*/
|
|
1922
|
-
function deferComponentRuntimeBlock(source) {
|
|
1923
|
-
const lines = source.split('\n');
|
|
1924
|
-
const importLines = [];
|
|
1925
|
-
const bodyLines = [];
|
|
1926
|
-
let inImportPrefix = true;
|
|
1927
|
-
for (const line of lines) {
|
|
1928
|
-
if (inImportPrefix && extractStaticImportSpecifier(line)) {
|
|
1929
|
-
importLines.push(line);
|
|
1930
|
-
continue;
|
|
1931
|
-
}
|
|
1932
|
-
inImportPrefix = false;
|
|
1933
|
-
bodyLines.push(line);
|
|
1934
|
-
}
|
|
1935
|
-
const body = bodyLines.join('\n');
|
|
1936
|
-
if (body.trim().length === 0) {
|
|
1937
|
-
return importLines.join('\n');
|
|
1938
|
-
}
|
|
1939
|
-
const { hoisted, deferred } = splitDeferredRuntimeCalls(body);
|
|
1940
|
-
if (deferred.trim().length === 0) {
|
|
1941
|
-
return [importLines.join('\n').trim(), hoisted.trim()]
|
|
1942
|
-
.filter((segment) => segment.length > 0)
|
|
1943
|
-
.join('\n');
|
|
1944
|
-
}
|
|
1945
|
-
const indentedBody = deferred
|
|
1946
|
-
.trim()
|
|
1947
|
-
.split('\n')
|
|
1948
|
-
.map((line) => ` ${line}`)
|
|
1949
|
-
.join('\n');
|
|
1950
|
-
const wrapped = [
|
|
1951
|
-
importLines.join('\n').trim(),
|
|
1952
|
-
hoisted.trim(),
|
|
1953
|
-
"__zenith_component_bootstraps.push(() => {",
|
|
1954
|
-
indentedBody,
|
|
1955
|
-
"});"
|
|
1956
|
-
]
|
|
1957
|
-
.filter((segment) => segment.length > 0)
|
|
1958
|
-
.join('\n');
|
|
1959
|
-
return wrapped;
|
|
1960
|
-
}
|
|
1961
|
-
/**
|
|
1962
|
-
* Run bundler process for one page envelope.
|
|
1963
|
-
*
|
|
1964
|
-
* @param {object|object[]} envelope
|
|
1965
|
-
* @param {string} outDir
|
|
1966
|
-
* @param {string} projectRoot
|
|
1967
|
-
* @param {object | null} [logger]
|
|
1968
|
-
* @param {boolean} [showInfo]
|
|
1969
|
-
* @param {string|object} [bundlerBin]
|
|
1970
|
-
* @returns {Promise<void>}
|
|
1971
|
-
*/
|
|
1972
|
-
function runBundler(envelope, outDir, projectRoot, logger = null, showInfo = true, bundlerBin = resolveBundlerBin(projectRoot)) {
|
|
1973
|
-
return new Promise((resolvePromise, rejectPromise) => {
|
|
1974
|
-
const useStructuredLogger = Boolean(logger && typeof logger.childLine === 'function');
|
|
1975
|
-
const bundlerToolchain = bundlerBin && typeof bundlerBin === 'object'
|
|
1976
|
-
? bundlerBin
|
|
1977
|
-
: null;
|
|
1978
|
-
const bundlerCandidate = bundlerToolchain
|
|
1979
|
-
? getActiveToolchainCandidate(bundlerToolchain)
|
|
1980
|
-
: null;
|
|
1981
|
-
const bundlerPath = bundlerCandidate?.command || bundlerBin;
|
|
1982
|
-
const bundlerArgs = [
|
|
1983
|
-
...(Array.isArray(bundlerCandidate?.argsPrefix) ? bundlerCandidate.argsPrefix : []),
|
|
1984
|
-
'--out-dir',
|
|
1985
|
-
outDir
|
|
1986
|
-
];
|
|
1987
|
-
const child = spawn(bundlerPath, bundlerArgs, {
|
|
1988
|
-
cwd: projectRoot,
|
|
1989
|
-
stdio: useStructuredLogger ? ['pipe', 'pipe', 'pipe'] : ['pipe', 'inherit', 'inherit']
|
|
1990
|
-
});
|
|
1991
|
-
if (useStructuredLogger) {
|
|
1992
|
-
forwardStreamLines(child.stdout, (line) => {
|
|
1993
|
-
logger.childLine('bundler', line, { stream: 'stdout', showInfo });
|
|
1994
|
-
});
|
|
1995
|
-
forwardStreamLines(child.stderr, (line) => {
|
|
1996
|
-
logger.childLine('bundler', line, { stream: 'stderr', showInfo: true });
|
|
1997
|
-
});
|
|
1998
|
-
}
|
|
1999
|
-
child.on('error', (err) => {
|
|
2000
|
-
rejectPromise(new Error(`Bundler spawn failed: ${err.message}`));
|
|
2001
|
-
});
|
|
2002
|
-
child.on('close', (code) => {
|
|
2003
|
-
if (code === 0) {
|
|
2004
|
-
resolvePromise();
|
|
2005
|
-
return;
|
|
2006
|
-
}
|
|
2007
|
-
rejectPromise(new Error(`Bundler failed with exit code ${code}`));
|
|
2008
|
-
});
|
|
2009
|
-
child.stdin.write(JSON.stringify(envelope));
|
|
2010
|
-
child.stdin.end();
|
|
2011
|
-
});
|
|
2012
|
-
}
|
|
2013
|
-
/**
|
|
2014
|
-
* Collect generated assets for reporting.
|
|
2015
|
-
*
|
|
2016
|
-
* @param {string} rootDir
|
|
2017
|
-
* @returns {Promise<string[]>}
|
|
2018
|
-
*/
|
|
2019
|
-
async function collectAssets(rootDir) {
|
|
2020
|
-
const files = [];
|
|
2021
|
-
async function walk(dir) {
|
|
2022
|
-
let entries = [];
|
|
2023
|
-
try {
|
|
2024
|
-
entries = await readdir(dir);
|
|
2025
|
-
}
|
|
2026
|
-
catch {
|
|
2027
|
-
return;
|
|
2028
|
-
}
|
|
2029
|
-
entries.sort((a, b) => a.localeCompare(b));
|
|
2030
|
-
for (const name of entries) {
|
|
2031
|
-
const fullPath = join(dir, name);
|
|
2032
|
-
const info = await stat(fullPath);
|
|
2033
|
-
if (info.isDirectory()) {
|
|
2034
|
-
await walk(fullPath);
|
|
2035
|
-
continue;
|
|
2036
|
-
}
|
|
2037
|
-
if (fullPath.endsWith('.js') || fullPath.endsWith('.css')) {
|
|
2038
|
-
files.push(relative(rootDir, fullPath).replaceAll('\\', '/'));
|
|
2039
|
-
}
|
|
2040
|
-
}
|
|
2041
|
-
}
|
|
2042
|
-
await walk(rootDir);
|
|
2043
|
-
files.sort((a, b) => a.localeCompare(b));
|
|
2044
|
-
return files;
|
|
2045
|
-
}
|
|
2046
|
-
/**
|
|
2047
|
-
* Build all pages by orchestrating compiler and bundler binaries.
|
|
2048
|
-
*
|
|
2049
|
-
* Pipeline:
|
|
2050
|
-
* 1. Build component registry (PascalCase name → .zen file path)
|
|
2051
|
-
* 2. For each page:
|
|
2052
|
-
* a. Expand PascalCase tags into component template HTML
|
|
2053
|
-
* b. Compile expanded page source via --stdin
|
|
2054
|
-
* c. Compile each used component separately for script IR
|
|
2055
|
-
* d. Merge component IRs into page IR
|
|
2056
|
-
* 3. Send all envelopes to bundler
|
|
2057
|
-
*
|
|
2058
|
-
* @param {{ pagesDir: string, outDir: string, config?: object, logger?: object | null, showBundlerInfo?: boolean }} options
|
|
2059
38
|
* @returns {Promise<{ pages: number, assets: string[] }>}
|
|
2060
39
|
*/
|
|
2061
40
|
export async function build(options) {
|
|
2062
41
|
const { pagesDir, outDir, config = {}, logger = null, showBundlerInfo = true } = options;
|
|
42
|
+
const startupProfile = createStartupProfiler('cli-build');
|
|
2063
43
|
const projectRoot = deriveProjectRootFromPagesDir(pagesDir);
|
|
44
|
+
const srcDir = resolve(pagesDir, '..');
|
|
2064
45
|
const compilerBin = createCompilerToolchain({ projectRoot, logger });
|
|
2065
46
|
const bundlerBin = createBundlerToolchain({ projectRoot, logger });
|
|
47
|
+
const compilerTotals = createCompilerTotals();
|
|
2066
48
|
const softNavigationEnabled = config.softNavigation === true || config.router === true;
|
|
2067
49
|
const compilerOpts = {
|
|
2068
50
|
typescriptDefault: config.typescriptDefault === true,
|
|
2069
|
-
experimentalEmbeddedMarkup: config.embeddedMarkupExpressions === true
|
|
51
|
+
experimentalEmbeddedMarkup: config.embeddedMarkupExpressions === true
|
|
52
|
+
|| config.experimental?.embeddedMarkupExpressions === true,
|
|
2070
53
|
strictDomLints: config.strictDomLints === true
|
|
2071
54
|
};
|
|
2072
|
-
await rm(outDir, { recursive: true, force: true });
|
|
2073
|
-
await mkdir(outDir, { recursive: true });
|
|
2074
55
|
ensureToolchainCompatibility(bundlerBin);
|
|
2075
56
|
const resolvedBundlerCandidate = getActiveToolchainCandidate(bundlerBin);
|
|
2076
57
|
if (logger) {
|
|
2077
|
-
await maybeWarnAboutZenithVersionMismatch({
|
|
58
|
+
await startupProfile.measureAsync('version_mismatch_check', () => maybeWarnAboutZenithVersionMismatch({
|
|
2078
59
|
projectRoot,
|
|
2079
60
|
logger,
|
|
2080
61
|
command: 'build',
|
|
2081
62
|
bundlerBinPath: resolvedBundlerCandidate?.path || resolveBundlerBin(projectRoot)
|
|
2082
|
-
});
|
|
2083
|
-
}
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
onceKey: `component-registry:${registry.size}`
|
|
2092
|
-
});
|
|
2093
|
-
}
|
|
2094
|
-
else {
|
|
2095
|
-
console.log(`[zenith] Component registry: ${registry.size} components`);
|
|
2096
|
-
}
|
|
2097
|
-
}
|
|
2098
|
-
const manifest = await generateManifest(pagesDir);
|
|
2099
|
-
await ensureZenithTypeDeclarations({ manifest, pagesDir });
|
|
2100
|
-
// Cache: avoid re-compiling the same component for multiple pages
|
|
2101
|
-
/** @type {Map<string, object>} */
|
|
2102
|
-
const componentIrCache = new Map();
|
|
2103
|
-
/** @type {Map<string, boolean>} */
|
|
2104
|
-
const componentDocumentModeCache = new Map();
|
|
2105
|
-
/** @type {Map<string, { map: Map<string, string>, ambiguous: Set<string> }>} */
|
|
2106
|
-
const componentExpressionRewriteCache = new Map();
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
65
|
+
const registry = startupProfile.measureSync('build_component_registry', () => buildComponentRegistry(srcDir));
|
|
66
|
+
void RUNTIME_MARKUP_BINDING;
|
|
67
|
+
const manifest = await startupProfile.measureAsync('generate_manifest', () => generateManifest(pagesDir));
|
|
68
|
+
await startupProfile.measureAsync('ensure_zenith_type_declarations', () => ensureZenithTypeDeclarations({
|
|
69
|
+
manifest,
|
|
70
|
+
pagesDir
|
|
71
|
+
}));
|
|
2107
72
|
const emitCompilerWarning = createCompilerWarningEmitter((line) => {
|
|
2108
73
|
if (logger && typeof logger.warn === 'function') {
|
|
2109
74
|
logger.warn(line, { onceKey: `compiler-warning:${line}` });
|
|
@@ -2111,190 +76,38 @@ export async function build(options) {
|
|
|
2111
76
|
}
|
|
2112
77
|
console.warn(line);
|
|
2113
78
|
});
|
|
2114
|
-
const envelopes =
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
if (!adjacentLoad && existsSync(`${baseName}.load${ext}`))
|
|
2127
|
-
adjacentLoad = `${baseName}.load${ext}`;
|
|
2128
|
-
}
|
|
2129
|
-
// 2a. Expand PascalCase component tags
|
|
2130
|
-
const { expandedSource } = expandComponents(rawSource, registry, sourceFile);
|
|
2131
|
-
const extractedServer = extractServerScript(expandedSource, sourceFile, compilerOpts);
|
|
2132
|
-
const compileSource = extractedServer.source;
|
|
2133
|
-
// 2b. Compile expanded page source via --stdin
|
|
2134
|
-
const pageIr = runCompiler(sourceFile, compileSource, compilerOpts, {
|
|
2135
|
-
compilerToolchain: compilerBin,
|
|
2136
|
-
onWarning: emitCompilerWarning
|
|
2137
|
-
});
|
|
2138
|
-
const hasGuard = (extractedServer.serverScript && extractedServer.serverScript.has_guard) || adjacentGuard !== null;
|
|
2139
|
-
const hasLoad = (extractedServer.serverScript && extractedServer.serverScript.has_load) || adjacentLoad !== null;
|
|
2140
|
-
if (extractedServer.serverScript) {
|
|
2141
|
-
pageIr.server_script = extractedServer.serverScript;
|
|
2142
|
-
pageIr.prerender = extractedServer.serverScript.prerender === true;
|
|
2143
|
-
if (pageIr.ssr_data === undefined) {
|
|
2144
|
-
pageIr.ssr_data = null;
|
|
2145
|
-
}
|
|
2146
|
-
}
|
|
2147
|
-
// Static Build Route Protection Policy
|
|
2148
|
-
if (pageIr.prerender === true && (hasGuard || hasLoad)) {
|
|
2149
|
-
throw new Error(`[zenith] Build failed for ${entry.file}: protected routes require SSR/runtime. ` +
|
|
2150
|
-
`Cannot prerender a static route with a \`guard\` or \`load\` function.`);
|
|
2151
|
-
}
|
|
2152
|
-
// Apply metadata to IR
|
|
2153
|
-
pageIr.has_guard = hasGuard;
|
|
2154
|
-
pageIr.has_load = hasLoad;
|
|
2155
|
-
pageIr.guard_module_ref = adjacentGuard ? relative(srcDir, adjacentGuard).replaceAll('\\', '/') : null;
|
|
2156
|
-
pageIr.load_module_ref = adjacentLoad ? relative(srcDir, adjacentLoad).replaceAll('\\', '/') : null;
|
|
2157
|
-
// Ensure IR has required array fields for merging
|
|
2158
|
-
pageIr.components_scripts = pageIr.components_scripts || {};
|
|
2159
|
-
pageIr.component_instances = pageIr.component_instances || [];
|
|
2160
|
-
pageIr.signals = Array.isArray(pageIr.signals) ? pageIr.signals : [];
|
|
2161
|
-
pageIr.hoisted = pageIr.hoisted || { imports: [], declarations: [], functions: [], signals: [], state: [], code: [] };
|
|
2162
|
-
pageIr.hoisted.imports = pageIr.hoisted.imports || [];
|
|
2163
|
-
pageIr.hoisted.declarations = pageIr.hoisted.declarations || [];
|
|
2164
|
-
pageIr.hoisted.functions = pageIr.hoisted.functions || [];
|
|
2165
|
-
pageIr.hoisted.signals = pageIr.hoisted.signals || [];
|
|
2166
|
-
pageIr.hoisted.state = pageIr.hoisted.state || [];
|
|
2167
|
-
pageIr.hoisted.code = pageIr.hoisted.code || [];
|
|
2168
|
-
const seenStaticImports = new Set();
|
|
2169
|
-
const occurrenceCountByPath = new Map();
|
|
2170
|
-
for (const occurrence of componentOccurrences) {
|
|
2171
|
-
const key = occurrence.componentPath || occurrence.name;
|
|
2172
|
-
occurrenceCountByPath.set(key, (occurrenceCountByPath.get(key) || 0) + 1);
|
|
2173
|
-
}
|
|
2174
|
-
const pageExpressionRewriteMap = new Map();
|
|
2175
|
-
const pageExpressionBindingMap = new Map();
|
|
2176
|
-
const pageAmbiguousExpressionMap = new Set();
|
|
2177
|
-
const knownRefKeys = new Set();
|
|
2178
|
-
const componentOccurrencePlans = [];
|
|
2179
|
-
const pageOwnerIr = componentOccurrences.length > 0
|
|
2180
|
-
? runCompiler(sourceFile, pageOwnerSource, compilerOpts, {
|
|
2181
|
-
suppressWarnings: true,
|
|
2182
|
-
compilerToolchain: compilerBin
|
|
2183
|
-
})
|
|
2184
|
-
: null;
|
|
2185
|
-
const pageOwnerExpressionRewrite = pageOwnerIr
|
|
2186
|
-
? buildComponentExpressionRewrite(sourceFile, pageOwnerSource, pageOwnerIr, compilerOpts, compilerBin)
|
|
2187
|
-
: { map: new Map(), bindings: new Map(), signals: [], stateBindings: [], ambiguous: new Set(), sequence: [] };
|
|
2188
|
-
const pageOwnerScopeRewrite = pageOwnerIr
|
|
2189
|
-
? buildScopedIdentifierRewrite(pageOwnerIr)
|
|
2190
|
-
: { map: new Map(), ambiguous: new Set() };
|
|
2191
|
-
const pageSelfExpressionRewrite = buildComponentExpressionRewrite(sourceFile, compileSource, pageIr, compilerOpts, compilerBin);
|
|
2192
|
-
mergeExpressionRewriteMaps(pageExpressionRewriteMap, pageExpressionBindingMap, pageAmbiguousExpressionMap, pageSelfExpressionRewrite, pageIr);
|
|
2193
|
-
const componentScopeRewriteCache = new Map();
|
|
2194
|
-
let componentInstanceCounter = 0;
|
|
2195
|
-
// 2c. Compile each used component separately for its script IR
|
|
2196
|
-
for (const occurrence of componentOccurrences) {
|
|
2197
|
-
const compName = occurrence.name;
|
|
2198
|
-
const compPath = occurrence.componentPath || registry.get(compName);
|
|
2199
|
-
if (!compPath)
|
|
2200
|
-
continue;
|
|
2201
|
-
const componentSource = readFileSync(compPath, 'utf8');
|
|
2202
|
-
const occurrenceCount = occurrenceCountByPath.get(compPath) || 0;
|
|
2203
|
-
let compIr;
|
|
2204
|
-
if (componentIrCache.has(compPath)) {
|
|
2205
|
-
compIr = componentIrCache.get(compPath);
|
|
2206
|
-
}
|
|
2207
|
-
else {
|
|
2208
|
-
const componentCompileSource = stripStyleBlocks(componentSource);
|
|
2209
|
-
compIr = runCompiler(compPath, componentCompileSource, compilerOpts, {
|
|
2210
|
-
compilerToolchain: compilerBin,
|
|
2211
|
-
onWarning: emitCompilerWarning
|
|
2212
|
-
});
|
|
2213
|
-
componentIrCache.set(compPath, compIr);
|
|
2214
|
-
}
|
|
2215
|
-
let isDocMode = componentDocumentModeCache.get(compPath);
|
|
2216
|
-
if (isDocMode === undefined) {
|
|
2217
|
-
isDocMode = isDocumentMode(extractTemplate(componentSource));
|
|
2218
|
-
componentDocumentModeCache.set(compPath, isDocMode);
|
|
2219
|
-
}
|
|
2220
|
-
let expressionRewrite = componentExpressionRewriteCache.get(compPath);
|
|
2221
|
-
if (!expressionRewrite) {
|
|
2222
|
-
expressionRewrite = buildComponentExpressionRewrite(compPath, componentSource, compIr, compilerOpts, compilerBin);
|
|
2223
|
-
componentExpressionRewriteCache.set(compPath, expressionRewrite);
|
|
2224
|
-
}
|
|
2225
|
-
let attrExpressionRewrite = pageOwnerExpressionRewrite;
|
|
2226
|
-
let attrScopeRewrite = pageOwnerScopeRewrite;
|
|
2227
|
-
const ownerPath = typeof occurrence.ownerPath === 'string' && occurrence.ownerPath.length > 0
|
|
2228
|
-
? occurrence.ownerPath
|
|
2229
|
-
: sourceFile;
|
|
2230
|
-
if (ownerPath !== sourceFile) {
|
|
2231
|
-
let ownerIr = componentIrCache.get(ownerPath);
|
|
2232
|
-
if (!ownerIr) {
|
|
2233
|
-
const ownerSource = readFileSync(ownerPath, 'utf8');
|
|
2234
|
-
ownerIr = runCompiler(ownerPath, stripStyleBlocks(ownerSource), compilerOpts, {
|
|
2235
|
-
compilerToolchain: compilerBin,
|
|
2236
|
-
onWarning: emitCompilerWarning
|
|
2237
|
-
});
|
|
2238
|
-
componentIrCache.set(ownerPath, ownerIr);
|
|
2239
|
-
}
|
|
2240
|
-
attrExpressionRewrite = componentExpressionRewriteCache.get(ownerPath);
|
|
2241
|
-
if (!attrExpressionRewrite) {
|
|
2242
|
-
const ownerSource = readFileSync(ownerPath, 'utf8');
|
|
2243
|
-
attrExpressionRewrite = buildComponentExpressionRewrite(ownerPath, ownerSource, ownerIr, compilerOpts, compilerBin);
|
|
2244
|
-
componentExpressionRewriteCache.set(ownerPath, attrExpressionRewrite);
|
|
2245
|
-
}
|
|
2246
|
-
attrScopeRewrite = componentScopeRewriteCache.get(ownerPath);
|
|
2247
|
-
if (!attrScopeRewrite) {
|
|
2248
|
-
attrScopeRewrite = buildScopedIdentifierRewrite(ownerIr);
|
|
2249
|
-
componentScopeRewriteCache.set(ownerPath, attrScopeRewrite);
|
|
2250
|
-
}
|
|
2251
|
-
}
|
|
2252
|
-
const useIsolatedInstance = occurrenceCount > 1;
|
|
2253
|
-
const { ir: instanceIr, refIdentifierPairs } = useIsolatedInstance
|
|
2254
|
-
? cloneComponentIrForInstance(compIr, componentInstanceCounter++, extractDeclaredIdentifiers, resolveStateKeyFromBindings)
|
|
2255
|
-
: { ir: compIr, refIdentifierPairs: [] };
|
|
2256
|
-
const instanceRewrite = useIsolatedInstance
|
|
2257
|
-
? buildComponentExpressionRewrite(compPath, componentSource, instanceIr, compilerOpts, compilerBin)
|
|
2258
|
-
: expressionRewrite;
|
|
2259
|
-
// 2d. Merge component IR into page IR
|
|
2260
|
-
mergeComponentIr(pageIr, instanceIr, compPath, sourceFile, {
|
|
2261
|
-
includeCode: true,
|
|
2262
|
-
cssImportsOnly: isDocMode,
|
|
2263
|
-
documentMode: isDocMode,
|
|
2264
|
-
componentAttrs: typeof occurrence.attrs === 'string' ? occurrence.attrs : '',
|
|
2265
|
-
componentAttrsRewrite: {
|
|
2266
|
-
expressionRewrite: attrExpressionRewrite,
|
|
2267
|
-
scopeRewrite: attrScopeRewrite
|
|
2268
|
-
}
|
|
2269
|
-
}, seenStaticImports, knownRefKeys);
|
|
2270
|
-
if (useIsolatedInstance) {
|
|
2271
|
-
componentOccurrencePlans.push({
|
|
2272
|
-
rewrite: instanceRewrite,
|
|
2273
|
-
expressionSequence: instanceRewrite.sequence,
|
|
2274
|
-
refSequence: refIdentifierPairs
|
|
2275
|
-
});
|
|
2276
|
-
}
|
|
2277
|
-
else {
|
|
2278
|
-
mergeExpressionRewriteMaps(pageExpressionRewriteMap, pageExpressionBindingMap, pageAmbiguousExpressionMap, expressionRewrite, pageIr);
|
|
2279
|
-
}
|
|
2280
|
-
}
|
|
2281
|
-
applyOccurrenceRewritePlans(pageIr, componentOccurrencePlans, (rewrite, binding) => resolveRewrittenBindingMetadata(pageIr, rewrite, binding));
|
|
2282
|
-
applyExpressionRewrites(pageIr, pageExpressionRewriteMap, pageExpressionBindingMap, pageAmbiguousExpressionMap);
|
|
2283
|
-
applyScopedIdentifierRewrites(pageIr, buildScopedIdentifierRewrite(pageIr));
|
|
2284
|
-
synthesizeSignalBackedCompiledExpressions(pageIr);
|
|
2285
|
-
normalizeExpressionBindingDependencies(pageIr);
|
|
2286
|
-
rewriteLegacyMarkupIdentifiers(pageIr);
|
|
2287
|
-
rewriteRefBindingIdentifiers(pageIr, knownRefKeys);
|
|
2288
|
-
envelopes.push({
|
|
2289
|
-
route: entry.path,
|
|
2290
|
-
file: sourceFile,
|
|
2291
|
-
ir: pageIr,
|
|
2292
|
-
router: softNavigationEnabled
|
|
2293
|
-
});
|
|
2294
|
-
}
|
|
79
|
+
const { envelopes, expressionRewriteMetrics } = await buildPageEnvelopes({
|
|
80
|
+
manifest,
|
|
81
|
+
pagesDir,
|
|
82
|
+
srcDir,
|
|
83
|
+
registry,
|
|
84
|
+
compilerOpts,
|
|
85
|
+
compilerBin,
|
|
86
|
+
softNavigationEnabled,
|
|
87
|
+
startupProfile,
|
|
88
|
+
compilerTotals,
|
|
89
|
+
emitCompilerWarning
|
|
90
|
+
});
|
|
2295
91
|
if (envelopes.length > 0) {
|
|
2296
|
-
await runBundler(envelopes, outDir, projectRoot, logger, showBundlerInfo, bundlerBin);
|
|
2297
|
-
}
|
|
2298
|
-
const
|
|
92
|
+
await startupProfile.measureAsync('run_bundler', () => runBundler(envelopes, outDir, projectRoot, logger, showBundlerInfo, bundlerBin), { envelopes: envelopes.length });
|
|
93
|
+
}
|
|
94
|
+
const { manifest: imageManifest } = await startupProfile.measureAsync('build_image_artifacts', () => buildImageArtifacts({
|
|
95
|
+
projectRoot,
|
|
96
|
+
outDir,
|
|
97
|
+
config: config.images
|
|
98
|
+
}));
|
|
99
|
+
const imageRuntimePayload = createImageRuntimePayload(config.images, imageManifest, 'passthrough');
|
|
100
|
+
await startupProfile.measureAsync('materialize_image_markup', () => materializeImageMarkupInHtmlFiles({
|
|
101
|
+
distDir: outDir,
|
|
102
|
+
payload: imageRuntimePayload
|
|
103
|
+
}));
|
|
104
|
+
await startupProfile.measureAsync('inject_image_runtime_payload', () => injectImageRuntimePayloadIntoHtmlFiles(outDir, imageRuntimePayload));
|
|
105
|
+
const assets = await startupProfile.measureAsync('collect_assets', () => collectAssets(outDir));
|
|
106
|
+
startupProfile.emit('build_complete', {
|
|
107
|
+
pages: manifest.length,
|
|
108
|
+
assets: assets.length,
|
|
109
|
+
compilerTotals,
|
|
110
|
+
expressionRewriteMetrics
|
|
111
|
+
});
|
|
2299
112
|
return { pages: manifest.length, assets };
|
|
2300
113
|
}
|