@zenithbuild/cli 0.6.0 → 0.6.3
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.js +749 -47
- package/dist/dev-server.js +61 -14
- package/dist/index.js +13 -13
- package/dist/preview.js +13 -5
- package/dist/ui/env.js +17 -1
- package/dist/ui/format.js +80 -42
- package/dist/ui/logger.js +239 -74
- package/package.json +3 -2
package/dist/build.js
CHANGED
|
@@ -92,6 +92,35 @@ export function createCompilerWarningEmitter(sink = (line) => console.warn(line)
|
|
|
92
92
|
};
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Forward child-process output line-by-line through the structured logger.
|
|
97
|
+
*
|
|
98
|
+
* @param {import('node:stream').Readable | null | undefined} stream
|
|
99
|
+
* @param {(line: string) => void} onLine
|
|
100
|
+
*/
|
|
101
|
+
function forwardStreamLines(stream, onLine) {
|
|
102
|
+
if (!stream || typeof stream.on !== 'function') {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
let pending = '';
|
|
106
|
+
stream.setEncoding?.('utf8');
|
|
107
|
+
stream.on('data', (chunk) => {
|
|
108
|
+
pending += String(chunk || '');
|
|
109
|
+
const lines = pending.split(/\r?\n/);
|
|
110
|
+
pending = lines.pop() || '';
|
|
111
|
+
for (const line of lines) {
|
|
112
|
+
if (line.trim().length > 0) {
|
|
113
|
+
onLine(line);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
stream.on('end', () => {
|
|
118
|
+
if (pending.trim().length > 0) {
|
|
119
|
+
onLine(pending);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
95
124
|
/**
|
|
96
125
|
* Run the compiler process and parse its JSON stdout.
|
|
97
126
|
*
|
|
@@ -171,11 +200,31 @@ function stripStyleBlocks(source) {
|
|
|
171
200
|
* @param {string} compPath
|
|
172
201
|
* @param {string} componentSource
|
|
173
202
|
* @param {object} compIr
|
|
174
|
-
* @returns {{
|
|
203
|
+
* @returns {{
|
|
204
|
+
* map: Map<string, string>,
|
|
205
|
+
* bindings: Map<string, {
|
|
206
|
+
* compiled_expr: string | null,
|
|
207
|
+
* signal_index: number | null,
|
|
208
|
+
* signal_indices: number[],
|
|
209
|
+
* state_index: number | null,
|
|
210
|
+
* component_instance: string | null,
|
|
211
|
+
* component_binding: string | null
|
|
212
|
+
* }>,
|
|
213
|
+
* signals: Array<{ id?: number, kind?: string, state_index?: number }>,
|
|
214
|
+
* stateBindings: Array<{ key?: string, value?: string }>,
|
|
215
|
+
* ambiguous: Set<string>
|
|
216
|
+
* }}
|
|
175
217
|
*/
|
|
176
218
|
function buildComponentExpressionRewrite(compPath, componentSource, compIr, compilerOpts) {
|
|
177
|
-
const out = {
|
|
219
|
+
const out = {
|
|
220
|
+
map: new Map(),
|
|
221
|
+
bindings: new Map(),
|
|
222
|
+
signals: Array.isArray(compIr?.signals) ? compIr.signals : [],
|
|
223
|
+
stateBindings: Array.isArray(compIr?.hoisted?.state) ? compIr.hoisted.state : [],
|
|
224
|
+
ambiguous: new Set()
|
|
225
|
+
};
|
|
178
226
|
const rewrittenExpressions = Array.isArray(compIr?.expressions) ? compIr.expressions : [];
|
|
227
|
+
const rewrittenBindings = Array.isArray(compIr?.expression_bindings) ? compIr.expression_bindings : [];
|
|
179
228
|
if (rewrittenExpressions.length === 0) {
|
|
180
229
|
return out;
|
|
181
230
|
}
|
|
@@ -200,34 +249,243 @@ function buildComponentExpressionRewrite(compPath, componentSource, compIr, comp
|
|
|
200
249
|
if (typeof raw !== 'string' || typeof rewritten !== 'string') {
|
|
201
250
|
continue;
|
|
202
251
|
}
|
|
203
|
-
|
|
204
|
-
|
|
252
|
+
|
|
253
|
+
const binding = rewrittenBindings[i];
|
|
254
|
+
const normalizedBinding = binding && typeof binding === 'object'
|
|
255
|
+
? {
|
|
256
|
+
compiled_expr: typeof binding.compiled_expr === 'string' ? binding.compiled_expr : null,
|
|
257
|
+
signal_index: Number.isInteger(binding.signal_index) ? binding.signal_index : null,
|
|
258
|
+
signal_indices: Array.isArray(binding.signal_indices)
|
|
259
|
+
? binding.signal_indices.filter((value) => Number.isInteger(value))
|
|
260
|
+
: [],
|
|
261
|
+
state_index: Number.isInteger(binding.state_index) ? binding.state_index : null,
|
|
262
|
+
component_instance: typeof binding.component_instance === 'string' ? binding.component_instance : null,
|
|
263
|
+
component_binding: typeof binding.component_binding === 'string' ? binding.component_binding : null
|
|
264
|
+
}
|
|
265
|
+
: null;
|
|
266
|
+
|
|
267
|
+
if (!out.ambiguous.has(raw) && normalizedBinding) {
|
|
268
|
+
const existingBinding = out.bindings.get(raw);
|
|
269
|
+
if (existingBinding) {
|
|
270
|
+
if (JSON.stringify(existingBinding) !== JSON.stringify(normalizedBinding)) {
|
|
271
|
+
out.bindings.delete(raw);
|
|
272
|
+
out.map.delete(raw);
|
|
273
|
+
out.ambiguous.add(raw);
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
} else {
|
|
277
|
+
out.bindings.set(raw, normalizedBinding);
|
|
278
|
+
}
|
|
205
279
|
}
|
|
206
|
-
|
|
207
|
-
if (
|
|
208
|
-
out.map.
|
|
209
|
-
|
|
280
|
+
|
|
281
|
+
if (raw !== rewritten) {
|
|
282
|
+
const existing = out.map.get(raw);
|
|
283
|
+
if (existing && existing !== rewritten) {
|
|
284
|
+
out.bindings.delete(raw);
|
|
285
|
+
out.map.delete(raw);
|
|
286
|
+
out.ambiguous.add(raw);
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (!out.ambiguous.has(raw)) {
|
|
290
|
+
out.map.set(raw, rewritten);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return out;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function remapCompiledExpressionSignals(compiledExpr, componentSignals, componentStateBindings, pageSignalIndexByStateKey) {
|
|
299
|
+
if (typeof compiledExpr !== 'string' || compiledExpr.length === 0) {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return compiledExpr.replace(/signalMap\.get\((\d+)\)/g, (full, rawIndex) => {
|
|
304
|
+
const localIndex = Number.parseInt(rawIndex, 10);
|
|
305
|
+
if (!Number.isInteger(localIndex)) {
|
|
306
|
+
return full;
|
|
307
|
+
}
|
|
308
|
+
const signal = componentSignals[localIndex];
|
|
309
|
+
if (!signal || !Number.isInteger(signal.state_index)) {
|
|
310
|
+
return full;
|
|
311
|
+
}
|
|
312
|
+
const stateKey = componentStateBindings[signal.state_index]?.key;
|
|
313
|
+
if (typeof stateKey !== 'string' || stateKey.length === 0) {
|
|
314
|
+
return full;
|
|
315
|
+
}
|
|
316
|
+
const pageIndex = pageSignalIndexByStateKey.get(stateKey);
|
|
317
|
+
if (!Number.isInteger(pageIndex)) {
|
|
318
|
+
return full;
|
|
319
|
+
}
|
|
320
|
+
return `signalMap.get(${pageIndex})`;
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function resolveRewrittenBindingMetadata(pageIr, componentRewrite, binding) {
|
|
325
|
+
if (!binding || typeof binding !== 'object') {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const pageStateBindings = Array.isArray(pageIr?.hoisted?.state) ? pageIr.hoisted.state : [];
|
|
330
|
+
const pageSignals = Array.isArray(pageIr?.hoisted?.signals) ? pageIr.hoisted.signals : [];
|
|
331
|
+
const pageStateIndexByKey = new Map();
|
|
332
|
+
const pageSignalIndexByStateKey = new Map();
|
|
333
|
+
|
|
334
|
+
for (let index = 0; index < pageStateBindings.length; index++) {
|
|
335
|
+
const key = pageStateBindings[index]?.key;
|
|
336
|
+
if (typeof key === 'string' && key.length > 0 && !pageStateIndexByKey.has(key)) {
|
|
337
|
+
pageStateIndexByKey.set(key, index);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
for (let index = 0; index < pageSignals.length; index++) {
|
|
342
|
+
const stateIndex = pageSignals[index]?.state_index;
|
|
343
|
+
if (!Number.isInteger(stateIndex)) {
|
|
210
344
|
continue;
|
|
211
345
|
}
|
|
212
|
-
|
|
213
|
-
|
|
346
|
+
const stateKey = pageStateBindings[stateIndex]?.key;
|
|
347
|
+
if (typeof stateKey === 'string' && stateKey.length > 0 && !pageSignalIndexByStateKey.has(stateKey)) {
|
|
348
|
+
pageSignalIndexByStateKey.set(stateKey, index);
|
|
214
349
|
}
|
|
215
350
|
}
|
|
216
351
|
|
|
217
|
-
|
|
352
|
+
const componentSignals = Array.isArray(componentRewrite?.signals) ? componentRewrite.signals : [];
|
|
353
|
+
const componentStateBindings = Array.isArray(componentRewrite?.stateBindings) ? componentRewrite.stateBindings : [];
|
|
354
|
+
|
|
355
|
+
let signalIndices = Array.isArray(binding.signal_indices)
|
|
356
|
+
? [...new Set(
|
|
357
|
+
binding.signal_indices
|
|
358
|
+
.map((signalIndex) => {
|
|
359
|
+
if (!Number.isInteger(signalIndex)) {
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
const signal = componentSignals[signalIndex];
|
|
363
|
+
if (!signal || !Number.isInteger(signal.state_index)) {
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
const stateKey = componentStateBindings[signal.state_index]?.key;
|
|
367
|
+
if (typeof stateKey !== 'string' || stateKey.length === 0) {
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
const pageIndex = pageSignalIndexByStateKey.get(stateKey);
|
|
371
|
+
return Number.isInteger(pageIndex) ? pageIndex : null;
|
|
372
|
+
})
|
|
373
|
+
.filter((value) => Number.isInteger(value))
|
|
374
|
+
)].sort((a, b) => a - b)
|
|
375
|
+
: [];
|
|
376
|
+
|
|
377
|
+
let signalIndex = null;
|
|
378
|
+
if (Number.isInteger(binding.signal_index)) {
|
|
379
|
+
const signal = componentSignals[binding.signal_index];
|
|
380
|
+
const stateKey = signal && Number.isInteger(signal.state_index)
|
|
381
|
+
? componentStateBindings[signal.state_index]?.key
|
|
382
|
+
: null;
|
|
383
|
+
const pageIndex = typeof stateKey === 'string' ? pageSignalIndexByStateKey.get(stateKey) : null;
|
|
384
|
+
signalIndex = Number.isInteger(pageIndex) ? pageIndex : null;
|
|
385
|
+
}
|
|
386
|
+
if (signalIndex === null && signalIndices.length === 1) {
|
|
387
|
+
signalIndex = signalIndices[0];
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
let stateIndex = null;
|
|
391
|
+
if (Number.isInteger(binding.state_index)) {
|
|
392
|
+
const stateKey = componentStateBindings[binding.state_index]?.key;
|
|
393
|
+
const pageIndex = typeof stateKey === 'string' ? pageStateIndexByKey.get(stateKey) : null;
|
|
394
|
+
stateIndex = Number.isInteger(pageIndex) ? pageIndex : null;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (Number.isInteger(stateIndex)) {
|
|
398
|
+
const fallbackSignalIndices = pageSignals
|
|
399
|
+
.map((signal, index) => signal?.state_index === stateIndex ? index : null)
|
|
400
|
+
.filter((value) => Number.isInteger(value));
|
|
401
|
+
const signalIndicesMatchState = signalIndices.every(
|
|
402
|
+
(index) => pageSignals[index]?.state_index === stateIndex
|
|
403
|
+
);
|
|
404
|
+
if ((!signalIndicesMatchState || signalIndices.length === 0) && fallbackSignalIndices.length > 0) {
|
|
405
|
+
signalIndices = fallbackSignalIndices;
|
|
406
|
+
}
|
|
407
|
+
if (
|
|
408
|
+
(signalIndex === null || pageSignals[signalIndex]?.state_index !== stateIndex) &&
|
|
409
|
+
fallbackSignalIndices.length === 1
|
|
410
|
+
) {
|
|
411
|
+
signalIndex = fallbackSignalIndices[0];
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
let compiledExpr = remapCompiledExpressionSignals(
|
|
416
|
+
binding.compiled_expr,
|
|
417
|
+
componentSignals,
|
|
418
|
+
componentStateBindings,
|
|
419
|
+
pageSignalIndexByStateKey
|
|
420
|
+
);
|
|
421
|
+
if (
|
|
422
|
+
typeof compiledExpr === 'string' &&
|
|
423
|
+
signalIndices.length === 1 &&
|
|
424
|
+
Array.isArray(binding.signal_indices) &&
|
|
425
|
+
binding.signal_indices.length <= 1
|
|
426
|
+
) {
|
|
427
|
+
compiledExpr = compiledExpr.replace(/signalMap\.get\(\d+\)/g, `signalMap.get(${signalIndices[0]})`);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return {
|
|
431
|
+
compiled_expr: compiledExpr,
|
|
432
|
+
signal_index: signalIndex,
|
|
433
|
+
signal_indices: signalIndices,
|
|
434
|
+
state_index: stateIndex,
|
|
435
|
+
component_instance: typeof binding.component_instance === 'string' ? binding.component_instance : null,
|
|
436
|
+
component_binding: typeof binding.component_binding === 'string' ? binding.component_binding : null
|
|
437
|
+
};
|
|
218
438
|
}
|
|
219
439
|
|
|
220
440
|
/**
|
|
221
441
|
* Merge a per-component rewrite table into the page-level rewrite table.
|
|
222
442
|
*
|
|
223
443
|
* @param {Map<string, string>} pageMap
|
|
444
|
+
* @param {Map<string, {
|
|
445
|
+
* compiled_expr: string | null,
|
|
446
|
+
* signal_index: number | null,
|
|
447
|
+
* signal_indices: number[],
|
|
448
|
+
* state_index: number | null,
|
|
449
|
+
* component_instance: string | null,
|
|
450
|
+
* component_binding: string | null
|
|
451
|
+
* }>} pageBindingMap
|
|
224
452
|
* @param {Set<string>} pageAmbiguous
|
|
225
|
-
* @param {{
|
|
453
|
+
* @param {{
|
|
454
|
+
* map: Map<string, string>,
|
|
455
|
+
* bindings: Map<string, {
|
|
456
|
+
* compiled_expr: string | null,
|
|
457
|
+
* signal_index: number | null,
|
|
458
|
+
* signal_indices: number[],
|
|
459
|
+
* state_index: number | null,
|
|
460
|
+
* component_instance: string | null,
|
|
461
|
+
* component_binding: string | null
|
|
462
|
+
* }>,
|
|
463
|
+
* signals: Array<{ id?: number, kind?: string, state_index?: number }>,
|
|
464
|
+
* stateBindings: Array<{ key?: string, value?: string }>,
|
|
465
|
+
* ambiguous: Set<string>
|
|
466
|
+
* }} componentRewrite
|
|
467
|
+
* @param {object} pageIr
|
|
226
468
|
*/
|
|
227
|
-
function mergeExpressionRewriteMaps(pageMap, pageAmbiguous, componentRewrite) {
|
|
469
|
+
function mergeExpressionRewriteMaps(pageMap, pageBindingMap, pageAmbiguous, componentRewrite, pageIr) {
|
|
228
470
|
for (const raw of componentRewrite.ambiguous) {
|
|
229
471
|
pageAmbiguous.add(raw);
|
|
230
472
|
pageMap.delete(raw);
|
|
473
|
+
pageBindingMap.delete(raw);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
for (const [raw, binding] of componentRewrite.bindings.entries()) {
|
|
477
|
+
if (pageAmbiguous.has(raw)) {
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
const resolved = resolveRewrittenBindingMetadata(pageIr, componentRewrite, binding);
|
|
481
|
+
const existing = pageBindingMap.get(raw);
|
|
482
|
+
if (existing && JSON.stringify(existing) !== JSON.stringify(resolved)) {
|
|
483
|
+
pageAmbiguous.add(raw);
|
|
484
|
+
pageMap.delete(raw);
|
|
485
|
+
pageBindingMap.delete(raw);
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
pageBindingMap.set(raw, resolved);
|
|
231
489
|
}
|
|
232
490
|
|
|
233
491
|
for (const [raw, rewritten] of componentRewrite.map.entries()) {
|
|
@@ -238,20 +496,80 @@ function mergeExpressionRewriteMaps(pageMap, pageAmbiguous, componentRewrite) {
|
|
|
238
496
|
if (existing && existing !== rewritten) {
|
|
239
497
|
pageAmbiguous.add(raw);
|
|
240
498
|
pageMap.delete(raw);
|
|
499
|
+
pageBindingMap.delete(raw);
|
|
241
500
|
continue;
|
|
242
501
|
}
|
|
243
502
|
pageMap.set(raw, rewritten);
|
|
244
503
|
}
|
|
245
504
|
}
|
|
246
505
|
|
|
506
|
+
function resolveStateKeyFromBindings(identifier, stateBindings, preferredKeys = null) {
|
|
507
|
+
const ident = String(identifier || '').trim();
|
|
508
|
+
if (!ident) {
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const exact = stateBindings.find((entry) => String(entry?.key || '') === ident);
|
|
513
|
+
if (exact && typeof exact.key === 'string') {
|
|
514
|
+
return exact.key;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const suffix = `_${ident}`;
|
|
518
|
+
const matches = stateBindings
|
|
519
|
+
.map((entry) => String(entry?.key || ''))
|
|
520
|
+
.filter((key) => key.endsWith(suffix));
|
|
521
|
+
|
|
522
|
+
if (preferredKeys instanceof Set && preferredKeys.size > 0) {
|
|
523
|
+
const preferredMatches = matches.filter((key) => preferredKeys.has(key));
|
|
524
|
+
if (preferredMatches.length === 1) {
|
|
525
|
+
return preferredMatches[0];
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (matches.length === 1) {
|
|
530
|
+
return matches[0];
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
return null;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function rewriteRefBindingIdentifiers(pageIr, preferredKeys = null) {
|
|
537
|
+
if (!Array.isArray(pageIr?.ref_bindings) || pageIr.ref_bindings.length === 0) {
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
const stateBindings = Array.isArray(pageIr?.hoisted?.state) ? pageIr.hoisted.state : [];
|
|
542
|
+
if (stateBindings.length === 0) {
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
for (const binding of pageIr.ref_bindings) {
|
|
547
|
+
if (!binding || typeof binding !== 'object' || typeof binding.identifier !== 'string') {
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
const resolved = resolveStateKeyFromBindings(binding.identifier, stateBindings, preferredKeys);
|
|
551
|
+
if (resolved) {
|
|
552
|
+
binding.identifier = resolved;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
247
557
|
/**
|
|
248
558
|
* Rewrite unresolved page expressions using component script-aware mappings.
|
|
249
559
|
*
|
|
250
560
|
* @param {object} pageIr
|
|
251
561
|
* @param {Map<string, string>} expressionMap
|
|
562
|
+
* @param {Map<string, {
|
|
563
|
+
* compiled_expr: string | null,
|
|
564
|
+
* signal_index: number | null,
|
|
565
|
+
* signal_indices: number[],
|
|
566
|
+
* state_index: number | null,
|
|
567
|
+
* component_instance: string | null,
|
|
568
|
+
* component_binding: string | null
|
|
569
|
+
* }>} bindingMap
|
|
252
570
|
* @param {Set<string>} ambiguous
|
|
253
571
|
*/
|
|
254
|
-
function applyExpressionRewrites(pageIr, expressionMap, ambiguous) {
|
|
572
|
+
function applyExpressionRewrites(pageIr, expressionMap, bindingMap, ambiguous) {
|
|
255
573
|
if (!Array.isArray(pageIr?.expressions) || pageIr.expressions.length === 0) {
|
|
256
574
|
return;
|
|
257
575
|
}
|
|
@@ -265,21 +583,109 @@ function applyExpressionRewrites(pageIr, expressionMap, ambiguous) {
|
|
|
265
583
|
if (ambiguous.has(current)) {
|
|
266
584
|
continue;
|
|
267
585
|
}
|
|
586
|
+
|
|
268
587
|
const rewritten = expressionMap.get(current);
|
|
269
|
-
|
|
588
|
+
const rewrittenBinding = bindingMap.get(current);
|
|
589
|
+
if (rewritten && rewritten !== current) {
|
|
590
|
+
pageIr.expressions[index] = rewritten;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if (!bindings[index] || typeof bindings[index] !== 'object') {
|
|
270
594
|
continue;
|
|
271
595
|
}
|
|
272
|
-
|
|
596
|
+
|
|
597
|
+
if (rewritten && rewritten !== current && bindings[index].literal === current) {
|
|
598
|
+
bindings[index].literal = rewritten;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
if (rewrittenBinding) {
|
|
602
|
+
bindings[index].compiled_expr = rewrittenBinding.compiled_expr;
|
|
603
|
+
bindings[index].signal_index = rewrittenBinding.signal_index;
|
|
604
|
+
bindings[index].signal_indices = rewrittenBinding.signal_indices;
|
|
605
|
+
bindings[index].state_index = rewrittenBinding.state_index;
|
|
606
|
+
bindings[index].component_instance = rewrittenBinding.component_instance;
|
|
607
|
+
bindings[index].component_binding = rewrittenBinding.component_binding;
|
|
608
|
+
} else if (rewritten && rewritten !== current && bindings[index].compiled_expr === current) {
|
|
609
|
+
bindings[index].compiled_expr = rewritten;
|
|
610
|
+
}
|
|
611
|
+
|
|
273
612
|
if (
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
bindings[index].literal === current
|
|
613
|
+
!rewrittenBinding &&
|
|
614
|
+
(!rewritten || rewritten === current) &&
|
|
615
|
+
bindings[index].literal === current &&
|
|
616
|
+
bindings[index].compiled_expr === current
|
|
277
617
|
) {
|
|
278
|
-
bindings[index].
|
|
279
|
-
|
|
280
|
-
|
|
618
|
+
bindings[index].compiled_expr = current;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function normalizeExpressionBindingDependencies(pageIr) {
|
|
624
|
+
if (!Array.isArray(pageIr?.expression_bindings) || pageIr.expression_bindings.length === 0) {
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
const signals = Array.isArray(pageIr.signals) ? pageIr.signals : [];
|
|
629
|
+
const dependencyRe = /signalMap\.get\((\d+)\)/g;
|
|
630
|
+
|
|
631
|
+
for (const binding of pageIr.expression_bindings) {
|
|
632
|
+
if (!binding || typeof binding !== 'object' || typeof binding.compiled_expr !== 'string') {
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const indices = [];
|
|
637
|
+
dependencyRe.lastIndex = 0;
|
|
638
|
+
let match;
|
|
639
|
+
while ((match = dependencyRe.exec(binding.compiled_expr)) !== null) {
|
|
640
|
+
const index = Number.parseInt(match[1], 10);
|
|
641
|
+
if (Number.isInteger(index)) {
|
|
642
|
+
indices.push(index);
|
|
281
643
|
}
|
|
282
644
|
}
|
|
645
|
+
|
|
646
|
+
if (indices.length === 0) {
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
let signalIndices = [...new Set(indices)].sort((a, b) => a - b);
|
|
651
|
+
if (Number.isInteger(binding.state_index)) {
|
|
652
|
+
const owningSignalIndices = signals
|
|
653
|
+
.map((signal, index) => signal?.state_index === binding.state_index ? index : null)
|
|
654
|
+
.filter((value) => Number.isInteger(value));
|
|
655
|
+
const extractedMatchState =
|
|
656
|
+
signalIndices.length > 0 &&
|
|
657
|
+
signalIndices.every((index) => signals[index]?.state_index === binding.state_index);
|
|
658
|
+
if (owningSignalIndices.length > 0 && !extractedMatchState) {
|
|
659
|
+
signalIndices = owningSignalIndices;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
if (
|
|
664
|
+
!Array.isArray(binding.signal_indices) ||
|
|
665
|
+
binding.signal_indices.length === 0 ||
|
|
666
|
+
binding.signal_indices.some((index) => signals[index]?.state_index !== binding.state_index)
|
|
667
|
+
) {
|
|
668
|
+
binding.signal_indices = signalIndices;
|
|
669
|
+
}
|
|
670
|
+
if (
|
|
671
|
+
(!Number.isInteger(binding.signal_index) ||
|
|
672
|
+
signals[binding.signal_index]?.state_index !== binding.state_index) &&
|
|
673
|
+
signalIndices.length === 1
|
|
674
|
+
) {
|
|
675
|
+
binding.signal_index = signalIndices[0];
|
|
676
|
+
}
|
|
677
|
+
if (!Number.isInteger(binding.state_index) && Number.isInteger(binding.signal_index)) {
|
|
678
|
+
const stateIndex = signals[binding.signal_index]?.state_index;
|
|
679
|
+
if (Number.isInteger(stateIndex)) {
|
|
680
|
+
binding.state_index = stateIndex;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
if (signalIndices.length === 1) {
|
|
684
|
+
binding.compiled_expr = binding.compiled_expr.replace(
|
|
685
|
+
/signalMap\.get\(\d+\)/g,
|
|
686
|
+
`signalMap.get(${signalIndices[0]})`
|
|
687
|
+
);
|
|
688
|
+
}
|
|
283
689
|
}
|
|
284
690
|
}
|
|
285
691
|
|
|
@@ -718,9 +1124,10 @@ const OPEN_COMPONENT_TAG_RE = /<([A-Z][a-zA-Z0-9]*)(\s[^<>]*?)?\s*(\/?)>/g;
|
|
|
718
1124
|
*
|
|
719
1125
|
* @param {string} source
|
|
720
1126
|
* @param {Map<string, string>} registry
|
|
721
|
-
* @
|
|
1127
|
+
* @param {string | null} ownerPath
|
|
1128
|
+
* @returns {Map<string, Array<{ attrs: string, ownerPath: string | null }>>}
|
|
722
1129
|
*/
|
|
723
|
-
function collectComponentUsageAttrs(source, registry) {
|
|
1130
|
+
function collectComponentUsageAttrs(source, registry, ownerPath = null) {
|
|
724
1131
|
const out = new Map();
|
|
725
1132
|
OPEN_COMPONENT_TAG_RE.lastIndex = 0;
|
|
726
1133
|
let match;
|
|
@@ -733,8 +1140,44 @@ function collectComponentUsageAttrs(source, registry) {
|
|
|
733
1140
|
if (!out.has(name)) {
|
|
734
1141
|
out.set(name, []);
|
|
735
1142
|
}
|
|
736
|
-
out.get(name).push(attrs);
|
|
1143
|
+
out.get(name).push({ attrs, ownerPath });
|
|
1144
|
+
}
|
|
1145
|
+
return out;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
/**
|
|
1149
|
+
* Collect component usage attrs recursively so nested component callsites
|
|
1150
|
+
* receive deterministic props preludes during page-hoist merging.
|
|
1151
|
+
*
|
|
1152
|
+
* Current Zenith architecture still resolves one attrs set per component type.
|
|
1153
|
+
* This helper preserves that model while ensuring nested usages are not lost.
|
|
1154
|
+
*
|
|
1155
|
+
* @param {string} source
|
|
1156
|
+
* @param {Map<string, string>} registry
|
|
1157
|
+
* @param {string | null} ownerPath
|
|
1158
|
+
* @param {Set<string>} visitedFiles
|
|
1159
|
+
* @param {Map<string, Array<{ attrs: string, ownerPath: string | null }>>} out
|
|
1160
|
+
* @returns {Map<string, Array<{ attrs: string, ownerPath: string | null }>>}
|
|
1161
|
+
*/
|
|
1162
|
+
function collectRecursiveComponentUsageAttrs(source, registry, ownerPath = null, visitedFiles = new Set(), out = new Map()) {
|
|
1163
|
+
const local = collectComponentUsageAttrs(source, registry, ownerPath);
|
|
1164
|
+
for (const [name, attrsList] of local.entries()) {
|
|
1165
|
+
if (!out.has(name)) {
|
|
1166
|
+
out.set(name, []);
|
|
1167
|
+
}
|
|
1168
|
+
out.get(name).push(...attrsList);
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
for (const name of local.keys()) {
|
|
1172
|
+
const compPath = registry.get(name);
|
|
1173
|
+
if (!compPath || visitedFiles.has(compPath)) {
|
|
1174
|
+
continue;
|
|
1175
|
+
}
|
|
1176
|
+
visitedFiles.add(compPath);
|
|
1177
|
+
const componentSource = readFileSync(compPath, 'utf8');
|
|
1178
|
+
collectRecursiveComponentUsageAttrs(componentSource, registry, compPath, visitedFiles, out);
|
|
737
1179
|
}
|
|
1180
|
+
|
|
738
1181
|
return out;
|
|
739
1182
|
}
|
|
740
1183
|
|
|
@@ -748,10 +1191,19 @@ function collectComponentUsageAttrs(source, registry) {
|
|
|
748
1191
|
* @param {object} compIr — the component's compiled IR
|
|
749
1192
|
* @param {string} compPath — component file path
|
|
750
1193
|
* @param {string} pageFile — page file path
|
|
751
|
-
* @param {{
|
|
1194
|
+
* @param {{
|
|
1195
|
+
* includeCode: boolean,
|
|
1196
|
+
* cssImportsOnly: boolean,
|
|
1197
|
+
* documentMode?: boolean,
|
|
1198
|
+
* componentAttrs?: string,
|
|
1199
|
+
* componentAttrsRewrite?: {
|
|
1200
|
+
* expressionRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null,
|
|
1201
|
+
* scopeRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null
|
|
1202
|
+
* } | null
|
|
1203
|
+
* }} options
|
|
752
1204
|
* @param {Set<string>} seenStaticImports
|
|
753
1205
|
*/
|
|
754
|
-
function mergeComponentIr(pageIr, compIr, compPath, pageFile, options, seenStaticImports) {
|
|
1206
|
+
function mergeComponentIr(pageIr, compIr, compPath, pageFile, options, seenStaticImports, knownRefKeys = null) {
|
|
755
1207
|
// Merge components_scripts
|
|
756
1208
|
if (compIr.components_scripts) {
|
|
757
1209
|
for (const [hoistId, script] of Object.entries(compIr.components_scripts)) {
|
|
@@ -766,6 +1218,17 @@ function mergeComponentIr(pageIr, compIr, compPath, pageFile, options, seenStati
|
|
|
766
1218
|
pageIr.component_instances.push(...compIr.component_instances);
|
|
767
1219
|
}
|
|
768
1220
|
|
|
1221
|
+
if (knownRefKeys instanceof Set && Array.isArray(compIr.ref_bindings)) {
|
|
1222
|
+
const componentStateBindings = Array.isArray(compIr?.hoisted?.state) ? compIr.hoisted.state : [];
|
|
1223
|
+
for (const binding of compIr.ref_bindings) {
|
|
1224
|
+
if (!binding || typeof binding.identifier !== 'string' || binding.identifier.length === 0) {
|
|
1225
|
+
continue;
|
|
1226
|
+
}
|
|
1227
|
+
const resolved = resolveStateKeyFromBindings(binding.identifier, componentStateBindings);
|
|
1228
|
+
knownRefKeys.add(resolved || binding.identifier);
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
|
|
769
1232
|
// Merge hoisted imports (deduplicated, rebased to the page file path)
|
|
770
1233
|
if (compIr.hoisted?.imports?.length) {
|
|
771
1234
|
for (const imp of compIr.hoisted.imports) {
|
|
@@ -829,6 +1292,41 @@ function mergeComponentIr(pageIr, compIr, compPath, pageFile, options, seenStati
|
|
|
829
1292
|
}
|
|
830
1293
|
}
|
|
831
1294
|
|
|
1295
|
+
if (options.includeCode && Array.isArray(compIr.signals)) {
|
|
1296
|
+
pageIr.signals = Array.isArray(pageIr.signals) ? pageIr.signals : [];
|
|
1297
|
+
const existingSignalStateKeys = new Set(
|
|
1298
|
+
pageIr.signals
|
|
1299
|
+
.map((signal) => {
|
|
1300
|
+
const stateIndex = signal?.state_index;
|
|
1301
|
+
return Number.isInteger(stateIndex) ? pageIr.hoisted.state?.[stateIndex]?.key : null;
|
|
1302
|
+
})
|
|
1303
|
+
.filter(Boolean)
|
|
1304
|
+
);
|
|
1305
|
+
|
|
1306
|
+
for (const signal of compIr.signals) {
|
|
1307
|
+
if (!signal || !Number.isInteger(signal.state_index)) {
|
|
1308
|
+
continue;
|
|
1309
|
+
}
|
|
1310
|
+
const stateKey = compIr.hoisted?.state?.[signal.state_index]?.key;
|
|
1311
|
+
if (typeof stateKey !== 'string' || stateKey.length === 0) {
|
|
1312
|
+
continue;
|
|
1313
|
+
}
|
|
1314
|
+
const pageStateIndex = pageIr.hoisted.state.findIndex((entry) => entry?.key === stateKey);
|
|
1315
|
+
if (!Number.isInteger(pageStateIndex) || pageStateIndex < 0) {
|
|
1316
|
+
continue;
|
|
1317
|
+
}
|
|
1318
|
+
if (existingSignalStateKeys.has(stateKey)) {
|
|
1319
|
+
continue;
|
|
1320
|
+
}
|
|
1321
|
+
existingSignalStateKeys.add(stateKey);
|
|
1322
|
+
pageIr.signals.push({
|
|
1323
|
+
id: pageIr.signals.length,
|
|
1324
|
+
kind: typeof signal.kind === 'string' && signal.kind.length > 0 ? signal.kind : 'signal',
|
|
1325
|
+
state_index: pageStateIndex
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
|
|
832
1330
|
// Merge hoisted code blocks (rebased to the page file path)
|
|
833
1331
|
if (options.includeCode && compIr.hoisted?.code?.length) {
|
|
834
1332
|
for (const block of compIr.hoisted.code) {
|
|
@@ -836,7 +1334,11 @@ function mergeComponentIr(pageIr, compIr, compPath, pageFile, options, seenStati
|
|
|
836
1334
|
const filteredImports = options.cssImportsOnly
|
|
837
1335
|
? stripNonCssStaticImportsInSource(rebased)
|
|
838
1336
|
: rebased;
|
|
839
|
-
const withPropsPrelude = injectPropsPrelude(
|
|
1337
|
+
const withPropsPrelude = injectPropsPrelude(
|
|
1338
|
+
filteredImports,
|
|
1339
|
+
options.componentAttrs || '',
|
|
1340
|
+
options.componentAttrsRewrite || null
|
|
1341
|
+
);
|
|
840
1342
|
const transpiled = transpileTypeScriptToJs(withPropsPrelude, compPath);
|
|
841
1343
|
const deduped = dedupeStaticImportsInSource(transpiled, seenStaticImports);
|
|
842
1344
|
const deferred = deferComponentRuntimeBlock(deduped);
|
|
@@ -1137,11 +1639,123 @@ function renderObjectKey(key) {
|
|
|
1137
1639
|
return JSON.stringify(key);
|
|
1138
1640
|
}
|
|
1139
1641
|
|
|
1642
|
+
/**
|
|
1643
|
+
* @param {string} value
|
|
1644
|
+
* @returns {string | null}
|
|
1645
|
+
*/
|
|
1646
|
+
function deriveScopedIdentifierAlias(value) {
|
|
1647
|
+
const ident = String(value || '').trim();
|
|
1648
|
+
if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(ident)) {
|
|
1649
|
+
return null;
|
|
1650
|
+
}
|
|
1651
|
+
const parts = ident.split('_').filter(Boolean);
|
|
1652
|
+
const candidate = parts.length > 1 ? parts[parts.length - 1] : ident;
|
|
1653
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(candidate) ? candidate : ident;
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
/**
|
|
1657
|
+
* @param {Map<string, string>} map
|
|
1658
|
+
* @param {Set<string>} ambiguous
|
|
1659
|
+
* @param {string | null} raw
|
|
1660
|
+
* @param {string | null} rewritten
|
|
1661
|
+
*/
|
|
1662
|
+
function recordScopedIdentifierRewrite(map, ambiguous, raw, rewritten) {
|
|
1663
|
+
if (typeof raw !== 'string' || raw.length === 0 || typeof rewritten !== 'string' || rewritten.length === 0) {
|
|
1664
|
+
return;
|
|
1665
|
+
}
|
|
1666
|
+
const existing = map.get(raw);
|
|
1667
|
+
if (existing && existing !== rewritten) {
|
|
1668
|
+
map.delete(raw);
|
|
1669
|
+
ambiguous.add(raw);
|
|
1670
|
+
return;
|
|
1671
|
+
}
|
|
1672
|
+
if (!ambiguous.has(raw)) {
|
|
1673
|
+
map.set(raw, rewritten);
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
/**
|
|
1678
|
+
* @param {object | null | undefined} ir
|
|
1679
|
+
* @returns {{ map: Map<string, string>, ambiguous: Set<string> }}
|
|
1680
|
+
*/
|
|
1681
|
+
function buildScopedIdentifierRewrite(ir) {
|
|
1682
|
+
const out = { map: new Map(), ambiguous: new Set() };
|
|
1683
|
+
if (!ir || typeof ir !== 'object') {
|
|
1684
|
+
return out;
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
const stateBindings = Array.isArray(ir?.hoisted?.state) ? ir.hoisted.state : [];
|
|
1688
|
+
for (const stateEntry of stateBindings) {
|
|
1689
|
+
const key = typeof stateEntry?.key === 'string' ? stateEntry.key : null;
|
|
1690
|
+
recordScopedIdentifierRewrite(out.map, out.ambiguous, deriveScopedIdentifierAlias(key), key);
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
const functionBindings = Array.isArray(ir?.hoisted?.functions) ? ir.hoisted.functions : [];
|
|
1694
|
+
for (const fnName of functionBindings) {
|
|
1695
|
+
if (typeof fnName !== 'string') {
|
|
1696
|
+
continue;
|
|
1697
|
+
}
|
|
1698
|
+
recordScopedIdentifierRewrite(out.map, out.ambiguous, deriveScopedIdentifierAlias(fnName), fnName);
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
return out;
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
/**
|
|
1705
|
+
* @param {string} expr
|
|
1706
|
+
* @param {{
|
|
1707
|
+
* expressionRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null,
|
|
1708
|
+
* scopeRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null
|
|
1709
|
+
* } | null} rewriteContext
|
|
1710
|
+
* @returns {string}
|
|
1711
|
+
*/
|
|
1712
|
+
function rewritePropsExpression(expr, rewriteContext = null) {
|
|
1713
|
+
const trimmed = String(expr || '').trim();
|
|
1714
|
+
if (!trimmed) {
|
|
1715
|
+
return trimmed;
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
const expressionMap = rewriteContext?.expressionRewrite?.map;
|
|
1719
|
+
const expressionAmbiguous = rewriteContext?.expressionRewrite?.ambiguous;
|
|
1720
|
+
if (
|
|
1721
|
+
expressionMap instanceof Map &&
|
|
1722
|
+
!(expressionAmbiguous instanceof Set && expressionAmbiguous.has(trimmed))
|
|
1723
|
+
) {
|
|
1724
|
+
const exact = expressionMap.get(trimmed);
|
|
1725
|
+
if (typeof exact === 'string' && exact.length > 0) {
|
|
1726
|
+
return exact;
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
const scopeMap = rewriteContext?.scopeRewrite?.map;
|
|
1731
|
+
const scopeAmbiguous = rewriteContext?.scopeRewrite?.ambiguous;
|
|
1732
|
+
const rootMatch = trimmed.match(/^([A-Za-z_$][A-Za-z0-9_$]*)([\s\S]*)$/);
|
|
1733
|
+
if (!rootMatch || !(scopeMap instanceof Map)) {
|
|
1734
|
+
return trimmed;
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
const root = rootMatch[1];
|
|
1738
|
+
if (scopeAmbiguous instanceof Set && scopeAmbiguous.has(root)) {
|
|
1739
|
+
return trimmed;
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
const rewrittenRoot = scopeMap.get(root);
|
|
1743
|
+
if (typeof rewrittenRoot !== 'string' || rewrittenRoot.length === 0 || rewrittenRoot === root) {
|
|
1744
|
+
return trimmed;
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
return `${rewrittenRoot}${rootMatch[2]}`;
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1140
1750
|
/**
|
|
1141
1751
|
* @param {string} attrs
|
|
1752
|
+
* @param {{
|
|
1753
|
+
* expressionRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null,
|
|
1754
|
+
* scopeRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null
|
|
1755
|
+
* } | null} rewriteContext
|
|
1142
1756
|
* @returns {string}
|
|
1143
1757
|
*/
|
|
1144
|
-
function renderPropsLiteralFromAttrs(attrs) {
|
|
1758
|
+
function renderPropsLiteralFromAttrs(attrs, rewriteContext = null) {
|
|
1145
1759
|
const src = String(attrs || '').trim();
|
|
1146
1760
|
if (!src) {
|
|
1147
1761
|
return '{}';
|
|
@@ -1166,7 +1780,7 @@ function renderPropsLiteralFromAttrs(attrs) {
|
|
|
1166
1780
|
valueCode = JSON.stringify(singleQuoted);
|
|
1167
1781
|
} else if (expressionValue !== undefined) {
|
|
1168
1782
|
const trimmed = String(expressionValue).trim();
|
|
1169
|
-
valueCode = trimmed.length > 0 ? trimmed : 'undefined';
|
|
1783
|
+
valueCode = trimmed.length > 0 ? rewritePropsExpression(trimmed, rewriteContext) : 'undefined';
|
|
1170
1784
|
}
|
|
1171
1785
|
|
|
1172
1786
|
entries.push(`${renderObjectKey(rawName)}: ${valueCode}`);
|
|
@@ -1182,9 +1796,13 @@ function renderPropsLiteralFromAttrs(attrs) {
|
|
|
1182
1796
|
/**
|
|
1183
1797
|
* @param {string} source
|
|
1184
1798
|
* @param {string} attrs
|
|
1799
|
+
* @param {{
|
|
1800
|
+
* expressionRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null,
|
|
1801
|
+
* scopeRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null
|
|
1802
|
+
* } | null} rewriteContext
|
|
1185
1803
|
* @returns {string}
|
|
1186
1804
|
*/
|
|
1187
|
-
function injectPropsPrelude(source, attrs) {
|
|
1805
|
+
function injectPropsPrelude(source, attrs, rewriteContext = null) {
|
|
1188
1806
|
if (typeof source !== 'string' || source.trim().length === 0) {
|
|
1189
1807
|
return source;
|
|
1190
1808
|
}
|
|
@@ -1195,7 +1813,7 @@ function injectPropsPrelude(source, attrs) {
|
|
|
1195
1813
|
return source;
|
|
1196
1814
|
}
|
|
1197
1815
|
|
|
1198
|
-
const propsLiteral = renderPropsLiteralFromAttrs(attrs);
|
|
1816
|
+
const propsLiteral = renderPropsLiteralFromAttrs(attrs, rewriteContext);
|
|
1199
1817
|
return `var props = ${propsLiteral};\n${source}`;
|
|
1200
1818
|
}
|
|
1201
1819
|
|
|
@@ -1253,16 +1871,32 @@ function deferComponentRuntimeBlock(source) {
|
|
|
1253
1871
|
*
|
|
1254
1872
|
* @param {object|object[]} envelope
|
|
1255
1873
|
* @param {string} outDir
|
|
1874
|
+
* @param {string} projectRoot
|
|
1875
|
+
* @param {object | null} [logger]
|
|
1876
|
+
* @param {boolean} [showInfo]
|
|
1256
1877
|
* @returns {Promise<void>}
|
|
1257
1878
|
*/
|
|
1258
|
-
function runBundler(envelope, outDir) {
|
|
1879
|
+
function runBundler(envelope, outDir, projectRoot, logger = null, showInfo = true) {
|
|
1259
1880
|
return new Promise((resolvePromise, rejectPromise) => {
|
|
1881
|
+
const useStructuredLogger = Boolean(logger && typeof logger.childLine === 'function');
|
|
1260
1882
|
const child = spawn(
|
|
1261
1883
|
getBundlerBin(),
|
|
1262
1884
|
['--out-dir', outDir],
|
|
1263
|
-
{
|
|
1885
|
+
{
|
|
1886
|
+
cwd: projectRoot,
|
|
1887
|
+
stdio: useStructuredLogger ? ['pipe', 'pipe', 'pipe'] : ['pipe', 'inherit', 'inherit']
|
|
1888
|
+
}
|
|
1264
1889
|
);
|
|
1265
1890
|
|
|
1891
|
+
if (useStructuredLogger) {
|
|
1892
|
+
forwardStreamLines(child.stdout, (line) => {
|
|
1893
|
+
logger.childLine('bundler', line, { stream: 'stdout', showInfo });
|
|
1894
|
+
});
|
|
1895
|
+
forwardStreamLines(child.stderr, (line) => {
|
|
1896
|
+
logger.childLine('bundler', line, { stream: 'stderr', showInfo: true });
|
|
1897
|
+
});
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1266
1900
|
child.on('error', (err) => {
|
|
1267
1901
|
rejectPromise(new Error(`Bundler spawn failed: ${err.message}`));
|
|
1268
1902
|
});
|
|
@@ -1328,11 +1962,12 @@ async function collectAssets(rootDir) {
|
|
|
1328
1962
|
* d. Merge component IRs into page IR
|
|
1329
1963
|
* 3. Send all envelopes to bundler
|
|
1330
1964
|
*
|
|
1331
|
-
* @param {{ pagesDir: string, outDir: string, config?: object }} options
|
|
1965
|
+
* @param {{ pagesDir: string, outDir: string, config?: object, logger?: object | null, showBundlerInfo?: boolean }} options
|
|
1332
1966
|
* @returns {Promise<{ pages: number, assets: string[] }>}
|
|
1333
1967
|
*/
|
|
1334
1968
|
export async function build(options) {
|
|
1335
|
-
const { pagesDir, outDir, config = {} } = options;
|
|
1969
|
+
const { pagesDir, outDir, config = {}, logger = null, showBundlerInfo = true } = options;
|
|
1970
|
+
const projectRoot = deriveProjectRootFromPagesDir(pagesDir);
|
|
1336
1971
|
const softNavigationEnabled = config.softNavigation === true || config.router === true;
|
|
1337
1972
|
const compilerOpts = {
|
|
1338
1973
|
typescriptDefault: config.typescriptDefault === true,
|
|
@@ -1349,7 +1984,13 @@ export async function build(options) {
|
|
|
1349
1984
|
// 1. Build component registry
|
|
1350
1985
|
const registry = buildComponentRegistry(srcDir);
|
|
1351
1986
|
if (registry.size > 0) {
|
|
1352
|
-
|
|
1987
|
+
if (logger && typeof logger.build === 'function') {
|
|
1988
|
+
logger.build(`registry=${registry.size} components`, {
|
|
1989
|
+
onceKey: `component-registry:${registry.size}`
|
|
1990
|
+
});
|
|
1991
|
+
} else {
|
|
1992
|
+
console.log(`[zenith] Component registry: ${registry.size} components`);
|
|
1993
|
+
}
|
|
1353
1994
|
}
|
|
1354
1995
|
|
|
1355
1996
|
const manifest = await generateManifest(pagesDir);
|
|
@@ -1362,13 +2003,19 @@ export async function build(options) {
|
|
|
1362
2003
|
const componentDocumentModeCache = new Map();
|
|
1363
2004
|
/** @type {Map<string, { map: Map<string, string>, ambiguous: Set<string> }>} */
|
|
1364
2005
|
const componentExpressionRewriteCache = new Map();
|
|
1365
|
-
const emitCompilerWarning = createCompilerWarningEmitter((line) =>
|
|
2006
|
+
const emitCompilerWarning = createCompilerWarningEmitter((line) => {
|
|
2007
|
+
if (logger && typeof logger.warn === 'function') {
|
|
2008
|
+
logger.warn(line, { onceKey: `compiler-warning:${line}` });
|
|
2009
|
+
return;
|
|
2010
|
+
}
|
|
2011
|
+
console.warn(line);
|
|
2012
|
+
});
|
|
1366
2013
|
|
|
1367
2014
|
const envelopes = [];
|
|
1368
2015
|
for (const entry of manifest) {
|
|
1369
2016
|
const sourceFile = join(pagesDir, entry.file);
|
|
1370
2017
|
const rawSource = readFileSync(sourceFile, 'utf8');
|
|
1371
|
-
const componentUsageAttrs =
|
|
2018
|
+
const componentUsageAttrs = collectRecursiveComponentUsageAttrs(rawSource, registry, sourceFile);
|
|
1372
2019
|
|
|
1373
2020
|
const baseName = sourceFile.slice(0, -extname(sourceFile).length);
|
|
1374
2021
|
let adjacentGuard = null;
|
|
@@ -1421,6 +2068,7 @@ export async function build(options) {
|
|
|
1421
2068
|
// Ensure IR has required array fields for merging
|
|
1422
2069
|
pageIr.components_scripts = pageIr.components_scripts || {};
|
|
1423
2070
|
pageIr.component_instances = pageIr.component_instances || [];
|
|
2071
|
+
pageIr.signals = Array.isArray(pageIr.signals) ? pageIr.signals : [];
|
|
1424
2072
|
pageIr.hoisted = pageIr.hoisted || { imports: [], declarations: [], functions: [], signals: [], state: [], code: [] };
|
|
1425
2073
|
pageIr.hoisted.imports = pageIr.hoisted.imports || [];
|
|
1426
2074
|
pageIr.hoisted.declarations = pageIr.hoisted.declarations || [];
|
|
@@ -1430,7 +2078,12 @@ export async function build(options) {
|
|
|
1430
2078
|
pageIr.hoisted.code = pageIr.hoisted.code || [];
|
|
1431
2079
|
const seenStaticImports = new Set();
|
|
1432
2080
|
const pageExpressionRewriteMap = new Map();
|
|
2081
|
+
const pageExpressionBindingMap = new Map();
|
|
1433
2082
|
const pageAmbiguousExpressionMap = new Set();
|
|
2083
|
+
const knownRefKeys = new Set();
|
|
2084
|
+
const pageScopeRewrite = buildScopedIdentifierRewrite(pageIr);
|
|
2085
|
+
const pageSelfExpressionRewrite = buildComponentExpressionRewrite(sourceFile, compileSource, pageIr, compilerOpts);
|
|
2086
|
+
const componentScopeRewriteCache = new Map();
|
|
1434
2087
|
|
|
1435
2088
|
// 2c. Compile each used component separately for its script IR
|
|
1436
2089
|
for (const compName of usedComponents) {
|
|
@@ -1463,11 +2116,44 @@ export async function build(options) {
|
|
|
1463
2116
|
expressionRewrite = buildComponentExpressionRewrite(compPath, componentSource, compIr, compilerOpts);
|
|
1464
2117
|
componentExpressionRewriteCache.set(compPath, expressionRewrite);
|
|
1465
2118
|
}
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
2119
|
+
|
|
2120
|
+
let usageEntry = (componentUsageAttrs.get(compName) || [])[0] || { attrs: '', ownerPath: sourceFile };
|
|
2121
|
+
if (!usageEntry || typeof usageEntry !== 'object') {
|
|
2122
|
+
usageEntry = { attrs: '', ownerPath: sourceFile };
|
|
2123
|
+
}
|
|
2124
|
+
|
|
2125
|
+
let attrExpressionRewrite = pageSelfExpressionRewrite;
|
|
2126
|
+
let attrScopeRewrite = pageScopeRewrite;
|
|
2127
|
+
const ownerPath = typeof usageEntry.ownerPath === 'string' && usageEntry.ownerPath.length > 0
|
|
2128
|
+
? usageEntry.ownerPath
|
|
2129
|
+
: sourceFile;
|
|
2130
|
+
|
|
2131
|
+
if (ownerPath !== sourceFile) {
|
|
2132
|
+
let ownerIr = componentIrCache.get(ownerPath);
|
|
2133
|
+
if (!ownerIr) {
|
|
2134
|
+
const ownerSource = readFileSync(ownerPath, 'utf8');
|
|
2135
|
+
ownerIr = runCompiler(
|
|
2136
|
+
ownerPath,
|
|
2137
|
+
stripStyleBlocks(ownerSource),
|
|
2138
|
+
compilerOpts,
|
|
2139
|
+
{ onWarning: emitCompilerWarning }
|
|
2140
|
+
);
|
|
2141
|
+
componentIrCache.set(ownerPath, ownerIr);
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
attrExpressionRewrite = componentExpressionRewriteCache.get(ownerPath);
|
|
2145
|
+
if (!attrExpressionRewrite) {
|
|
2146
|
+
const ownerSource = readFileSync(ownerPath, 'utf8');
|
|
2147
|
+
attrExpressionRewrite = buildComponentExpressionRewrite(ownerPath, ownerSource, ownerIr, compilerOpts);
|
|
2148
|
+
componentExpressionRewriteCache.set(ownerPath, attrExpressionRewrite);
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2151
|
+
attrScopeRewrite = componentScopeRewriteCache.get(ownerPath);
|
|
2152
|
+
if (!attrScopeRewrite) {
|
|
2153
|
+
attrScopeRewrite = buildScopedIdentifierRewrite(ownerIr);
|
|
2154
|
+
componentScopeRewriteCache.set(ownerPath, attrScopeRewrite);
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
1471
2157
|
|
|
1472
2158
|
// 2d. Merge component IR into page IR
|
|
1473
2159
|
mergeComponentIr(
|
|
@@ -1479,19 +2165,35 @@ export async function build(options) {
|
|
|
1479
2165
|
includeCode: true,
|
|
1480
2166
|
cssImportsOnly: isDocMode,
|
|
1481
2167
|
documentMode: isDocMode,
|
|
1482
|
-
componentAttrs:
|
|
2168
|
+
componentAttrs: typeof usageEntry.attrs === 'string' ? usageEntry.attrs : '',
|
|
2169
|
+
componentAttrsRewrite: {
|
|
2170
|
+
expressionRewrite: attrExpressionRewrite,
|
|
2171
|
+
scopeRewrite: attrScopeRewrite
|
|
2172
|
+
}
|
|
1483
2173
|
},
|
|
1484
|
-
seenStaticImports
|
|
2174
|
+
seenStaticImports,
|
|
2175
|
+
knownRefKeys
|
|
2176
|
+
);
|
|
2177
|
+
|
|
2178
|
+
mergeExpressionRewriteMaps(
|
|
2179
|
+
pageExpressionRewriteMap,
|
|
2180
|
+
pageExpressionBindingMap,
|
|
2181
|
+
pageAmbiguousExpressionMap,
|
|
2182
|
+
expressionRewrite,
|
|
2183
|
+
pageIr
|
|
1485
2184
|
);
|
|
1486
2185
|
}
|
|
1487
2186
|
|
|
1488
2187
|
applyExpressionRewrites(
|
|
1489
2188
|
pageIr,
|
|
1490
2189
|
pageExpressionRewriteMap,
|
|
2190
|
+
pageExpressionBindingMap,
|
|
1491
2191
|
pageAmbiguousExpressionMap
|
|
1492
2192
|
);
|
|
2193
|
+
normalizeExpressionBindingDependencies(pageIr);
|
|
1493
2194
|
|
|
1494
2195
|
rewriteLegacyMarkupIdentifiers(pageIr);
|
|
2196
|
+
rewriteRefBindingIdentifiers(pageIr, knownRefKeys);
|
|
1495
2197
|
|
|
1496
2198
|
envelopes.push({
|
|
1497
2199
|
route: entry.path,
|
|
@@ -1502,7 +2204,7 @@ export async function build(options) {
|
|
|
1502
2204
|
}
|
|
1503
2205
|
|
|
1504
2206
|
if (envelopes.length > 0) {
|
|
1505
|
-
|
|
2207
|
+
await runBundler(envelopes, outDir, projectRoot, logger, showBundlerInfo);
|
|
1506
2208
|
}
|
|
1507
2209
|
|
|
1508
2210
|
const assets = await collectAssets(outDir);
|