@vesk/compiler 0.2.26 → 0.2.27
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/client-codegen.d.ts +15 -0
- package/dist/client-codegen.d.ts.map +1 -1
- package/dist/client-codegen.js +233 -64
- package/dist/errors.d.ts +3 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +22 -1
- package/dist/ir-generator.d.ts.map +1 -1
- package/dist/ir-generator.js +200 -22
- package/dist/ir.d.ts +24 -1
- package/dist/ir.d.ts.map +1 -1
- package/dist/ir.js +28 -0
- package/dist/module-imports.d.ts +22 -1
- package/dist/module-imports.d.ts.map +1 -1
- package/dist/module-imports.js +177 -2
- package/dist/server-jsgen.d.ts.map +1 -1
- package/dist/server-jsgen.js +51 -16
- package/dist/server-render.d.ts.map +1 -1
- package/dist/server-render.js +20 -15
- package/package.json +2 -2
package/dist/client-codegen.d.ts
CHANGED
|
@@ -16,6 +16,20 @@ export declare function collectTrackedNames(body: IRNode[]): Map<string, Tracked
|
|
|
16
16
|
* normal vesk parser so TS generics and `.vsk` syntax survive.
|
|
17
17
|
*/
|
|
18
18
|
export declare function transformTrackedInit(init: string, tracked: Map<string, TrackedInfo>): string;
|
|
19
|
+
/** Shared counter for generated `$n` identifiers. */
|
|
20
|
+
export type NameAlloc = {
|
|
21
|
+
c: number;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Allocates a disjoint `$n` name block per source file. esbuild's bundler
|
|
25
|
+
* renames colliding top-level symbols when it links a chunk, and on 0.25.x
|
|
26
|
+
* that rename can leave references inside nested closures pointing at the
|
|
27
|
+
* pre-rename name — surfacing as `$nNN is not defined` at hydration. Seeding
|
|
28
|
+
* every file from a hash of its path keeps the names globally unique so the
|
|
29
|
+
* renamer has nothing to rename. Callers without a `sourcePath` keep the
|
|
30
|
+
* historical `$n0…` numbering.
|
|
31
|
+
*/
|
|
32
|
+
export declare function nameAllocFor(sourcePath?: string, start?: number): NameAlloc;
|
|
19
33
|
declare function isStaticIR(body: IRNode[]): boolean;
|
|
20
34
|
export declare function escapeHtml(str: string): string;
|
|
21
35
|
export declare function compileClient(source: string, _componentName: string | null, options?: {
|
|
@@ -24,6 +38,7 @@ export declare function compileClient(source: string, _componentName: string | n
|
|
|
24
38
|
includeTopLevel?: boolean;
|
|
25
39
|
sourcePath?: string;
|
|
26
40
|
mdRoots?: string[];
|
|
41
|
+
nameAllocator?: NameAlloc;
|
|
27
42
|
}): string;
|
|
28
43
|
/**
|
|
29
44
|
* Compiles a component source in BOTH client modes (plain + hydrate)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-codegen.d.ts","sourceRoot":"","sources":["../src/client-codegen.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAuB,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAGL,cAAc,EAQd,gBAAgB,
|
|
1
|
+
{"version":3,"file":"client-codegen.d.ts","sourceRoot":"","sources":["../src/client-codegen.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAuB,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAGL,cAAc,EAQd,gBAAgB,EASjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAyCxD,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,SAAS,GAAG,MAAM,CAAC;CAC1B;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI1D;AAiDD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,gBAAgB,GAAG,cAAc,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,CAwGlI;AAID,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAU5E;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,CAY5F;AAoBD,qDAAqD;AACrD,MAAM,MAAM,SAAS,GAAG;IAAE,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtC;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,KAAK,SAAI,GAAG,SAAS,CAQtE;AAmjDD,iBAAS,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAe3C;AAgID,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO9C;AAqED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,aAAa,CAAC,EAAE,SAAS,CAAA;CAAO,GAAG,MAAM,CAO9O;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAwBxJ;AAED,OAAO,EAAE,aAAa,IAAI,OAAO,EAAE,UAAU,EAAE,CAAC"}
|
package/dist/client-codegen.js
CHANGED
|
@@ -2,7 +2,7 @@ import { walk } from 'zimmerframe';
|
|
|
2
2
|
import { print } from 'esrap';
|
|
3
3
|
import ts from 'esrap/languages/ts';
|
|
4
4
|
import tsx from 'esrap/languages/tsx';
|
|
5
|
-
import { StaticNode, TextNode, DynamicBinding, OpaqueDynamicRegion, MapRegion, WhileLoop, SwitchBlock, TryCatch, ForLoop, TrackDecl, RuntimeStatement, ComponentRef, ComponentCall, ServerBlock, ClientBlock, HeadBlock, SlotNode, } from '@vesk/compiler/src/ir';
|
|
5
|
+
import { StaticNode, TextNode, DynamicBinding, OpaqueDynamicRegion, MapRegion, WhileLoop, SwitchBlock, TryCatch, ForLoop, TrackDecl, RuntimeStatement, ComponentRef, ComponentCall, ServerBlock, ClientBlock, HeadBlock, SlotNode, PropSlot, PropSlotRender, } from '@vesk/compiler/src/ir';
|
|
6
6
|
import { parse } from '@vesk/compiler/src/parser';
|
|
7
7
|
import { generateIR } from '@vesk/compiler/src/ir-generator';
|
|
8
8
|
import { transformTopLevelForActions } from '@vesk/compiler/src/actions';
|
|
@@ -248,10 +248,29 @@ function indent(code, level = 1) {
|
|
|
248
248
|
const pad = '\t'.repeat(level);
|
|
249
249
|
return code.split('\n').map((l) => (l ? pad + l : '')).join('\n');
|
|
250
250
|
}
|
|
251
|
+
/**
|
|
252
|
+
* Allocates a disjoint `$n` name block per source file. esbuild's bundler
|
|
253
|
+
* renames colliding top-level symbols when it links a chunk, and on 0.25.x
|
|
254
|
+
* that rename can leave references inside nested closures pointing at the
|
|
255
|
+
* pre-rename name — surfacing as `$nNN is not defined` at hydration. Seeding
|
|
256
|
+
* every file from a hash of its path keeps the names globally unique so the
|
|
257
|
+
* renamer has nothing to rename. Callers without a `sourcePath` keep the
|
|
258
|
+
* historical `$n0…` numbering.
|
|
259
|
+
*/
|
|
260
|
+
export function nameAllocFor(sourcePath, start = 0) {
|
|
261
|
+
if (!sourcePath)
|
|
262
|
+
return { c: start };
|
|
263
|
+
let h = 0x811c9dc5;
|
|
264
|
+
for (let i = 0; i < sourcePath.length; i++) {
|
|
265
|
+
h ^= sourcePath.charCodeAt(i);
|
|
266
|
+
h = Math.imul(h, 0x01000193) >>> 0;
|
|
267
|
+
}
|
|
268
|
+
return { c: h * 1_000_000 + start };
|
|
269
|
+
}
|
|
251
270
|
class Ctx {
|
|
252
271
|
lines = [];
|
|
253
272
|
effects = [];
|
|
254
|
-
|
|
273
|
+
alloc;
|
|
255
274
|
importedNames = new Set();
|
|
256
275
|
linkNames = new Set();
|
|
257
276
|
// Runtime component imports that claim their own SSR root via `nextElement`
|
|
@@ -270,12 +289,15 @@ class Ctx {
|
|
|
270
289
|
walker = '__hydrate';
|
|
271
290
|
asyncComps = new Set();
|
|
272
291
|
isAsyncScope = false;
|
|
292
|
+
constructor(alloc) {
|
|
293
|
+
this.alloc = alloc ?? { c: 0 };
|
|
294
|
+
}
|
|
273
295
|
push(...args) {
|
|
274
296
|
for (const a of args)
|
|
275
297
|
if (a)
|
|
276
298
|
this.lines.push(a);
|
|
277
299
|
}
|
|
278
|
-
n() { return `$n${this.c++}`; }
|
|
300
|
+
n() { return `$n${this.alloc.c++}`; }
|
|
279
301
|
getCode() { return this.lines.join('\n'); }
|
|
280
302
|
flushEffects() {
|
|
281
303
|
if (this.effects.length === 0)
|
|
@@ -292,8 +314,17 @@ class Ctx {
|
|
|
292
314
|
lines.push(`if (!document.${guard}) {`);
|
|
293
315
|
lines.push(`\tdocument.${guard} = true;`);
|
|
294
316
|
lines.push(`\tdocument.addEventListener(${JSON.stringify(type)}, (e) => {`);
|
|
295
|
-
|
|
296
|
-
|
|
317
|
+
// Walk up from the target to the nearest element that actually has a
|
|
318
|
+
// handler for this event. Using `closest('[data-vsk-ev]')` alone stops at
|
|
319
|
+
// the nearest event-capable element even when its handler is undefined
|
|
320
|
+
// (`<Button/>` forwards an optional onClick that was never passed), which
|
|
321
|
+
// shadows a real handler on an ancestor (e.g. a Popover trigger wrapper).
|
|
322
|
+
lines.push(`\t\tvar el = e.target;`);
|
|
323
|
+
lines.push(`\t\tif (el && el.nodeType !== 1) el = el.parentElement;`);
|
|
324
|
+
lines.push(`\t\twhile (el && el.nodeType === 1) {`);
|
|
325
|
+
lines.push(`\t\t\tif (el.${prop}) { el.${prop}(e); break; }`);
|
|
326
|
+
lines.push(`\t\t\tel = el.parentElement;`);
|
|
327
|
+
lines.push(`\t\t}`);
|
|
297
328
|
lines.push(`\t});`);
|
|
298
329
|
lines.push(`}`);
|
|
299
330
|
}
|
|
@@ -325,7 +356,7 @@ function emitNode(ctx, node, tracked, effectsVar, parentVar, compPrefix = '__com
|
|
|
325
356
|
if (node instanceof OpaqueDynamicRegion)
|
|
326
357
|
return emitOpaque(ctx, node, tracked, effectsVar, parentVar);
|
|
327
358
|
if (node instanceof MapRegion)
|
|
328
|
-
return emitMap(ctx, node, tracked, parentVar);
|
|
359
|
+
return emitMap(ctx, node, tracked, effectsVar, parentVar);
|
|
329
360
|
if (node instanceof ServerBlock)
|
|
330
361
|
return null;
|
|
331
362
|
if (node instanceof ClientBlock) {
|
|
@@ -353,7 +384,7 @@ function emitNode(ctx, node, tracked, effectsVar, parentVar, compPrefix = '__com
|
|
|
353
384
|
ctx.push(`if (props.children !== undefined && props.children !== null) {`);
|
|
354
385
|
ctx.push(` if (typeof props.children === 'function') {`);
|
|
355
386
|
ctx.push(` const __child = props.children(${ctx.walker});`);
|
|
356
|
-
ctx.push(` if (__child && typeof __child.then === 'function') __pendingChild = __child.then(() => $root);`);
|
|
387
|
+
ctx.push(` if (__child && typeof __child.then === 'function') __pendingChild = __child.then(() => $mount || $root);`);
|
|
357
388
|
ctx.push(` } else {`);
|
|
358
389
|
ctx.push(` ${parentVar}.appendChild(props.children);`);
|
|
359
390
|
ctx.push(` }`);
|
|
@@ -372,6 +403,32 @@ function emitNode(ctx, node, tracked, effectsVar, parentVar, compPrefix = '__com
|
|
|
372
403
|
return emitForLoop(ctx, node, tracked, parentVar);
|
|
373
404
|
if (node instanceof SwitchBlock)
|
|
374
405
|
return emitSwitchBlock(ctx, node, tracked, parentVar);
|
|
406
|
+
if (node instanceof PropSlot)
|
|
407
|
+
return null;
|
|
408
|
+
if (node instanceof PropSlotRender) {
|
|
409
|
+
const slotProp = `props.${node.propName}`;
|
|
410
|
+
if (!parentVar)
|
|
411
|
+
return null;
|
|
412
|
+
if (ctx.hydrate) {
|
|
413
|
+
ctx.push(`if (${slotProp} !== undefined && ${slotProp} !== null) {`);
|
|
414
|
+
ctx.push(` if (typeof ${slotProp} === 'function') {`);
|
|
415
|
+
ctx.push(` const __child = ${slotProp}(${ctx.walker});`);
|
|
416
|
+
ctx.push(` if (__child && typeof __child.then === 'function') __pendingChild = __child.then(() => ${parentVar});`);
|
|
417
|
+
ctx.push(` } else if (${slotProp}.nodeType !== undefined) {`);
|
|
418
|
+
ctx.push(` ${parentVar}.appendChild(${slotProp});`);
|
|
419
|
+
ctx.push(` } else {`);
|
|
420
|
+
ctx.push(` ${parentVar}.appendChild(document.createTextNode(String(${slotProp})));`);
|
|
421
|
+
ctx.push(` }`);
|
|
422
|
+
ctx.push(`}`);
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
ctx.push(`if (${slotProp} !== undefined && ${slotProp} !== null) {`);
|
|
426
|
+
ctx.push(` if (${slotProp}.nodeType !== undefined) ${parentVar}.appendChild(${slotProp});`);
|
|
427
|
+
ctx.push(` else ${parentVar}.appendChild(document.createTextNode(String(${slotProp})));`);
|
|
428
|
+
ctx.push(`}`);
|
|
429
|
+
}
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
375
432
|
return null;
|
|
376
433
|
}
|
|
377
434
|
const PROPERTY_ATTRS = {
|
|
@@ -408,9 +465,15 @@ function emitStatic(ctx, node, tracked, effectsVar) {
|
|
|
408
465
|
children.push(child);
|
|
409
466
|
}
|
|
410
467
|
}
|
|
468
|
+
const dynAttrNames = new Set();
|
|
469
|
+
for (const d of dynAttrs)
|
|
470
|
+
if (d.target)
|
|
471
|
+
dynAttrNames.add(d.target);
|
|
411
472
|
for (const attr of node.attributes) {
|
|
412
473
|
if (attr.name.startsWith('on') && attr.name.length > 2)
|
|
413
474
|
continue;
|
|
475
|
+
if (dynAttrNames.has(attr.name))
|
|
476
|
+
continue;
|
|
414
477
|
if (attr.value === '') {
|
|
415
478
|
ctx.push(`${el}.setAttribute(${JSON.stringify(attr.name)}, '');`);
|
|
416
479
|
}
|
|
@@ -418,9 +481,24 @@ function emitStatic(ctx, node, tracked, effectsVar) {
|
|
|
418
481
|
ctx.push(`${el}.setAttribute(${JSON.stringify(attr.name)}, ${JSON.stringify(escapeHtml(attr.value))});`);
|
|
419
482
|
}
|
|
420
483
|
}
|
|
484
|
+
// Hydrate-mode A5 snapshot: exactly one text-ish child that is a reactive
|
|
485
|
+
// dynamic binding gets initialized from the claimed parent's SSR text.
|
|
486
|
+
// Computed only in hydrate mode — normal mode pays zero for this.
|
|
487
|
+
let singleReactiveText = false;
|
|
488
|
+
if (ctx.hydrate) {
|
|
489
|
+
const textKids = children.filter((c) => c instanceof TextNode || (c instanceof DynamicBinding && c.kind === 'text'));
|
|
490
|
+
singleReactiveText = textKids.length === 1 && textKids[0] instanceof DynamicBinding
|
|
491
|
+
&& isReactiveExpression(textKids[0].expression, tracked);
|
|
492
|
+
}
|
|
421
493
|
let residueBefore = 0;
|
|
422
494
|
for (const child of children) {
|
|
423
|
-
|
|
495
|
+
let childVar;
|
|
496
|
+
if (singleReactiveText && child instanceof DynamicBinding && child.kind === 'text') {
|
|
497
|
+
childVar = emitDynamicBinding(ctx, child, tracked, effectsVar, `${el}.__vsk_ssrText`);
|
|
498
|
+
}
|
|
499
|
+
else {
|
|
500
|
+
childVar = emitNode(ctx, child, tracked, effectsVar, el);
|
|
501
|
+
}
|
|
424
502
|
if (childVar) {
|
|
425
503
|
if (ctx.hydrate) {
|
|
426
504
|
// Fresh text nodes (static or dynamic) cannot be appended at the end of
|
|
@@ -467,12 +545,22 @@ function emitStatic(ctx, node, tracked, effectsVar) {
|
|
|
467
545
|
}
|
|
468
546
|
ctx.push(`${el}.setAttribute('data-vsk-ev', '');`);
|
|
469
547
|
}
|
|
548
|
+
else if (target === 'style') {
|
|
549
|
+
const expr = transformTracked(attr.expression, tracked);
|
|
550
|
+
const eff = `effect(() => { applyStyle(${el}, ${expr}); })`;
|
|
551
|
+
if (effectsVar) {
|
|
552
|
+
ctx.push(`${effectsVar}.push(${eff});`);
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
ctx.effects.push(`${eff};`);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
470
558
|
else {
|
|
471
559
|
const expr = transformTracked(attr.expression, tracked);
|
|
472
560
|
const useProp = PROPERTY_ATTRS[node.tag]?.has(target);
|
|
473
561
|
const eff = useProp
|
|
474
562
|
? `effect(() => { ${el}.${target} = ${expr}; })`
|
|
475
|
-
: `effect(() => { ${el}.setAttribute(${JSON.stringify(target)}, String(
|
|
563
|
+
: `effect(() => { const __v = ${expr}; if (__v != null && __v !== false) ${el}.setAttribute(${JSON.stringify(target)}, __v === true ? 'true' : String(__v)); })`;
|
|
476
564
|
if (effectsVar) {
|
|
477
565
|
ctx.push(`${effectsVar}.push(${eff});`);
|
|
478
566
|
}
|
|
@@ -627,7 +715,7 @@ function isReactiveExpression(node, tracked) {
|
|
|
627
715
|
});
|
|
628
716
|
return reactive;
|
|
629
717
|
}
|
|
630
|
-
function emitDynamicBinding(ctx, node, tracked, effectsVar) {
|
|
718
|
+
function emitDynamicBinding(ctx, node, tracked, effectsVar, ssrTextExpr = null) {
|
|
631
719
|
if (node.kind === 'attribute')
|
|
632
720
|
return null;
|
|
633
721
|
const expr = transformTracked(node.expression, tracked);
|
|
@@ -636,7 +724,13 @@ function emitDynamicBinding(ctx, node, tracked, effectsVar) {
|
|
|
636
724
|
ctx.push(`const ${v} = document.createTextNode(String(${expr}));`);
|
|
637
725
|
return v;
|
|
638
726
|
}
|
|
639
|
-
|
|
727
|
+
// Hydrate-mode sole-dynamic-text snapshot (Hydrate-Todo A5): initialize from
|
|
728
|
+
// the claimed parent's stashed SSR text so a missed effect degrades to stale
|
|
729
|
+
// text instead of an empty element. The effect below still overwrites on its
|
|
730
|
+
// first run, so live values are unaffected. Fresh (unclaimed) parents stash
|
|
731
|
+
// nothing and the `?? ''` keeps the old empty initial.
|
|
732
|
+
const init = ssrTextExpr ? `String(${ssrTextExpr} ?? '')` : `''`;
|
|
733
|
+
ctx.push(`const ${v} = document.createTextNode(${init});`);
|
|
640
734
|
const eff = `effect(() => { ${v}.data = String(${expr}); })`;
|
|
641
735
|
if (effectsVar) {
|
|
642
736
|
ctx.push(`${effectsVar}.push(${eff});`);
|
|
@@ -646,6 +740,29 @@ function emitDynamicBinding(ctx, node, tracked, effectsVar) {
|
|
|
646
740
|
}
|
|
647
741
|
return v;
|
|
648
742
|
}
|
|
743
|
+
/**
|
|
744
|
+
* Emits a fresh DocumentFragment built from `nodes` (used for both the
|
|
745
|
+
* `children` of a component call and every named content slot). Children
|
|
746
|
+
* effects are collected into `effectsVar` (or `ctx.effects`) exactly as the
|
|
747
|
+
* component-call path does today, so slot bodies inherit identical effects,
|
|
748
|
+
* claim, and async semantics as regular children.
|
|
749
|
+
*/
|
|
750
|
+
function emitFragment(ctx, nodes, tracked, effectsVar) {
|
|
751
|
+
const frag = ctx.n();
|
|
752
|
+
const savedEffects = ctx.effects;
|
|
753
|
+
ctx.effects = [];
|
|
754
|
+
ctx.push(`const ${frag} = (() => { const $f = document.createDocumentFragment();`);
|
|
755
|
+
for (const child of nodes) {
|
|
756
|
+
const childVar = emitNode(ctx, child, tracked, effectsVar, '$f');
|
|
757
|
+
if (childVar)
|
|
758
|
+
ctx.push(`$f.appendChild(${childVar});`);
|
|
759
|
+
}
|
|
760
|
+
for (const eff of ctx.effects)
|
|
761
|
+
ctx.push(effectsVar ? `${effectsVar}.push(${effectBlockToHandlerExpr(eff)});` : eff);
|
|
762
|
+
ctx.effects = savedEffects;
|
|
763
|
+
ctx.push(`return $f; })();`);
|
|
764
|
+
return frag;
|
|
765
|
+
}
|
|
649
766
|
function emitComponentCall(ctx, node, tracked, effectsVar, parentVar, compPrefix = '__components') {
|
|
650
767
|
const propsEntries = node.props.map((p) => {
|
|
651
768
|
if (typeof p.value === 'string')
|
|
@@ -661,7 +778,6 @@ function emitComponentCall(ctx, node, tracked, effectsVar, parentVar, compPrefix
|
|
|
661
778
|
const expr = transformTracked(sp, tracked);
|
|
662
779
|
propsEntries.push(`...${expr}`);
|
|
663
780
|
}
|
|
664
|
-
const propsObj = `{ ${propsEntries.join(', ')} }`;
|
|
665
781
|
const v = ctx.n();
|
|
666
782
|
// Await children whenever the calling scope is async. Async-ness is
|
|
667
783
|
// parent-driven (mirroring the server side) so that imported async
|
|
@@ -671,6 +787,11 @@ function emitComponentCall(ctx, node, tracked, effectsVar, parentVar, compPrefix
|
|
|
671
787
|
// Member-expression tags (`<it.icon>`) carry the raw component-valued
|
|
672
788
|
// expression — invoke it directly; it is never a registry name.
|
|
673
789
|
const calleeExpr = node.calleeExpr ? `(${node.calleeExpr})` : null;
|
|
790
|
+
// Named content slots (`trigger={<Button/>}`) thread through the same channel
|
|
791
|
+
// as `children`: a fresh DocumentFragment per slot hoisted from the IR.
|
|
792
|
+
const slotChildren = node.children.filter((c) => c instanceof PropSlot);
|
|
793
|
+
const regChildren = node.children.filter((c) => !(c instanceof PropSlot));
|
|
794
|
+
const callArgs = () => `{ ${propsEntries.join(', ')} }`;
|
|
674
795
|
if (ctx.hydrate) {
|
|
675
796
|
const access = calleeExpr
|
|
676
797
|
?? (ctx.importedNames.has(node.componentName)
|
|
@@ -697,19 +818,20 @@ function emitComponentCall(ctx, node, tracked, effectsVar, parentVar, compPrefix
|
|
|
697
818
|
ctx.push(`${walkerArg}.retireDetached();`);
|
|
698
819
|
}
|
|
699
820
|
};
|
|
700
|
-
|
|
821
|
+
for (const slot of slotChildren) {
|
|
822
|
+
const frag = emitFragment(ctx, slot.body, tracked, effectsVar);
|
|
823
|
+
propsEntries.push(`${JSON.stringify(slot.propName)}: ${frag}`);
|
|
824
|
+
}
|
|
825
|
+
if (regChildren.length > 0) {
|
|
701
826
|
const frag = ctx.n();
|
|
702
|
-
ctx.push(`const ${frag} = (() => { const $f = document.createDocumentFragment();`);
|
|
703
|
-
const savedEffects = ctx.effects;
|
|
704
|
-
// Link/NavLink hydrate branches WIPE their SSR anchor (a.replaceChildren())
|
|
705
|
-
// and remount the children fragment. Suppressed fully-static children would
|
|
706
|
-
// therefore be lost, so rebuild the whole subtree fresh instead of claiming.
|
|
707
827
|
const wipeMount = ctx.linkNames.has(node.componentName);
|
|
708
828
|
const savedHydrate = ctx.hydrate;
|
|
709
829
|
if (wipeMount)
|
|
710
830
|
ctx.hydrate = false;
|
|
831
|
+
ctx.push(`const ${frag} = (() => { const $f = document.createDocumentFragment();`);
|
|
832
|
+
const savedEffects = ctx.effects;
|
|
711
833
|
ctx.effects = [];
|
|
712
|
-
for (const child of
|
|
834
|
+
for (const child of regChildren) {
|
|
713
835
|
const childVar = emitNode(ctx, child, tracked, effectsVar, '$f');
|
|
714
836
|
if (childVar)
|
|
715
837
|
ctx.push(`$f.appendChild(${childVar});`);
|
|
@@ -720,23 +842,23 @@ function emitComponentCall(ctx, node, tracked, effectsVar, parentVar, compPrefix
|
|
|
720
842
|
ctx.hydrate = savedHydrate;
|
|
721
843
|
ctx.push(`return $f; })();`);
|
|
722
844
|
propsEntries.push(`children: ${frag}`);
|
|
723
|
-
ctx.push(`const ${v} = ${awaitKw}${access}({ ${propsEntries.join(', ')} }, __registry, ${walkerArg});`);
|
|
724
|
-
maybeReplace(v);
|
|
725
|
-
maybeRetire();
|
|
726
|
-
return v;
|
|
727
845
|
}
|
|
728
|
-
ctx.push(`const ${v} = ${awaitKw}${access}(${
|
|
846
|
+
ctx.push(`const ${v} = ${awaitKw}${access}(${callArgs()}, __registry, ${walkerArg});`);
|
|
729
847
|
maybeReplace(v);
|
|
730
848
|
maybeRetire();
|
|
731
849
|
return v;
|
|
732
850
|
}
|
|
733
851
|
else {
|
|
734
|
-
|
|
852
|
+
for (const slot of slotChildren) {
|
|
853
|
+
const frag = emitFragment(ctx, slot.body, tracked, effectsVar);
|
|
854
|
+
propsEntries.push(`${JSON.stringify(slot.propName)}: ${frag}`);
|
|
855
|
+
}
|
|
856
|
+
if (regChildren.length > 0) {
|
|
735
857
|
const frag = ctx.n();
|
|
736
|
-
ctx.push(`const ${frag} = (() => { const $f = document.createDocumentFragment();`);
|
|
737
858
|
const savedEffects = ctx.effects;
|
|
738
859
|
ctx.effects = [];
|
|
739
|
-
|
|
860
|
+
ctx.push(`const ${frag} = (() => { const $f = document.createDocumentFragment();`);
|
|
861
|
+
for (const child of regChildren) {
|
|
740
862
|
const childVar = emitNode(ctx, child, tracked, effectsVar, '$f');
|
|
741
863
|
if (childVar)
|
|
742
864
|
ctx.push(`$f.appendChild(${childVar});`);
|
|
@@ -748,13 +870,13 @@ function emitComponentCall(ctx, node, tracked, effectsVar, parentVar, compPrefix
|
|
|
748
870
|
propsEntries.push(`children: ${frag}`);
|
|
749
871
|
}
|
|
750
872
|
if (calleeExpr) {
|
|
751
|
-
ctx.push(`const ${v} = ${calleeExpr}(
|
|
873
|
+
ctx.push(`const ${v} = ${calleeExpr}(${callArgs()});`);
|
|
752
874
|
}
|
|
753
875
|
else if (ctx.importedNames.has(node.componentName)) {
|
|
754
|
-
ctx.push(`const ${v} = ${awaitKw}${node.componentName}(
|
|
876
|
+
ctx.push(`const ${v} = ${awaitKw}${node.componentName}(${callArgs()});`);
|
|
755
877
|
}
|
|
756
878
|
else {
|
|
757
|
-
ctx.push(`const ${v} = ${awaitKw}${compPrefix}[${JSON.stringify(node.componentName)}](
|
|
879
|
+
ctx.push(`const ${v} = ${awaitKw}${compPrefix}[${JSON.stringify(node.componentName)}](${callArgs()});`);
|
|
758
880
|
}
|
|
759
881
|
}
|
|
760
882
|
return v;
|
|
@@ -1373,7 +1495,7 @@ function emitSwitchBlock(ctx, node, tracked, parentVar) {
|
|
|
1373
1495
|
}`);
|
|
1374
1496
|
return null;
|
|
1375
1497
|
}
|
|
1376
|
-
function emitMap(ctx, node, tracked, parentVar) {
|
|
1498
|
+
function emitMap(ctx, node, tracked, effTarget, parentVar) {
|
|
1377
1499
|
const arrExpr = transformTracked(node.expression, tracked);
|
|
1378
1500
|
const itemVar = node.itemVariable;
|
|
1379
1501
|
const anchor = ctx.n();
|
|
@@ -1393,6 +1515,26 @@ function emitMap(ctx, node, tracked, parentVar) {
|
|
|
1393
1515
|
// emitting a top-level effect there would reference the loop/statement local
|
|
1394
1516
|
// (`col`, `pages`) which is out of scope — `ReferenceError` at hydrate.
|
|
1395
1517
|
const reactiveSource = isReactiveExpression(node.expression, tracked);
|
|
1518
|
+
// The re-render effect below closes over the anchors/reconciler declared
|
|
1519
|
+
// inline above. When this map is nested inside a branch/item builder those
|
|
1520
|
+
// bindings live in that nested closure — so the effect must be created in
|
|
1521
|
+
// the same scope (registered on the enclosing effects array), not flushed
|
|
1522
|
+
// to the component top level where the names are out of scope
|
|
1523
|
+
// (`$nNN is not defined` at hydration, plus an esbuild shadowing rename).
|
|
1524
|
+
const pushEffect = (lets, effCall) => {
|
|
1525
|
+
if (effTarget) {
|
|
1526
|
+
ctx.push(`${effTarget}.push((() => {
|
|
1527
|
+
${lets}
|
|
1528
|
+
return ${effCall};
|
|
1529
|
+
})());`);
|
|
1530
|
+
}
|
|
1531
|
+
else {
|
|
1532
|
+
ctx.effects.push(`{
|
|
1533
|
+
${lets}
|
|
1534
|
+
${effCall};
|
|
1535
|
+
}`);
|
|
1536
|
+
}
|
|
1537
|
+
};
|
|
1396
1538
|
ctx.push(`const ${anchor} = document.createComment('map');`);
|
|
1397
1539
|
if (!hyd && !hydKeyed)
|
|
1398
1540
|
ctx.push(`${parent}.appendChild(${anchor});`);
|
|
@@ -1480,6 +1622,13 @@ function emitMap(ctx, node, tracked, parentVar) {
|
|
|
1480
1622
|
ctx.push(`const ${hasItems} = () => { const __l = ${arrExpr}; return __l != null && __l.length > 0; };`);
|
|
1481
1623
|
if (node.keyExpr) {
|
|
1482
1624
|
const keyExpr = transformTracked(node.keyExpr, tracked);
|
|
1625
|
+
// A `key` may reference the loop's `index` variable (`for (… ; key i; index i)`).
|
|
1626
|
+
// The key function is evaluated outside the item renderer (where the index is
|
|
1627
|
+
// in scope), so it must receive the index as its own parameter — otherwise the
|
|
1628
|
+
// emitted `item => i` key closes over an undefined `i` (ReferenceError).
|
|
1629
|
+
// Parenthesize the parameters: without them `item, i => …` parses as a comma
|
|
1630
|
+
// sequence expression rather than a two-parameter arrow in argument position.
|
|
1631
|
+
const keyFnSrc = `(${itemVar}${node.indexVariable ? `, ${node.indexVariable}` : ''}) => ${keyExpr}`;
|
|
1483
1632
|
const reconciler = ctx.n();
|
|
1484
1633
|
const initList = ctx.n();
|
|
1485
1634
|
ctx.push(`let ${reconciler} = () => {};`);
|
|
@@ -1488,10 +1637,10 @@ function emitMap(ctx, node, tracked, parentVar) {
|
|
|
1488
1637
|
// inside `parent` (pre-attach, `anchor.parentNode` is null) and peeks all
|
|
1489
1638
|
// keys before claim-by-key consumes the walker, so server-rendered items
|
|
1490
1639
|
// are adopted and wired in place instead of being rebuilt.
|
|
1491
|
-
ctx.push(`const ${initList} = () => { ${reconciler} = reconcileHydrated(${anchor}, ${endAnchor}, ${arrExpr}, ${
|
|
1640
|
+
ctx.push(`const ${initList} = () => { ${reconciler} = reconcileHydrated(${anchor}, ${endAnchor}, ${arrExpr}, ${keyFnSrc}, (${itemVar}, __i, __e, __root) => ${renderItem}(${itemVar}${indexParam ? ', __i, __e' : ', __e'}, null, __root), __hydrate, ${parent}); };`);
|
|
1492
1641
|
}
|
|
1493
1642
|
else {
|
|
1494
|
-
ctx.push(`const ${initList} = () => { ${reconciler} = reconcile(${anchor}, ${endAnchor}, ${arrExpr}, ${
|
|
1643
|
+
ctx.push(`const ${initList} = () => { ${reconciler} = reconcile(${anchor}, ${endAnchor}, ${arrExpr}, ${keyFnSrc}, (${itemVar}, __i, __e) => ${renderItem}(${itemVar}${indexParam ? ', __i, __e' : ', __e'})); };`);
|
|
1495
1644
|
}
|
|
1496
1645
|
if (emptyRenderName) {
|
|
1497
1646
|
const isEmptyVar = ctx.n();
|
|
@@ -1499,9 +1648,7 @@ function emitMap(ctx, node, tracked, parentVar) {
|
|
|
1499
1648
|
ctx.push(`if (!${isEmptyVar}) { ${initList}(); } else { ${aw}${emptyRenderName}(); }`);
|
|
1500
1649
|
if (!reactiveSource)
|
|
1501
1650
|
return null;
|
|
1502
|
-
|
|
1503
|
-
let __first = true;
|
|
1504
|
-
effect(${effOpen}
|
|
1651
|
+
pushEffect(`let __first = true;`, `effect(${effOpen}
|
|
1505
1652
|
const __new = ${hasItems}();
|
|
1506
1653
|
if (__first) { __first = false; __new; return; }
|
|
1507
1654
|
if (__new !== ${isEmptyVar}) {
|
|
@@ -1513,21 +1660,17 @@ function emitMap(ctx, node, tracked, parentVar) {
|
|
|
1513
1660
|
${effectsVar}.length = 0;
|
|
1514
1661
|
__cleanup(${anchor}, ${endAnchor});
|
|
1515
1662
|
if (__new) { ${initList}(); } else { ${aw}${emptyRenderName}(); }
|
|
1516
|
-
});
|
|
1517
|
-
}`);
|
|
1663
|
+
})`);
|
|
1518
1664
|
}
|
|
1519
1665
|
else {
|
|
1520
1666
|
ctx.push(`${initList}();`);
|
|
1521
1667
|
if (!reactiveSource)
|
|
1522
1668
|
return null;
|
|
1523
|
-
|
|
1524
|
-
let __first = true;
|
|
1525
|
-
effect(() => {
|
|
1669
|
+
pushEffect(`let __first = true;`, `effect(() => {
|
|
1526
1670
|
const __nv = ${arrExpr};
|
|
1527
1671
|
if (__first) { __first = false; __nv; ${reconciler}(${arrExpr}); return; }
|
|
1528
1672
|
${reconciler}(${arrExpr});
|
|
1529
|
-
});
|
|
1530
|
-
}`);
|
|
1673
|
+
})`);
|
|
1531
1674
|
}
|
|
1532
1675
|
}
|
|
1533
1676
|
else {
|
|
@@ -1560,9 +1703,7 @@ function emitMap(ctx, node, tracked, parentVar) {
|
|
|
1560
1703
|
ctx.push(`__place(${anchor}, ${endAnchor}, ${collectVar}, ${parent});`);
|
|
1561
1704
|
if (!reactiveSource)
|
|
1562
1705
|
return null;
|
|
1563
|
-
|
|
1564
|
-
let __first = true;
|
|
1565
|
-
effect(${effOpen}
|
|
1706
|
+
pushEffect(`let __first = true;`, `effect(${effOpen}
|
|
1566
1707
|
const __new = ${hasItems}();
|
|
1567
1708
|
if (__first) { __first = false; __new; return; }
|
|
1568
1709
|
if (__new !== ${isEmptyVar}) {
|
|
@@ -1581,8 +1722,7 @@ ${renderAllItems(' ', null)}
|
|
|
1581
1722
|
if (__new) {
|
|
1582
1723
|
${renderAllItems(' ', null)}
|
|
1583
1724
|
} else { ${aw}${emptyRenderName}(${collectVar ? collectVar : ''}); }
|
|
1584
|
-
});
|
|
1585
|
-
}`);
|
|
1725
|
+
})`);
|
|
1586
1726
|
}
|
|
1587
1727
|
else {
|
|
1588
1728
|
const collectVar = hyd ? ctx.n() : null;
|
|
@@ -1593,17 +1733,14 @@ ${renderAllItems(' ', null)}
|
|
|
1593
1733
|
ctx.push(`__place(${anchor}, ${endAnchor}, ${collectVar}, ${parent});`);
|
|
1594
1734
|
if (!reactiveSource)
|
|
1595
1735
|
return null;
|
|
1596
|
-
|
|
1597
|
-
let __first = true;
|
|
1598
|
-
effect(${effOpen}
|
|
1736
|
+
pushEffect(`let __first = true;`, `effect(${effOpen}
|
|
1599
1737
|
const __nv = ${arrExpr};
|
|
1600
1738
|
if (__first) { __first = false; __nv; return; }
|
|
1601
1739
|
for (const e of ${effectsVar}) destroy_block(e);
|
|
1602
1740
|
${effectsVar}.length = 0;
|
|
1603
1741
|
__cleanup(${anchor}, ${endAnchor});
|
|
1604
1742
|
${renderAllItems(' ', null)}
|
|
1605
|
-
});
|
|
1606
|
-
}`);
|
|
1743
|
+
})`);
|
|
1607
1744
|
}
|
|
1608
1745
|
}
|
|
1609
1746
|
return null;
|
|
@@ -1611,9 +1748,9 @@ ${renderAllItems(' ', null)}
|
|
|
1611
1748
|
function computeAsyncComponents(comps) {
|
|
1612
1749
|
return new Set(comps.filter((c) => c.isAsync).map((c) => c.name));
|
|
1613
1750
|
}
|
|
1614
|
-
function generateComponent(comp, importedNames = new Set(), hydrate = false, asyncComps = new Set(), linkNames = new Set(), selfClaimNames = new Set()) {
|
|
1751
|
+
function generateComponent(comp, importedNames = new Set(), hydrate = false, asyncComps = new Set(), linkNames = new Set(), selfClaimNames = new Set(), alloc) {
|
|
1615
1752
|
const tracked = collectTrackedNames(comp.body);
|
|
1616
|
-
const ctx = new Ctx();
|
|
1753
|
+
const ctx = new Ctx(alloc);
|
|
1617
1754
|
ctx.importedNames = importedNames;
|
|
1618
1755
|
ctx.linkNames = linkNames;
|
|
1619
1756
|
ctx.selfClaimNames = selfClaimNames;
|
|
@@ -1632,7 +1769,16 @@ function generateComponent(comp, importedNames = new Set(), hydrate = false, asy
|
|
|
1632
1769
|
ctx.push(indent(`}`));
|
|
1633
1770
|
}
|
|
1634
1771
|
if (ctx.hydrate) {
|
|
1772
|
+
// Two distinct roles must not be conflated:
|
|
1773
|
+
// - `$root` is the shared page container. It is only ever a last-resort
|
|
1774
|
+
// mount target for detached dynamic regions (`__place`) that could not
|
|
1775
|
+
// be claimed in place; it must never be handed back to a caller.
|
|
1776
|
+
// - `$mount` is THIS component's own first top-level node. It is what the
|
|
1777
|
+
// hydrator returns so the parent can position/guard the component. A
|
|
1778
|
+
// component's own node is never an ancestor of the caller, so the
|
|
1779
|
+
// parent's `contains()` guards and `__place` end-anchor bounds are valid.
|
|
1635
1780
|
ctx.push(indent(`const $root = __hydrate.root;`));
|
|
1781
|
+
ctx.push(indent(`let $mount = null;`));
|
|
1636
1782
|
}
|
|
1637
1783
|
else {
|
|
1638
1784
|
ctx.push(indent(`const $root = document.createDocumentFragment();`));
|
|
@@ -1653,7 +1799,11 @@ function generateComponent(comp, importedNames = new Set(), hydrate = false, asy
|
|
|
1653
1799
|
const v = emitNode(ctx, node, tracked, null);
|
|
1654
1800
|
if (v) {
|
|
1655
1801
|
if (ctx.hydrate) {
|
|
1656
|
-
|
|
1802
|
+
// The first top-level node becomes `$mount`. Late top-level nodes are
|
|
1803
|
+
// already in the DOM when claimed; only a divergent fresh node is
|
|
1804
|
+
// nested onto the mount (matching the pre-marker wrapper behaviour).
|
|
1805
|
+
ctx.push(indent(`if ($mount === null) $mount = ${v};`));
|
|
1806
|
+
ctx.push(indent(`if (${v} !== $mount && ${v}.parentNode == null) $mount.appendChild(${v});`));
|
|
1657
1807
|
}
|
|
1658
1808
|
else {
|
|
1659
1809
|
ctx.push(indent(`$root.appendChild(${v});`));
|
|
@@ -1666,7 +1816,15 @@ function generateComponent(comp, importedNames = new Set(), hydrate = false, asy
|
|
|
1666
1816
|
const delCode = ctx.emitDelegates();
|
|
1667
1817
|
if (delCode)
|
|
1668
1818
|
ctx.push(indent(delCode.trim()));
|
|
1669
|
-
ctx.
|
|
1819
|
+
if (ctx.hydrate) {
|
|
1820
|
+
// A hydrator with no claimable nodes must still hand its caller a value the
|
|
1821
|
+
// parent guards can safely inspect (an empty fragment places nothing).
|
|
1822
|
+
ctx.push(indent(`if ($mount === null) $mount = document.createDocumentFragment();`));
|
|
1823
|
+
ctx.push(indent(`return __pendingChild || $mount;`));
|
|
1824
|
+
}
|
|
1825
|
+
else {
|
|
1826
|
+
ctx.push(indent(`return __pendingChild || $root;`));
|
|
1827
|
+
}
|
|
1670
1828
|
ctx.push(indent(`} finally {`));
|
|
1671
1829
|
ctx.push(indent(`\tsetActiveComponent(__prev);`));
|
|
1672
1830
|
ctx.push(indent(`}`));
|
|
@@ -1680,7 +1838,7 @@ function buildParamInit(paramNames) {
|
|
|
1680
1838
|
return '';
|
|
1681
1839
|
return `const { ${paramNames.join(', ')} } = props;`;
|
|
1682
1840
|
}
|
|
1683
|
-
function buildComponentMap(irRoot, hydrate = false) {
|
|
1841
|
+
function buildComponentMap(irRoot, hydrate = false, alloc) {
|
|
1684
1842
|
const mapLines = [];
|
|
1685
1843
|
mapLines.push(`const __components = {};`);
|
|
1686
1844
|
const asyncComps = computeAsyncComponents(irRoot.components);
|
|
@@ -1721,7 +1879,7 @@ function buildComponentMap(irRoot, hydrate = false) {
|
|
|
1721
1879
|
mapLines.push(`__components[${JSON.stringify(comp.name)}] = ${stub};`);
|
|
1722
1880
|
continue;
|
|
1723
1881
|
}
|
|
1724
|
-
const code = generateComponent(comp, directNames, hydrate, asyncComps, linkNames, selfClaimNames);
|
|
1882
|
+
const code = generateComponent(comp, directNames, hydrate, asyncComps, linkNames, selfClaimNames, alloc);
|
|
1725
1883
|
mapLines.push(`__components[${JSON.stringify(comp.name)}] = ${code};`);
|
|
1726
1884
|
}
|
|
1727
1885
|
mapLines.push(`function __cleanup(start, end) {`);
|
|
@@ -1879,6 +2037,10 @@ function findBindingInIR(nodes, names) {
|
|
|
1879
2037
|
if (findBindingInIR(node.children, names))
|
|
1880
2038
|
return true;
|
|
1881
2039
|
}
|
|
2040
|
+
else if (node instanceof PropSlot) {
|
|
2041
|
+
if (findBindingInIR(node.body, names))
|
|
2042
|
+
return true;
|
|
2043
|
+
}
|
|
1882
2044
|
else if (node instanceof RuntimeStatement) {
|
|
1883
2045
|
if (astHasBinding(node.ast, names))
|
|
1884
2046
|
return true;
|
|
@@ -1907,6 +2069,10 @@ function findFormNameInIR(nodes, name) {
|
|
|
1907
2069
|
if (findFormNameInIR(node.children, name))
|
|
1908
2070
|
return true;
|
|
1909
2071
|
}
|
|
2072
|
+
if (node instanceof PropSlot) {
|
|
2073
|
+
if (findFormNameInIR(node.body, name))
|
|
2074
|
+
return true;
|
|
2075
|
+
}
|
|
1910
2076
|
if (node instanceof DynamicBinding && node.expression && node.expression.raw) {
|
|
1911
2077
|
if (node.expression.raw.includes(fnPattern))
|
|
1912
2078
|
return true;
|
|
@@ -1985,7 +2151,7 @@ function emitClientFromIR(ir, options) {
|
|
|
1985
2151
|
if (!options.forceClient && !needsClient) {
|
|
1986
2152
|
return '';
|
|
1987
2153
|
}
|
|
1988
|
-
const componentMapCode = buildComponentMap(ir, options.hydrate);
|
|
2154
|
+
const componentMapCode = buildComponentMap(ir, options.hydrate, options.nameAllocator);
|
|
1989
2155
|
const importLines = ir.imports.length > 0 ? ir.imports.join('\n') + '\n' : '';
|
|
1990
2156
|
const topCode = (options.includeTopLevel === false ? [] : transformTopLevelForActions(ir.topLevelCode, 'client')).join('\n') + '\n';
|
|
1991
2157
|
const exportLines = [];
|
|
@@ -2000,7 +2166,7 @@ function emitClientFromIR(ir, options) {
|
|
|
2000
2166
|
}
|
|
2001
2167
|
}
|
|
2002
2168
|
const exportCode = exportLines.join('\n');
|
|
2003
|
-
const runtimeNames = ['track', 'get', 'set', 'destroy_block', 'getActiveComponent', 'setActiveComponent', 'reactiveProps'];
|
|
2169
|
+
const runtimeNames = ['track', 'get', 'set', 'destroy_block', 'getActiveComponent', 'setActiveComponent', 'reactiveProps', 'applyStyle'];
|
|
2004
2170
|
if (ir.components.some(c => !isStaticIR(c.body)))
|
|
2005
2171
|
runtimeNames.push('effect');
|
|
2006
2172
|
for (const name of usedRuntimeBindings(ir))
|
|
@@ -2051,7 +2217,7 @@ export function compileClient(source, _componentName, options = {}) {
|
|
|
2051
2217
|
}
|
|
2052
2218
|
const ast = parse(source, options.sourcePath ? { filename: options.sourcePath } : {});
|
|
2053
2219
|
const ir = generateIR(ast, source, options.sourcePath);
|
|
2054
|
-
return emitClientFromIR(ir, options);
|
|
2220
|
+
return emitClientFromIR(ir, { ...options, nameAllocator: options.nameAllocator ?? nameAllocFor(options.sourcePath) });
|
|
2055
2221
|
}
|
|
2056
2222
|
/**
|
|
2057
2223
|
* Compiles a component source in BOTH client modes (plain + hydrate)
|
|
@@ -2080,9 +2246,12 @@ export function compileClientBoth(source, _componentName, sourcePath) {
|
|
|
2080
2246
|
if (exportedComp)
|
|
2081
2247
|
name = exportedComp.name;
|
|
2082
2248
|
}
|
|
2249
|
+
// Comp and hyd land in the same scoped file block, so they share one
|
|
2250
|
+
// allocator: hyd continues after the names comp already consumed.
|
|
2251
|
+
const alloc = nameAllocFor(sourcePath);
|
|
2083
2252
|
return {
|
|
2084
|
-
comp: emitClientFromIR(ir, { forceClient: true }),
|
|
2085
|
-
hyd: emitClientFromIR(irHyd, { forceClient: true, hydrate: true, includeTopLevel: false }),
|
|
2253
|
+
comp: emitClientFromIR(ir, { forceClient: true, nameAllocator: alloc }),
|
|
2254
|
+
hyd: emitClientFromIR(irHyd, { forceClient: true, hydrate: true, includeTopLevel: false, nameAllocator: alloc }),
|
|
2086
2255
|
name,
|
|
2087
2256
|
};
|
|
2088
2257
|
}
|
package/dist/errors.d.ts
CHANGED
|
@@ -28,6 +28,9 @@ export declare class VeskError extends Error {
|
|
|
28
28
|
static componentNotFound(name: string, available?: string[], context?: VeskErrorOptions): VeskError;
|
|
29
29
|
static configError(msg: string, validOptions?: string[], context?: VeskErrorOptions): VeskError;
|
|
30
30
|
static asyncChildInSyncParent(parentName: string, childName: string, context?: VeskErrorOptions): VeskError;
|
|
31
|
+
static attrJsxElement(context?: VeskErrorOptions & {
|
|
32
|
+
attr?: string;
|
|
33
|
+
}): VeskError;
|
|
31
34
|
toString(): string;
|
|
32
35
|
toJSON(): Record<string, unknown>;
|
|
33
36
|
}
|