@vesk/compiler 0.2.16 → 0.2.18
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 +8 -0
- package/dist/client-codegen.d.ts.map +1 -1
- package/dist/client-codegen.js +144 -34
- package/dist/ir-generator.d.ts.map +1 -1
- package/dist/ir-generator.js +31 -0
- package/dist/module-imports.d.ts.map +1 -1
- package/dist/module-imports.js +256 -15
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +29 -0
- package/dist/server-jsgen.d.ts.map +1 -1
- package/dist/server-jsgen.js +21 -8
- package/dist/vesk-plugin.d.ts.map +1 -1
- package/dist/vesk-plugin.js +26 -15
- package/package.json +2 -2
package/dist/client-codegen.d.ts
CHANGED
|
@@ -8,6 +8,14 @@ export interface TrackedInfo {
|
|
|
8
8
|
export declare function semicolonizeStatement(code: string): string;
|
|
9
9
|
export declare function transformTracked(irNode: Expression | RuntimeStatement | DynamicBinding, tracked: Map<string, TrackedInfo>): string;
|
|
10
10
|
export declare function collectTrackedNames(body: IRNode[]): Map<string, TrackedInfo>;
|
|
11
|
+
/**
|
|
12
|
+
* Tracked reads inside a track/derived declaration init are re-emitted as
|
|
13
|
+
* `get(…)` subscriptions so a `derived(() => … cell …)` (or
|
|
14
|
+
* `track(() => … cell …)`) recomputes when the cell changes, instead of
|
|
15
|
+
* reading the raw cell object as a key/property. The init is parsed with the
|
|
16
|
+
* normal vesk parser so TS generics and `.vsk` syntax survive.
|
|
17
|
+
*/
|
|
18
|
+
export declare function transformTrackedInit(init: string, tracked: Map<string, TrackedInfo>): string;
|
|
11
19
|
declare function isStaticIR(body: IRNode[]): boolean;
|
|
12
20
|
export declare function escapeHtml(str: string): string;
|
|
13
21
|
export declare function compileClient(source: string, _componentName: string | null, options?: {
|
|
@@ -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,EAOjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAwCxD,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;
|
|
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,EAOjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAwCxD,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;AA0pCD,iBAAS,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAe3C;AA2HD,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO9C;AAoED,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,CAAA;CAAO,GAAG,MAAM,CAOnN;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,CAqBxJ;AAED,OAAO,EAAE,aAAa,IAAI,OAAO,EAAE,UAAU,EAAE,CAAC"}
|
package/dist/client-codegen.js
CHANGED
|
@@ -208,6 +208,29 @@ export function collectTrackedNames(body) {
|
|
|
208
208
|
}
|
|
209
209
|
return names;
|
|
210
210
|
}
|
|
211
|
+
/**
|
|
212
|
+
* Tracked reads inside a track/derived declaration init are re-emitted as
|
|
213
|
+
* `get(…)` subscriptions so a `derived(() => … cell …)` (or
|
|
214
|
+
* `track(() => … cell …)`) recomputes when the cell changes, instead of
|
|
215
|
+
* reading the raw cell object as a key/property. The init is parsed with the
|
|
216
|
+
* normal vesk parser so TS generics and `.vsk` syntax survive.
|
|
217
|
+
*/
|
|
218
|
+
export function transformTrackedInit(init, tracked) {
|
|
219
|
+
if (!init)
|
|
220
|
+
return init;
|
|
221
|
+
let expr = null;
|
|
222
|
+
try {
|
|
223
|
+
const prog = parse(`(${init})`);
|
|
224
|
+
expr = prog.body?.[0]?.type === 'ExpressionStatement' ? (prog.body[0].expression ?? null) : null;
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
// Unparseable init (defensive) — leave raw; the generated code is checked
|
|
228
|
+
// for syntax at build time and would be reported there.
|
|
229
|
+
}
|
|
230
|
+
if (!expr)
|
|
231
|
+
return init;
|
|
232
|
+
return transformTracked({ raw: init, ast: expr }, tracked);
|
|
233
|
+
}
|
|
211
234
|
const NON_BUBBLING_EVENTS = new Set([
|
|
212
235
|
'focus', 'blur',
|
|
213
236
|
'mouseenter', 'mouseleave',
|
|
@@ -229,6 +252,7 @@ class Ctx {
|
|
|
229
252
|
effects = [];
|
|
230
253
|
c = 0;
|
|
231
254
|
importedNames = new Set();
|
|
255
|
+
linkNames = new Set();
|
|
232
256
|
delegatedEvents = new Set();
|
|
233
257
|
directEvents = new Set();
|
|
234
258
|
hydrate = false;
|
|
@@ -281,7 +305,7 @@ function emitNode(ctx, node, tracked, effectsVar, parentVar, compPrefix = '__com
|
|
|
281
305
|
return emitDynamicBinding(ctx, node, tracked, effectsVar);
|
|
282
306
|
if (node instanceof TrackDecl) {
|
|
283
307
|
const cellName = node.rawName || node.name;
|
|
284
|
-
const init = stripTrackGeneric(node.init);
|
|
308
|
+
const init = transformTrackedInit(stripTrackGeneric(node.init), tracked);
|
|
285
309
|
ctx.push(`const ${cellName} = ${init};`);
|
|
286
310
|
return null;
|
|
287
311
|
}
|
|
@@ -621,6 +645,13 @@ function emitComponentCall(ctx, node, tracked, effectsVar, parentVar, compPrefix
|
|
|
621
645
|
ctx.push(`const ${frag} = (() => { const $f = document.createDocumentFragment();`);
|
|
622
646
|
const savedWalker = ctx.walker;
|
|
623
647
|
const savedEffects = ctx.effects;
|
|
648
|
+
// Link/NavLink hydrate branches WIPE their SSR anchor (a.replaceChildren())
|
|
649
|
+
// and remount the children fragment. Suppressed fully-static children would
|
|
650
|
+
// therefore be lost, so rebuild the whole subtree fresh instead of claiming.
|
|
651
|
+
const wipeMount = ctx.linkNames.has(node.componentName);
|
|
652
|
+
const savedHydrate = ctx.hydrate;
|
|
653
|
+
if (wipeMount)
|
|
654
|
+
ctx.hydrate = false;
|
|
624
655
|
ctx.walker = walkerVar;
|
|
625
656
|
ctx.effects = [];
|
|
626
657
|
for (const child of node.children) {
|
|
@@ -632,6 +663,7 @@ function emitComponentCall(ctx, node, tracked, effectsVar, parentVar, compPrefix
|
|
|
632
663
|
ctx.push(effectsVar ? `${effectsVar}.push(${effectBlockToHandlerExpr(eff)});` : eff);
|
|
633
664
|
ctx.effects = savedEffects;
|
|
634
665
|
ctx.walker = savedWalker;
|
|
666
|
+
ctx.hydrate = savedHydrate;
|
|
635
667
|
ctx.push(`return $f; })();`);
|
|
636
668
|
propsEntries.push(`children: ${frag}`);
|
|
637
669
|
ctx.push(`const ${v} = ${awaitKw}${access}({ ${propsEntries.join(', ')} }, __registry, ${walkerVar});`);
|
|
@@ -697,7 +729,7 @@ function emitTryCatch(ctx, node, tracked, effectsVar, parentVar) {
|
|
|
697
729
|
ctx.push(indent(`const __cl = [];`));
|
|
698
730
|
}
|
|
699
731
|
for (const child of body) {
|
|
700
|
-
const childVar = emitNode(ctx, child, tracked, isCatch ? null : effArr, undefined, compPrefix);
|
|
732
|
+
const childVar = emitNode(ctx, child, tracked, isCatch ? null : effArr, hydMode ? undefined : '__p', compPrefix);
|
|
701
733
|
if (childVar) {
|
|
702
734
|
if (hydMode)
|
|
703
735
|
ctx.push(indent(`__cl.push(${childVar});`));
|
|
@@ -817,7 +849,7 @@ function emitOpaque(ctx, node, tracked, effTarget, parentVar) {
|
|
|
817
849
|
ctx.push(`if (${condExpr}) { ${asyncKw}${conRenderName}(); }` + (hasElse ? ` else { ${asyncKw}${altRenderName}(); }` : ''));
|
|
818
850
|
const renderBranch = `if (__nv) { ${asyncKw}${conRenderName}(); }` + (hasElse ? ` else { ${asyncKw}${altRenderName}(); }` : '');
|
|
819
851
|
const effectBody = `effect(${ctx.isAsyncScope ? 'async () => {' : '() => {'}
|
|
820
|
-
if (__first) { __first = false; return; }
|
|
852
|
+
if (__first) { __first = false; __iv = ${condExpr}; return; }
|
|
821
853
|
const __nv = ${condExpr};
|
|
822
854
|
if (__nv !== __iv) {
|
|
823
855
|
for (const e of ${effectsVar}) destroy_block(e);
|
|
@@ -827,6 +859,11 @@ function emitOpaque(ctx, node, tracked, effTarget, parentVar) {
|
|
|
827
859
|
__iv = __nv;
|
|
828
860
|
}
|
|
829
861
|
});`;
|
|
862
|
+
// A static condition renders once; an effect referencing it would only ever
|
|
863
|
+
// reference statement/loop-local bindings that are out of scope at the flush
|
|
864
|
+
// site (e.g. `const pages = …; if (pages.length > 0)` inside a for-of body).
|
|
865
|
+
if (!isReactiveExpression(node.condition, tracked))
|
|
866
|
+
return null;
|
|
830
867
|
if (effTarget) {
|
|
831
868
|
ctx.push(`${effTarget}.push((() => {
|
|
832
869
|
let __iv = ${condExpr};
|
|
@@ -859,17 +896,19 @@ function emitWhileLoop(ctx, node, tracked, parentVar) {
|
|
|
859
896
|
ctx.push(`const ${renderLoop} = ${ctx.isAsyncScope ? 'async () => {' : '() => {'}`);
|
|
860
897
|
if (!hyd)
|
|
861
898
|
ctx.push(indent(`const __p = ${anchor}.parentNode;`));
|
|
899
|
+
if (!hyd)
|
|
900
|
+
ctx.push(indent(`const __b = document.createDocumentFragment();`));
|
|
862
901
|
if (hyd)
|
|
863
902
|
ctx.push(indent(`const __cl = [];`));
|
|
864
903
|
if (node.isDoWhile) {
|
|
865
904
|
ctx.push(indent(`do {`));
|
|
866
905
|
for (const n of node.bodyTemplate) {
|
|
867
|
-
const v = emitNode(ctx, n, tracked, effectsVar, hyd ? parentVar :
|
|
906
|
+
const v = emitNode(ctx, n, tracked, effectsVar, hyd ? parentVar : '__b');
|
|
868
907
|
if (v) {
|
|
869
908
|
if (hyd)
|
|
870
909
|
ctx.push(indent(`__cl.push(${v});`));
|
|
871
910
|
else
|
|
872
|
-
ctx.push(indent(`
|
|
911
|
+
ctx.push(indent(`__b.appendChild(${v});`));
|
|
873
912
|
}
|
|
874
913
|
}
|
|
875
914
|
ctx.push(indent(`} while (${condExpr});`));
|
|
@@ -877,16 +916,18 @@ function emitWhileLoop(ctx, node, tracked, parentVar) {
|
|
|
877
916
|
else {
|
|
878
917
|
ctx.push(indent(`while (${condExpr}) {`));
|
|
879
918
|
for (const n of node.bodyTemplate) {
|
|
880
|
-
const v = emitNode(ctx, n, tracked, effectsVar, hyd ? parentVar :
|
|
919
|
+
const v = emitNode(ctx, n, tracked, effectsVar, hyd ? parentVar : '__b');
|
|
881
920
|
if (v) {
|
|
882
921
|
if (hyd)
|
|
883
922
|
ctx.push(indent(`__cl.push(${v});`));
|
|
884
923
|
else
|
|
885
|
-
ctx.push(indent(`
|
|
924
|
+
ctx.push(indent(`__b.appendChild(${v});`));
|
|
886
925
|
}
|
|
887
926
|
}
|
|
888
927
|
ctx.push(indent(`}`));
|
|
889
928
|
}
|
|
929
|
+
if (!hyd)
|
|
930
|
+
ctx.push(indent(`__p.insertBefore(__b, ${endAnchor});`));
|
|
890
931
|
if (hyd)
|
|
891
932
|
ctx.push(indent(`__place(${anchor}, ${endAnchor}, __cl, ${parent});`));
|
|
892
933
|
ctx.push(`};`);
|
|
@@ -898,7 +939,7 @@ function emitWhileLoop(ctx, node, tracked, parentVar) {
|
|
|
898
939
|
let __iv = (${condExpr});
|
|
899
940
|
let __first = true;
|
|
900
941
|
effect(${ctx.isAsyncScope ? 'async () => {' : '() => {'}
|
|
901
|
-
if (__first) { __first = false; return; }
|
|
942
|
+
if (__first) { __first = false; __iv = (${condExpr}); return; }
|
|
902
943
|
if (__busy) return;
|
|
903
944
|
const __nv = (${condExpr});
|
|
904
945
|
if (__nv !== __iv) {
|
|
@@ -929,40 +970,55 @@ function emitForLoop(ctx, node, tracked, parentVar) {
|
|
|
929
970
|
ctx.push(`const ${renderLoop} = ${ctx.isAsyncScope ? 'async () => {' : '() => {'}`);
|
|
930
971
|
if (!hyd)
|
|
931
972
|
ctx.push(indent(`const __p = ${anchor}.parentNode;`));
|
|
973
|
+
if (!hyd)
|
|
974
|
+
ctx.push(indent(`const __b = document.createDocumentFragment();`));
|
|
932
975
|
if (hyd)
|
|
933
976
|
ctx.push(indent(`const __cl = [];`));
|
|
934
977
|
if (node.kind === 'for-in') {
|
|
935
978
|
const srcExpr = transformTracked(node.condition, tracked);
|
|
936
979
|
ctx.push(indent(`for (${node.init} of (Array.isArray(${srcExpr}) ? ${srcExpr} : (${srcExpr} == null ? [] : Object.keys(${srcExpr})))) {`));
|
|
937
980
|
for (const n of node.bodyTemplate) {
|
|
938
|
-
const v = emitNode(ctx, n, tracked, effectsVar, hyd ? parentVar :
|
|
981
|
+
const v = emitNode(ctx, n, tracked, effectsVar, hyd ? parentVar : '__b');
|
|
939
982
|
if (v) {
|
|
940
983
|
if (hyd)
|
|
941
984
|
ctx.push(indent(`__cl.push(${v});`));
|
|
942
985
|
else
|
|
943
|
-
ctx.push(indent(`
|
|
986
|
+
ctx.push(indent(`__b.appendChild(${v});`));
|
|
944
987
|
}
|
|
945
988
|
}
|
|
946
989
|
ctx.push(indent(`}`));
|
|
947
990
|
}
|
|
948
991
|
else {
|
|
949
992
|
const condExpr = transformTracked(node.condition, tracked);
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
993
|
+
// Emit a real `for` statement. A `let`/`const` declared in the init clause
|
|
994
|
+
// gets a fresh binding per iteration (per the spec), so closures created in
|
|
995
|
+
// the body — event handlers, effects, `createComment('if')` render fns —
|
|
996
|
+
// capture that iteration's value instead of the loop variable's final value.
|
|
997
|
+
// The old hoisted `let i = 0; while (…) { …; i++; }` shape made every
|
|
998
|
+
// closure alias-exit to the final counter (e.g. `onClick={() => index = i}`
|
|
999
|
+
// inside `for (let i = 0; i < n; i++)` always wrote n-last, and
|
|
1000
|
+
// `aria-pressed={i === index}` compared against n-last for every button).
|
|
1001
|
+
let initPart = (node.init || '').trim();
|
|
1002
|
+
let updatePart = (node.update || '').trim();
|
|
1003
|
+
if (initPart.endsWith(';'))
|
|
1004
|
+
initPart = initPart.slice(0, -1).trim();
|
|
1005
|
+
if (updatePart.endsWith(';'))
|
|
1006
|
+
updatePart = updatePart.slice(0, -1).trim();
|
|
1007
|
+
const head = initPart ? `${initPart}; ${condExpr}` : `; ${condExpr}`;
|
|
1008
|
+
ctx.push(indent(`for (${head}; ${updatePart}) {`));
|
|
953
1009
|
for (const n of node.bodyTemplate) {
|
|
954
|
-
const v = emitNode(ctx, n, tracked, effectsVar, hyd ? parentVar :
|
|
1010
|
+
const v = emitNode(ctx, n, tracked, effectsVar, hyd ? parentVar : '__b');
|
|
955
1011
|
if (v) {
|
|
956
1012
|
if (hyd)
|
|
957
1013
|
ctx.push(indent(`__cl.push(${v});`));
|
|
958
1014
|
else
|
|
959
|
-
ctx.push(indent(`
|
|
1015
|
+
ctx.push(indent(`__b.appendChild(${v});`));
|
|
960
1016
|
}
|
|
961
1017
|
}
|
|
962
|
-
if (node.update)
|
|
963
|
-
ctx.push(indent(`${node.update}`));
|
|
964
1018
|
ctx.push(indent(`}`));
|
|
965
1019
|
}
|
|
1020
|
+
if (!hyd)
|
|
1021
|
+
ctx.push(indent(`__p.insertBefore(__b, ${endAnchor});`));
|
|
966
1022
|
if (hyd)
|
|
967
1023
|
ctx.push(indent(`__place(${anchor}, ${endAnchor}, __cl, ${parent});`));
|
|
968
1024
|
ctx.push(`};`);
|
|
@@ -971,12 +1027,20 @@ function emitForLoop(ctx, node, tracked, parentVar) {
|
|
|
971
1027
|
ctx.push(`${ctx.isAsyncScope ? 'await ' : ''}${renderLoop}();`);
|
|
972
1028
|
if (node.kind === 'for-in') {
|
|
973
1029
|
const srcExpr = transformTracked(node.condition, tracked);
|
|
1030
|
+
// A `for ... of x` loop is only reactive when its source references a
|
|
1031
|
+
// tracked cell/prop. Emitting the re-render effect unconditionally would
|
|
1032
|
+
// reference the loop-local item variable (e.g. `col`, `group`, `p`) at
|
|
1033
|
+
// the top level, where it is out of scope — `ReferenceError` at hydrate
|
|
1034
|
+
// for loops over static/member collections or statement-mode locals.
|
|
1035
|
+
if (!isReactiveExpression(node.condition, tracked)) {
|
|
1036
|
+
return null;
|
|
1037
|
+
}
|
|
974
1038
|
ctx.effects.push(`{
|
|
975
1039
|
let __busy = false;
|
|
976
1040
|
let __iv = undefined;
|
|
977
1041
|
let __first = true;
|
|
978
1042
|
effect(${ctx.isAsyncScope ? 'async () => {' : '() => {'}
|
|
979
|
-
if (__first) { __first = false; return; }
|
|
1043
|
+
if (__first) { __first = false; __iv = (${srcExpr}); return; }
|
|
980
1044
|
if (__busy) return;
|
|
981
1045
|
const __nv = (${srcExpr});
|
|
982
1046
|
if (__nv !== __iv) {
|
|
@@ -1012,10 +1076,16 @@ function emitSwitchBlock(ctx, node, tracked, parentVar) {
|
|
|
1012
1076
|
ctx.push(`const ${endAnchor} = document.createComment('switch-end');`);
|
|
1013
1077
|
const renderSwitch = ctx.n();
|
|
1014
1078
|
ctx.push(`const ${renderSwitch} = ${ctx.isAsyncScope ? 'async () => {' : '() => {'}`);
|
|
1015
|
-
if (
|
|
1016
|
-
ctx.push(indent(`const __p = ${anchor}.parentNode;`));
|
|
1017
|
-
if (hyd)
|
|
1079
|
+
if (hyd) {
|
|
1018
1080
|
ctx.push(indent(`const __cl = [];`));
|
|
1081
|
+
}
|
|
1082
|
+
else {
|
|
1083
|
+
// Case bodies can contain block-level nodes (`if`/`map`/…). Route them into
|
|
1084
|
+
// a whole-case fragment so their anchors stay inside the switch region
|
|
1085
|
+
// instead of leaking to `$root` on fresh client renders.
|
|
1086
|
+
ctx.push(indent(`const __p = ${anchor}.parentNode;`));
|
|
1087
|
+
ctx.push(indent(`const __c = document.createDocumentFragment();`));
|
|
1088
|
+
}
|
|
1019
1089
|
ctx.push(indent(`switch (${discExpr}) {`));
|
|
1020
1090
|
for (const c of node.cases) {
|
|
1021
1091
|
if (c.test) {
|
|
@@ -1025,17 +1095,19 @@ function emitSwitchBlock(ctx, node, tracked, parentVar) {
|
|
|
1025
1095
|
ctx.push(indent(`default:`));
|
|
1026
1096
|
}
|
|
1027
1097
|
for (const n of c.body) {
|
|
1028
|
-
const v = emitNode(ctx, n, tracked, effectsVar, hyd ? parentVar :
|
|
1098
|
+
const v = emitNode(ctx, n, tracked, effectsVar, hyd ? parentVar : '__c');
|
|
1029
1099
|
if (v) {
|
|
1030
1100
|
if (hyd)
|
|
1031
1101
|
ctx.push(indent(`__cl.push(${v});`));
|
|
1032
1102
|
else
|
|
1033
|
-
ctx.push(indent(`
|
|
1103
|
+
ctx.push(indent(`__c.appendChild(${v});`));
|
|
1034
1104
|
}
|
|
1035
1105
|
}
|
|
1036
1106
|
ctx.push(indent(`break;`));
|
|
1037
1107
|
}
|
|
1038
1108
|
ctx.push(indent(`}`));
|
|
1109
|
+
if (!hyd)
|
|
1110
|
+
ctx.push(indent(`__p.insertBefore(__c, ${endAnchor});`));
|
|
1039
1111
|
if (hyd)
|
|
1040
1112
|
ctx.push(indent(`__place(${anchor}, ${endAnchor}, __cl, ${parent});`));
|
|
1041
1113
|
ctx.push(`};`);
|
|
@@ -1047,7 +1119,7 @@ function emitSwitchBlock(ctx, node, tracked, parentVar) {
|
|
|
1047
1119
|
let __iv = (${discExpr});
|
|
1048
1120
|
let __first = true;
|
|
1049
1121
|
effect(${ctx.isAsyncScope ? 'async () => {' : '() => {'}
|
|
1050
|
-
if (__first) { __first = false; return; }
|
|
1122
|
+
if (__first) { __first = false; __iv = (${discExpr}); return; }
|
|
1051
1123
|
if (__busy) return;
|
|
1052
1124
|
const __nv = (${discExpr});
|
|
1053
1125
|
if (__nv !== __iv) {
|
|
@@ -1074,6 +1146,12 @@ function emitMap(ctx, node, tracked, parentVar) {
|
|
|
1074
1146
|
const keyed = !!node.keyExpr;
|
|
1075
1147
|
const hyd = ctx.hydrate && !keyed;
|
|
1076
1148
|
const parent = parentVar || '$root';
|
|
1149
|
+
// A re-render effect is only valid when the map iterates a tracked/reactive
|
|
1150
|
+
// source. Iterating a static collection (module const, top-level array) or a
|
|
1151
|
+
// loop/statement-local derived value (e.g. `col.links`, `pages`) renders once;
|
|
1152
|
+
// emitting a top-level effect there would reference the loop/statement local
|
|
1153
|
+
// (`col`, `pages`) which is out of scope — `ReferenceError` at hydrate.
|
|
1154
|
+
const reactiveSource = isReactiveExpression(node.expression, tracked);
|
|
1077
1155
|
ctx.push(`const ${anchor} = document.createComment('map');`);
|
|
1078
1156
|
if (!hyd)
|
|
1079
1157
|
ctx.push(`${parent}.appendChild(${anchor});`);
|
|
@@ -1085,16 +1163,29 @@ function emitMap(ctx, node, tracked, parentVar) {
|
|
|
1085
1163
|
ctx.push(indent(`__r = __r || ${endAnchor};`));
|
|
1086
1164
|
if (node.indexVariable)
|
|
1087
1165
|
ctx.push(indent(`const ${node.indexVariable} = __i;`));
|
|
1088
|
-
|
|
1166
|
+
if (hyd) {
|
|
1167
|
+
ctx.push(indent(`const __p = ${anchor}.parentNode;`));
|
|
1168
|
+
}
|
|
1169
|
+
else {
|
|
1170
|
+
// Block-level item nodes (`if`/`map`/`try`/…) self-append their anchors to
|
|
1171
|
+
// whatever container they are told to. A fresh fragment per item keeps them
|
|
1172
|
+
// inside the map region (before `__r`/`endAnchor`) instead of leaking to
|
|
1173
|
+
// `$root`, which would render map content above the enclosing layout slot
|
|
1174
|
+
// during every fresh client render (SPA navigation / renderMatch).
|
|
1175
|
+
ctx.push(indent(`const __p = ${anchor}.parentNode;`));
|
|
1176
|
+
ctx.push(indent(`const __it = document.createDocumentFragment();`));
|
|
1177
|
+
}
|
|
1089
1178
|
for (const n of node.bodyTemplate) {
|
|
1090
|
-
const v = emitNode(ctx, n, tracked, '__e');
|
|
1179
|
+
const v = emitNode(ctx, n, tracked, '__e', hyd ? undefined : '__it');
|
|
1091
1180
|
if (v) {
|
|
1092
1181
|
if (hyd)
|
|
1093
1182
|
ctx.push(indent(`if (__cl) __cl.push(${v}); else __p.insertBefore(${v}, __r);`));
|
|
1094
1183
|
else
|
|
1095
|
-
ctx.push(indent(`
|
|
1184
|
+
ctx.push(indent(`__it.appendChild(${v});`));
|
|
1096
1185
|
}
|
|
1097
1186
|
}
|
|
1187
|
+
if (!hyd)
|
|
1188
|
+
ctx.push(indent(`__p.insertBefore(__it, __r);`));
|
|
1098
1189
|
ctx.push(`};`);
|
|
1099
1190
|
let emptyRenderName = null;
|
|
1100
1191
|
if (node.alternateNodes.length > 0) {
|
|
@@ -1137,11 +1228,13 @@ function emitMap(ctx, node, tracked, parentVar) {
|
|
|
1137
1228
|
const isEmptyVar = ctx.n();
|
|
1138
1229
|
ctx.push(`let ${isEmptyVar} = !${hasItems}();`);
|
|
1139
1230
|
ctx.push(`if (!${isEmptyVar}) { ${initList}(); } else { ${aw}${emptyRenderName}(); }`);
|
|
1231
|
+
if (!reactiveSource)
|
|
1232
|
+
return null;
|
|
1140
1233
|
ctx.effects.push(`{
|
|
1141
1234
|
let __first = true;
|
|
1142
1235
|
effect(${effOpen}
|
|
1143
1236
|
const __new = ${hasItems}();
|
|
1144
|
-
if (__first) { __first = false; return; }
|
|
1237
|
+
if (__first) { __first = false; __new; return; }
|
|
1145
1238
|
if (__new !== ${isEmptyVar}) {
|
|
1146
1239
|
if (__new) ${reconciler}(${arrExpr});
|
|
1147
1240
|
return;
|
|
@@ -1156,11 +1249,13 @@ function emitMap(ctx, node, tracked, parentVar) {
|
|
|
1156
1249
|
}
|
|
1157
1250
|
else {
|
|
1158
1251
|
ctx.push(`${initList}();`);
|
|
1252
|
+
if (!reactiveSource)
|
|
1253
|
+
return null;
|
|
1159
1254
|
ctx.effects.push(`{
|
|
1160
1255
|
let __first = true;
|
|
1161
1256
|
effect(() => {
|
|
1162
1257
|
const __nv = ${arrExpr};
|
|
1163
|
-
if (__first) { __first = false; return; }
|
|
1258
|
+
if (__first) { __first = false; __nv; ${reconciler}(${arrExpr}); return; }
|
|
1164
1259
|
${reconciler}(${arrExpr});
|
|
1165
1260
|
});
|
|
1166
1261
|
}`);
|
|
@@ -1194,11 +1289,13 @@ function emitMap(ctx, node, tracked, parentVar) {
|
|
|
1194
1289
|
ctx.push(indent(`} else { ${aw}${emptyRenderName}(${collectVar ? collectVar : ''}); }`));
|
|
1195
1290
|
if (collectVar)
|
|
1196
1291
|
ctx.push(`__place(${anchor}, ${endAnchor}, ${collectVar}, ${parent});`);
|
|
1292
|
+
if (!reactiveSource)
|
|
1293
|
+
return null;
|
|
1197
1294
|
ctx.effects.push(`{
|
|
1198
1295
|
let __first = true;
|
|
1199
1296
|
effect(${effOpen}
|
|
1200
1297
|
const __new = ${hasItems}();
|
|
1201
|
-
if (__first) { __first = false; return; }
|
|
1298
|
+
if (__first) { __first = false; __new; return; }
|
|
1202
1299
|
if (__new !== ${isEmptyVar}) {
|
|
1203
1300
|
if (__new) {
|
|
1204
1301
|
for (const e of ${effectsVar}) destroy_block(e);
|
|
@@ -1225,11 +1322,13 @@ ${renderAllItems(' ', null)}
|
|
|
1225
1322
|
ctx.push(renderAllItems('', collectVar));
|
|
1226
1323
|
if (collectVar)
|
|
1227
1324
|
ctx.push(`__place(${anchor}, ${endAnchor}, ${collectVar}, ${parent});`);
|
|
1325
|
+
if (!reactiveSource)
|
|
1326
|
+
return null;
|
|
1228
1327
|
ctx.effects.push(`{
|
|
1229
1328
|
let __first = true;
|
|
1230
1329
|
effect(${effOpen}
|
|
1231
1330
|
const __nv = ${arrExpr};
|
|
1232
|
-
if (__first) { __first = false; return; }
|
|
1331
|
+
if (__first) { __first = false; __nv; return; }
|
|
1233
1332
|
for (const e of ${effectsVar}) destroy_block(e);
|
|
1234
1333
|
${effectsVar}.length = 0;
|
|
1235
1334
|
__cleanup(${anchor}, ${endAnchor});
|
|
@@ -1243,10 +1342,11 @@ ${renderAllItems(' ', null)}
|
|
|
1243
1342
|
function computeAsyncComponents(comps) {
|
|
1244
1343
|
return new Set(comps.filter((c) => c.isAsync).map((c) => c.name));
|
|
1245
1344
|
}
|
|
1246
|
-
function generateComponent(comp, importedNames = new Set(), hydrate = false, asyncComps = new Set()) {
|
|
1345
|
+
function generateComponent(comp, importedNames = new Set(), hydrate = false, asyncComps = new Set(), linkNames = new Set()) {
|
|
1247
1346
|
const tracked = collectTrackedNames(comp.body);
|
|
1248
1347
|
const ctx = new Ctx();
|
|
1249
1348
|
ctx.importedNames = importedNames;
|
|
1349
|
+
ctx.linkNames = linkNames;
|
|
1250
1350
|
ctx.hydrate = hydrate;
|
|
1251
1351
|
ctx.asyncComps = asyncComps;
|
|
1252
1352
|
ctx.isAsyncScope = comp.isAsync || asyncComps.has(comp.name);
|
|
@@ -1275,7 +1375,7 @@ function generateComponent(comp, importedNames = new Set(), hydrate = false, asy
|
|
|
1275
1375
|
const v = emitNode(ctx, node, tracked, null);
|
|
1276
1376
|
if (v) {
|
|
1277
1377
|
if (ctx.hydrate) {
|
|
1278
|
-
ctx.push(indent(`if (${v}.parentNode !== $root) $root.appendChild(${v})
|
|
1378
|
+
ctx.push(indent(`if (${v}.parentNode !== $root) { if (!$root || ${v}.parentNode == null || !$root.contains(${v})) $root.appendChild(${v}); }`));
|
|
1279
1379
|
}
|
|
1280
1380
|
else {
|
|
1281
1381
|
ctx.push(indent(`$root.appendChild(${v});`));
|
|
@@ -1306,6 +1406,16 @@ function buildComponentMap(irRoot, hydrate = false) {
|
|
|
1306
1406
|
const mapLines = [];
|
|
1307
1407
|
mapLines.push(`const __components = {};`);
|
|
1308
1408
|
const asyncComps = computeAsyncComponents(irRoot.components);
|
|
1409
|
+
// Wipe-style router components (Link/NavLink) replace their SSR children on
|
|
1410
|
+
// hydration, so their children fragments must be rebuilt in full. Track the
|
|
1411
|
+
// file-local binding names regardless of aliasing (`const Nav = NavLink`).
|
|
1412
|
+
const linkNames = new Set();
|
|
1413
|
+
for (const imp of irRoot.imports) {
|
|
1414
|
+
for (const pair of importBindingPairs(imp)) {
|
|
1415
|
+
if (pair.imported === 'Link' || pair.imported === 'NavLink')
|
|
1416
|
+
linkNames.add(pair.local);
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1309
1419
|
// Top-level value bindings (`const MyIcon = Cpu`) live in module scope
|
|
1310
1420
|
// alongside the component map, so a same-named JSX tag must invoke that
|
|
1311
1421
|
// in-scope value directly — not a registry lookup. File-defined components
|
|
@@ -1319,7 +1429,7 @@ function buildComponentMap(irRoot, hydrate = false) {
|
|
|
1319
1429
|
mapLines.push(`__components[${JSON.stringify(comp.name)}] = ${stub};`);
|
|
1320
1430
|
continue;
|
|
1321
1431
|
}
|
|
1322
|
-
const code = generateComponent(comp, directNames, hydrate, asyncComps);
|
|
1432
|
+
const code = generateComponent(comp, directNames, hydrate, asyncComps, linkNames);
|
|
1323
1433
|
mapLines.push(`__components[${JSON.stringify(comp.name)}] = ${code};`);
|
|
1324
1434
|
}
|
|
1325
1435
|
mapLines.push(`function __cleanup(start, end) {`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ir-generator.d.ts","sourceRoot":"","sources":["../src/ir-generator.ts"],"names":[],"mappings":"AACA,OAAO,EACL,MAAM,EAoBP,MAAM,uBAAuB,CAAC;AA0L/B,wBAAgB,YAAY,CAAC,MAAM,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,GAAG,CAAC;IAAC,cAAc,CAAC,EAAE,GAAG,CAAC;IAAC,UAAU,CAAC,EAAE,GAAG,EAAE,CAAA;CAAE,CAAC,GAAG,SAAS,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkCnL;AAg0BD,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"ir-generator.d.ts","sourceRoot":"","sources":["../src/ir-generator.ts"],"names":[],"mappings":"AACA,OAAO,EACL,MAAM,EAoBP,MAAM,uBAAuB,CAAC;AA0L/B,wBAAgB,YAAY,CAAC,MAAM,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,GAAG,CAAC;IAAC,cAAc,CAAC,EAAE,GAAG,CAAC;IAAC,UAAU,CAAC,EAAE,GAAG,EAAE,CAAA;CAAE,CAAC,GAAG,SAAS,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkCnL;AAg0BD,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CA6P9E"}
|
package/dist/ir-generator.js
CHANGED
|
@@ -1277,6 +1277,7 @@ export function generateIR(ast, source, filename) {
|
|
|
1277
1277
|
'createContext',
|
|
1278
1278
|
'redirect', 'permanentRedirect', 'notFound', 'NotFoundError',
|
|
1279
1279
|
'createResource', 'getAction', 'validateActionInput', 'issuesToFieldMap', 'isFormAction',
|
|
1280
|
+
'Show', 'For', 'Switch', 'Match',
|
|
1280
1281
|
];
|
|
1281
1282
|
const usedFunctions = new Set();
|
|
1282
1283
|
const addUsedFrom = (code) => {
|
|
@@ -1297,6 +1298,36 @@ export function generateIR(ast, source, filename) {
|
|
|
1297
1298
|
if (node instanceof RuntimeStatement && node.raw) {
|
|
1298
1299
|
addUsedFrom(node.raw);
|
|
1299
1300
|
}
|
|
1301
|
+
if (node instanceof TrackDecl && node.init) {
|
|
1302
|
+
addUsedFrom(node.init);
|
|
1303
|
+
}
|
|
1304
|
+
if (node instanceof ForLoop) {
|
|
1305
|
+
if (node.init)
|
|
1306
|
+
addUsedFrom(node.init);
|
|
1307
|
+
if (node.update)
|
|
1308
|
+
addUsedFrom(node.update);
|
|
1309
|
+
if (node.condition && node.condition.raw)
|
|
1310
|
+
addUsedFrom(node.condition.raw);
|
|
1311
|
+
scanForAutoImport(node.bodyTemplate);
|
|
1312
|
+
}
|
|
1313
|
+
if (node instanceof WhileLoop) {
|
|
1314
|
+
if (node.condition && node.condition.raw)
|
|
1315
|
+
addUsedFrom(node.condition.raw);
|
|
1316
|
+
scanForAutoImport(node.bodyTemplate);
|
|
1317
|
+
}
|
|
1318
|
+
if (node instanceof SwitchBlock) {
|
|
1319
|
+
if (node.discriminant && node.discriminant.raw)
|
|
1320
|
+
addUsedFrom(node.discriminant.raw);
|
|
1321
|
+
for (const c of node.cases) {
|
|
1322
|
+
if (c.test && c.test.raw)
|
|
1323
|
+
addUsedFrom(c.test.raw);
|
|
1324
|
+
scanForAutoImport(c.body);
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
if (node instanceof TryCatch) {
|
|
1328
|
+
scanForAutoImport(node.bodyTemplate);
|
|
1329
|
+
scanForAutoImport(node.catchBody);
|
|
1330
|
+
}
|
|
1300
1331
|
if (node instanceof ComponentCall) {
|
|
1301
1332
|
if (autoImportable.includes(node.componentName)) {
|
|
1302
1333
|
usedFunctions.add(node.componentName);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module-imports.d.ts","sourceRoot":"","sources":["../src/module-imports.ts"],"names":[],"mappings":"AAmGA;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAgC1F;AAED,yEAAyE;AACzE,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAQpE;AAED,0FAA0F;AAC1F,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAIvD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,UAAU,EAAE,MAAM,EAAE,EACpB,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,IAAI,CAuBN;AAiBD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"module-imports.d.ts","sourceRoot":"","sources":["../src/module-imports.ts"],"names":[],"mappings":"AAmGA;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAgC1F;AAED,yEAAyE;AACzE,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAQpE;AAED,0FAA0F;AAC1F,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAIvD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,UAAU,EAAE,MAAM,EAAE,EACpB,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,IAAI,CAuBN;AAiBD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAiE7E;AA0WD;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA8BlF;AAoJD,kFAAkF;AAClF,wBAAgB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,MAAM,CAiGxF"}
|
package/dist/module-imports.js
CHANGED
|
@@ -238,13 +238,26 @@ export function loadSsrModule(absPath) {
|
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
240
|
else {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
//
|
|
245
|
-
//
|
|
246
|
-
|
|
247
|
-
|
|
241
|
+
// Pure re-export barrels (`export { x as y } from`, `export * from`,
|
|
242
|
+
// `export * as ns from`) return a lazy Proxy: only the submodule backing
|
|
243
|
+
// a name the consumer actually touches gets evaluated. Without this, a
|
|
244
|
+
// several-thousand-export barrel (e.g. lucide's icon index) pays its full
|
|
245
|
+
// transitive closure on every cold load (tens of seconds dev, too slow to
|
|
246
|
+
// ship in prod).
|
|
247
|
+
const src = readSsrSource(absPath);
|
|
248
|
+
const body = (src?.ast?.body || []);
|
|
249
|
+
if (src && isPureReexportBarrel(body)) {
|
|
250
|
+
exportsObj = createLazyBarrelExports(buildBarrelSpec(body), dirname(absPath));
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
exportsObj = {};
|
|
254
|
+
EVALUATING.set(absPath, exportsObj);
|
|
255
|
+
const ran = evaluateModuleFile(absPath, exportsObj, src);
|
|
256
|
+
// Runtime evaluation failure (already warned) — give Node's own loader
|
|
257
|
+
// a chance before giving up.
|
|
258
|
+
if (!ran)
|
|
259
|
+
exportsObj = null;
|
|
260
|
+
}
|
|
248
261
|
}
|
|
249
262
|
if (exportsObj === null) {
|
|
250
263
|
exportsObj = nativeRequireFallback(absPath);
|
|
@@ -287,19 +300,14 @@ function depsFresh(cachedVal) {
|
|
|
287
300
|
}
|
|
288
301
|
return true;
|
|
289
302
|
}
|
|
290
|
-
|
|
291
|
-
* Runs one module file. Returns `true` when the body executed, `false` when a
|
|
292
|
-
* runtime failure was already warned about. Unsupported ESM constructs throw —
|
|
293
|
-
* the only honest outcome is a loud, specific error, never silently undefined.
|
|
294
|
-
*/
|
|
295
|
-
function evaluateModuleFile(absPath, exportsObj) {
|
|
303
|
+
function readSsrSource(absPath) {
|
|
296
304
|
let raw;
|
|
297
305
|
try {
|
|
298
306
|
raw = readFileSync(absPath, 'utf-8');
|
|
299
307
|
}
|
|
300
308
|
catch (err) {
|
|
301
309
|
console.warn(`[vesk] SSR: failed to read ${absPath}: ${err?.message ?? String(err)}`);
|
|
302
|
-
return
|
|
310
|
+
return null;
|
|
303
311
|
}
|
|
304
312
|
let ast = null;
|
|
305
313
|
try {
|
|
@@ -308,6 +316,233 @@ function evaluateModuleFile(absPath, exportsObj) {
|
|
|
308
316
|
catch {
|
|
309
317
|
ast = null;
|
|
310
318
|
}
|
|
319
|
+
return { raw, ast };
|
|
320
|
+
}
|
|
321
|
+
/** True when every statement in a module body is a re-export (or a type-only /
|
|
322
|
+
* directive statement). Such "pure barrels" carry no computation of their own,
|
|
323
|
+
* so they can be served lazily instead of paying their full transitive closure. */
|
|
324
|
+
function isPureReexportBarrel(body) {
|
|
325
|
+
for (const stmt of body) {
|
|
326
|
+
if (stmt.type === 'EmptyStatement')
|
|
327
|
+
continue;
|
|
328
|
+
if (stmt.type === 'ExpressionStatement') {
|
|
329
|
+
// A leading directive such as 'use strict' — no runtime effect here.
|
|
330
|
+
const expr = stmt.expression;
|
|
331
|
+
if (expr && expr.type === 'Literal' && typeof expr.value === 'string' && expr.value.length > 0)
|
|
332
|
+
continue;
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
if (stmt.type === 'ExportAllDeclaration') {
|
|
336
|
+
if (stmt.exportKind === 'type')
|
|
337
|
+
continue;
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
if (stmt.type === 'ExportNamedDeclaration') {
|
|
341
|
+
if (stmt.exportKind === 'type')
|
|
342
|
+
continue;
|
|
343
|
+
if (stmt.source)
|
|
344
|
+
continue; // `export { x } from 'm'` re-export
|
|
345
|
+
return false; // local declaration or local re-export — real code
|
|
346
|
+
}
|
|
347
|
+
if (stmt.type === 'ImportDeclaration' && stmt.importKind === 'type')
|
|
348
|
+
continue;
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
return true;
|
|
352
|
+
}
|
|
353
|
+
function buildBarrelSpec(body) {
|
|
354
|
+
const spec = { direct: new Map(), namespaces: new Map(), stars: [] };
|
|
355
|
+
for (const stmt of body) {
|
|
356
|
+
if (stmt.type === 'ExportNamedDeclaration') {
|
|
357
|
+
if (stmt.exportKind === 'type' || !stmt.source)
|
|
358
|
+
continue;
|
|
359
|
+
// The module source is a string literal — read its value directly instead
|
|
360
|
+
// of reprinting the node (esrap print per statement is the dominant cost
|
|
361
|
+
// in multi-thousand-export barrels).
|
|
362
|
+
const source = quotedSourceValue(stmt.source);
|
|
363
|
+
const specifiers = stmt.specifiers || [];
|
|
364
|
+
for (const ex of specifiers) {
|
|
365
|
+
if (ex.exportKind === 'type')
|
|
366
|
+
continue;
|
|
367
|
+
const imported = exportKeyName(ex.local);
|
|
368
|
+
const exported = exportKeyName(ex.exported);
|
|
369
|
+
if (!exported || !imported)
|
|
370
|
+
continue;
|
|
371
|
+
if (source !== null)
|
|
372
|
+
spec.direct.set(exported, { source, imported });
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
else if (stmt.type === 'ExportAllDeclaration') {
|
|
376
|
+
if (stmt.exportKind === 'type')
|
|
377
|
+
continue;
|
|
378
|
+
const source = quotedSourceValue(stmt.source);
|
|
379
|
+
if (stmt.exported) {
|
|
380
|
+
const nsName = exportKeyName(stmt.exported);
|
|
381
|
+
if (source !== null && nsName)
|
|
382
|
+
spec.namespaces.set(nsName, source);
|
|
383
|
+
}
|
|
384
|
+
else if (source !== null) {
|
|
385
|
+
spec.stars.push(source);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return spec;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Raw string value of an `... from <literal>` source node. Returns the unquoted
|
|
393
|
+
* specifier, or `null` when the node isn't a plain string literal (callers fall
|
|
394
|
+
* back — and caching the string is safer than reprinting per statement).
|
|
395
|
+
*/
|
|
396
|
+
/**
|
|
397
|
+
* Raw string value of an `... from <literal>` source node. String literals are
|
|
398
|
+
* read directly (esrap printing per statement is the dominant cost in
|
|
399
|
+
* multi-thousand-export barrels); any other node falls back to a reprinted,
|
|
400
|
+
* quote-stripped value so no export is ever dropped.
|
|
401
|
+
*/
|
|
402
|
+
function quotedSourceValue(node) {
|
|
403
|
+
const n = node;
|
|
404
|
+
if (n && (n.type === 'Literal' || n.type === 'StringLiteral') && typeof n.value === 'string') {
|
|
405
|
+
return n.value;
|
|
406
|
+
}
|
|
407
|
+
const printed = printNode(node);
|
|
408
|
+
return printed ? stripOuterQuotes(printed) : null;
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* A lazy exports object for a pure re-export barrel. Names resolve on access;
|
|
412
|
+
* the submodule backing them is loaded (and cached) only then. Barrels load
|
|
413
|
+
* with an empty dependency closure, so editing one re-exported module
|
|
414
|
+
* invalidates that module alone — never the whole barrel (a several-thousand
|
|
415
|
+
* file stat walk otherwise).
|
|
416
|
+
*/
|
|
417
|
+
function createLazyBarrelExports(spec, dir) {
|
|
418
|
+
// Resolved submodule TARGETS are cached, but their values are pulled through
|
|
419
|
+
// `loadSsrModule` on every access — that call is mtime-keyed per module, so
|
|
420
|
+
// editing a re-exported file invalidates exactly that module (and is cheap:
|
|
421
|
+
// one stat), while the barrel itself stays cached with no dependency walk.
|
|
422
|
+
const targets = new Map();
|
|
423
|
+
const starKeys = new Set();
|
|
424
|
+
let starsEnumerated = false;
|
|
425
|
+
const loadSource = (source) => {
|
|
426
|
+
let target = targets.get(source);
|
|
427
|
+
if (!target) {
|
|
428
|
+
target = resolveSsrModule(source, dir) ?? '';
|
|
429
|
+
if (!target)
|
|
430
|
+
return null;
|
|
431
|
+
targets.set(source, target);
|
|
432
|
+
}
|
|
433
|
+
const mod = loadSsrModule(target);
|
|
434
|
+
return mod && typeof mod === 'object' ? mod : null;
|
|
435
|
+
};
|
|
436
|
+
const hasPresent = (prop) => {
|
|
437
|
+
if (spec.direct.has(prop) || spec.namespaces.has(prop))
|
|
438
|
+
return true;
|
|
439
|
+
if (starsEnumerated && starKeys.has(prop))
|
|
440
|
+
return true;
|
|
441
|
+
if (prop === 'default')
|
|
442
|
+
return false; // `export *` never re-exports default
|
|
443
|
+
for (const source of spec.stars) {
|
|
444
|
+
const mod = loadSource(source);
|
|
445
|
+
if (mod && Object.prototype.hasOwnProperty.call(mod, prop))
|
|
446
|
+
return true;
|
|
447
|
+
}
|
|
448
|
+
return false;
|
|
449
|
+
};
|
|
450
|
+
const getNamed = (prop) => {
|
|
451
|
+
const direct = spec.direct.get(prop);
|
|
452
|
+
if (direct) {
|
|
453
|
+
const mod = loadSource(direct.source);
|
|
454
|
+
if (!mod)
|
|
455
|
+
return undefined;
|
|
456
|
+
return mod[direct.imported];
|
|
457
|
+
}
|
|
458
|
+
const nsSource = spec.namespaces.get(prop);
|
|
459
|
+
if (nsSource) {
|
|
460
|
+
return loadSource(nsSource) ?? undefined;
|
|
461
|
+
}
|
|
462
|
+
if (prop === 'default')
|
|
463
|
+
return undefined;
|
|
464
|
+
for (const source of spec.stars) {
|
|
465
|
+
const mod = loadSource(source);
|
|
466
|
+
if (mod && Object.prototype.hasOwnProperty.call(mod, prop)) {
|
|
467
|
+
return mod[prop];
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return undefined;
|
|
471
|
+
};
|
|
472
|
+
return new Proxy(Object.create(null), {
|
|
473
|
+
get(_target, prop) {
|
|
474
|
+
if (typeof prop === 'symbol')
|
|
475
|
+
return undefined;
|
|
476
|
+
return getNamed(prop);
|
|
477
|
+
},
|
|
478
|
+
has(_target, prop) {
|
|
479
|
+
if (typeof prop === 'symbol')
|
|
480
|
+
return false;
|
|
481
|
+
return hasPresent(prop);
|
|
482
|
+
},
|
|
483
|
+
ownKeys() {
|
|
484
|
+
if (!starsEnumerated) {
|
|
485
|
+
for (const source of spec.stars) {
|
|
486
|
+
const mod = loadSource(source);
|
|
487
|
+
if (!mod)
|
|
488
|
+
continue;
|
|
489
|
+
for (const k of Reflect.ownKeys(mod)) {
|
|
490
|
+
if (typeof k === 'string' && k !== 'default')
|
|
491
|
+
starKeys.add(k);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
starsEnumerated = true;
|
|
495
|
+
}
|
|
496
|
+
return [...new Set([...spec.direct.keys(), ...spec.namespaces.keys(), ...starKeys])];
|
|
497
|
+
},
|
|
498
|
+
getOwnPropertyDescriptor(_target, prop) {
|
|
499
|
+
if (typeof prop === 'symbol')
|
|
500
|
+
return undefined;
|
|
501
|
+
if (!hasPresent(prop))
|
|
502
|
+
return undefined;
|
|
503
|
+
// Accessor descriptor so iteration doesn't force-load every submodule;
|
|
504
|
+
// values are pulled via the getter only when a consumer reads them.
|
|
505
|
+
return {
|
|
506
|
+
enumerable: true,
|
|
507
|
+
configurable: true,
|
|
508
|
+
get: () => getNamed(prop),
|
|
509
|
+
};
|
|
510
|
+
},
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Runs one module file. Returns `true` when the body executed, `false` when a
|
|
515
|
+
* runtime failure was already warned about. Unsupported ESM constructs throw —
|
|
516
|
+
* the only honest outcome is a loud, specific error, never silently undefined.
|
|
517
|
+
* `src` carries a read+parse already done by the caller (either a barrel probe
|
|
518
|
+
* or a plain load) so big files aren't read/parsed twice.
|
|
519
|
+
*/
|
|
520
|
+
function evaluateModuleFile(absPath, exportsObj, src) {
|
|
521
|
+
let raw;
|
|
522
|
+
if (src) {
|
|
523
|
+
raw = src.raw;
|
|
524
|
+
}
|
|
525
|
+
else {
|
|
526
|
+
try {
|
|
527
|
+
raw = readFileSync(absPath, 'utf-8');
|
|
528
|
+
}
|
|
529
|
+
catch (err) {
|
|
530
|
+
console.warn(`[vesk] SSR: failed to read ${absPath}: ${err?.message ?? String(err)}`);
|
|
531
|
+
return false;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
let ast;
|
|
535
|
+
if (src) {
|
|
536
|
+
ast = src.ast;
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
try {
|
|
540
|
+
ast = parse(raw, { filename: absPath });
|
|
541
|
+
}
|
|
542
|
+
catch {
|
|
543
|
+
ast = null;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
311
546
|
if (!ast) {
|
|
312
547
|
// Not parseable as ESM — run verbatim as CJS.
|
|
313
548
|
try {
|
|
@@ -421,7 +656,13 @@ function createModuleRequire(fromDir) {
|
|
|
421
656
|
*/
|
|
422
657
|
export function resolveSsrModule(specifier, fromDir) {
|
|
423
658
|
let resolved = null;
|
|
424
|
-
if (specifier === '.' || specifier === '..'
|
|
659
|
+
if (specifier === '.' || specifier === '..') {
|
|
660
|
+
// Literal `.`/`..` are relative — anchor them to the importing file's
|
|
661
|
+
// directory like every other relative specifier (a bare resolve()
|
|
662
|
+
// against process.cwd() silently resolved the wrong directory).
|
|
663
|
+
resolved = probeFile(resolve(fromDir, specifier));
|
|
664
|
+
}
|
|
665
|
+
else if (isAbsolute(specifier)) {
|
|
425
666
|
resolved = probeFile(resolve(specifier));
|
|
426
667
|
}
|
|
427
668
|
else if (specifier.startsWith('./') || specifier.startsWith('../')) {
|
package/dist/parser.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAMtC,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,YAAY,CAAC;IACnB,8CAA8C;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,WAAW,EAAE,MAAM,CAAC;IACpB,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAMtC,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,YAAY,CAAC;IACnB,8CAA8C;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,WAAW,EAAE,MAAM,CAAC;IACpB,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA0BD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,cAAc,EAAE,CAAA;CAAE,CA4LpG;AAED,wBAAgB,gBAAgB,IAAI,OAAO,KAAK,CAAC,MAAM,CAEtD;AAED,wBAAgB,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CA8CzE"}
|
package/dist/parser.js
CHANGED
|
@@ -9,6 +9,20 @@ function isIdentChar(code) {
|
|
|
9
9
|
function isWhitespaceChar(ch) {
|
|
10
10
|
return ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r' || ch === '\f' || ch === '\v' || ch === '\u00a0' || ch === '\ufeff';
|
|
11
11
|
}
|
|
12
|
+
function offsetToLineCol(source, offset) {
|
|
13
|
+
let line = 1;
|
|
14
|
+
let column = 1;
|
|
15
|
+
for (let i = 0; i < offset && i < source.length; i++) {
|
|
16
|
+
if (source.charCodeAt(i) === 10) {
|
|
17
|
+
line++;
|
|
18
|
+
column = 1;
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
column++;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return { line, column };
|
|
25
|
+
}
|
|
12
26
|
/**
|
|
13
27
|
* Scans the source and blanks `; key <expr>` / `; index <ident>` clauses
|
|
14
28
|
* found in `for (...)` headers, returning the rewritten code (same length
|
|
@@ -181,6 +195,21 @@ export function preprocessForClauses(source) {
|
|
|
181
195
|
k++;
|
|
182
196
|
const expr = source.slice(k, end).trim();
|
|
183
197
|
if (expr) {
|
|
198
|
+
if (expr[0] === ':') {
|
|
199
|
+
// `; key : <expr>` / `; key: <expr>` (colon form) is not valid
|
|
200
|
+
// Vesk: the clause is `; key <expr>`, and passing the colon
|
|
201
|
+
// through used to emit a broken reconciliation key.
|
|
202
|
+
const { line, column } = offsetToLineCol(source, start);
|
|
203
|
+
throw new VeskError(`Invalid \`; key\` clause — write the key expression after \`key\` without a colon: \`key ${expr.slice(1).trim()}\`.`, {
|
|
204
|
+
line,
|
|
205
|
+
column,
|
|
206
|
+
code: codeFrame(source, line, column),
|
|
207
|
+
nextSteps: [
|
|
208
|
+
'Use `; key <expr>` (no colon), e.g. `for (const item of items; key item.id)`.',
|
|
209
|
+
'For the iteration index use `; index <name>`, e.g. `for (const item of items; key item.id; index i)`.',
|
|
210
|
+
],
|
|
211
|
+
});
|
|
212
|
+
}
|
|
184
213
|
keyRange = [k, start + clause.length];
|
|
185
214
|
clauseCode = ' '.repeat(clause.length);
|
|
186
215
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-jsgen.d.ts","sourceRoot":"","sources":["../src/server-jsgen.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAOzE,OAAO,
|
|
1
|
+
{"version":3,"file":"server-jsgen.d.ts","sourceRoot":"","sources":["../src/server-jsgen.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAOzE,OAAO,EAAkG,KAAK,WAAW,EAAE,MAAM,mCAAmC,CAAC;AAWrK,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,EAAE,OAAO,GAAE,OAAe,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,CAmDjJ;AAiUD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,WAAW,EAAE,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAmD1F;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,GAAG,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAkChG"}
|
package/dist/server-jsgen.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { StaticNode, TextNode, DynamicBinding, OpaqueDynamicRegion, MapRegion, WhileLoop, SwitchBlock, TryCatch, ForLoop, TrackDecl, RuntimeStatement, ComponentRef, ComponentCall, ServerBlock, ClientBlock, HeadBlock, SlotNode, } from '@vesk/compiler/src/ir';
|
|
2
|
-
import { isStaticIR, collectTrackedNames, transformTracked, semicolonizeStatement } from '@vesk/compiler/src/client-codegen';
|
|
2
|
+
import { isStaticIR, collectTrackedNames, transformTracked, transformTrackedInit, semicolonizeStatement } from '@vesk/compiler/src/client-codegen';
|
|
3
3
|
import { unwrapTrackCall, stripTrackGeneric, hasTopLevelComma, skipWhitespace, findBalancedEnd, startsWithIdentifier } from '@vesk/compiler/src/scan';
|
|
4
4
|
import { escapeHtml, indent, exprJS, extractTopLevelNames, extractRuntimeNames, buildParamInit, __vskHydrate, __vskImportedNames, setVskImportedNames, setVskForceClaim, takeVskForceClaim, nextVskId, } from '@vesk/compiler/src/server-utils';
|
|
5
5
|
import { localValueImportNames } from '@vesk/compiler/src/module-imports';
|
|
@@ -45,8 +45,13 @@ export function irNodeToJS(node, importedNames, isAsync = false, tracked) {
|
|
|
45
45
|
return forLoopToJS(node, isAsync, tracked);
|
|
46
46
|
if (node instanceof TrackDecl) {
|
|
47
47
|
const cellName = node.rawName || node.name;
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
// Tracked reads inside a `track`/`derived` init are re-emitted as `get(…)`
|
|
49
|
+
// (matching client codegen) so a function init like
|
|
50
|
+
// `track(() => x * 2)` / `derived(() => props.tabs[x])` computes against the
|
|
51
|
+
// cell VALUES server-side instead of the raw cell objects.
|
|
52
|
+
const init = transformTrackedInit(node.init, tracked || new Map());
|
|
53
|
+
const unwrapped = unwrapTrackCall(init);
|
|
54
|
+
const inner = hasTopLevelComma(unwrapped) ? stripTrackGeneric(init) : unwrapped;
|
|
50
55
|
const key = JSON.stringify(`${compKey(node)}:${node.name}`);
|
|
51
56
|
return [
|
|
52
57
|
`const ${cellName} = (() => {`,
|
|
@@ -316,16 +321,24 @@ function forLoopToJS(node, isAsync = false, tracked) {
|
|
|
316
321
|
lines.push(`}`);
|
|
317
322
|
}
|
|
318
323
|
else {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
324
|
+
// Emit a real `for` statement (matching the client codegen): a `let`/`const`
|
|
325
|
+
// declared in the init clause gets a fresh binding per iteration, so closures
|
|
326
|
+
// created in the body capture that iteration's value rather than the loop
|
|
327
|
+
// variable's final value after the last pass.
|
|
328
|
+
let initPart = (node.init || '').trim();
|
|
329
|
+
let updatePart = (node.update || '').trim();
|
|
330
|
+
if (initPart.endsWith(';'))
|
|
331
|
+
initPart = initPart.slice(0, -1).trim();
|
|
332
|
+
if (updatePart.endsWith(';'))
|
|
333
|
+
updatePart = updatePart.slice(0, -1).trim();
|
|
334
|
+
const cond = exprJSX(node.condition, tracked);
|
|
335
|
+
const head = initPart ? `${initPart}; ${cond}` : `; ${cond}`;
|
|
336
|
+
lines.push(`for (${head}; ${updatePart}) {`);
|
|
322
337
|
for (const n of node.bodyTemplate) {
|
|
323
338
|
const code = irNodeToJS(n, null, isAsync, tracked);
|
|
324
339
|
if (code)
|
|
325
340
|
lines.push(indent(code));
|
|
326
341
|
}
|
|
327
|
-
if (node.update)
|
|
328
|
-
lines.push(indent(`${node.update}`));
|
|
329
342
|
lines.push(`}`);
|
|
330
343
|
}
|
|
331
344
|
return lines.join('\n');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vesk-plugin.d.ts","sourceRoot":"","sources":["../src/vesk-plugin.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"vesk-plugin.d.ts","sourceRoot":"","sources":["../src/vesk-plugin.ts"],"names":[],"mappings":"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"}
|
package/dist/vesk-plugin.js
CHANGED
|
@@ -463,16 +463,32 @@ export function VeskParserPlugin(config = {}) {
|
|
|
463
463
|
}
|
|
464
464
|
if (this.#componentDepth > 0 && code === 60 && this.#isBlockContext()) {
|
|
465
465
|
const next = this.input.charCodeAt(this.pos + 1);
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
466
|
+
const startsNewStatement = this.hasPrecedingLineBreak();
|
|
467
|
+
const inType = this.inType;
|
|
468
|
+
const forceJsx = () => {
|
|
469
|
+
const savedExprAllowed = this.exprAllowed;
|
|
470
|
+
this.exprAllowed = true;
|
|
471
|
+
const result = super.readToken(code);
|
|
472
|
+
this.exprAllowed = savedExprAllowed;
|
|
473
|
+
return result;
|
|
474
|
+
};
|
|
475
|
+
const prev = this.type;
|
|
476
|
+
const canEndExpr = prev === tt.name || prev === tt.num || prev === tt.string || prev === tt.regexp ||
|
|
477
|
+
prev === tt.bracketR || prev === tt.backQuote || prev === tt.template ||
|
|
478
|
+
prev === tt._this || prev === tt._super || prev === tt._true || prev === tt._false ||
|
|
479
|
+
prev === tt._null || prev === tt.jsxTagEnd;
|
|
480
|
+
// A bare `<>` fragment opening a statement (rather than a
|
|
481
|
+
// relational/type token) is JSX. It is only safe to force when it
|
|
482
|
+
// follows a line break — e.g. a semicolon-less declaration such as
|
|
483
|
+
// `const &[x] = track(...)` in statement mode, where plain
|
|
484
|
+
// tokenisation emits a `<`/`>` pair. No expression can end in `<>`,
|
|
485
|
+
// so a line break alone is sufficient to disambiguate.
|
|
486
|
+
const fragmentStart = !inType && next === 62 && startsNewStatement;
|
|
487
|
+
if (next === 47 || fragmentStart || (next >= 65 && next <= 90) || (next >= 97 && next <= 122)) {
|
|
488
|
+
if (fragmentStart) {
|
|
489
|
+
this.#jsxStartsStatement = true;
|
|
490
|
+
return forceJsx();
|
|
491
|
+
}
|
|
476
492
|
if (inType) {
|
|
477
493
|
if (startsNewStatement) {
|
|
478
494
|
if (looksLikeGenericArrowAt(this.input, this.pos) || looksLikeTypeAssertionAt(this.input, this.pos)) {
|
|
@@ -485,11 +501,6 @@ export function VeskParserPlugin(config = {}) {
|
|
|
485
501
|
}
|
|
486
502
|
}
|
|
487
503
|
else {
|
|
488
|
-
const prev = this.type;
|
|
489
|
-
const canEndExpr = prev === tt.name || prev === tt.num || prev === tt.string || prev === tt.regexp ||
|
|
490
|
-
prev === tt.bracketR || prev === tt.backQuote || prev === tt.template ||
|
|
491
|
-
prev === tt._this || prev === tt._super || prev === tt._true || prev === tt._false ||
|
|
492
|
-
prev === tt._null || prev === tt.jsxTagEnd;
|
|
493
504
|
if (!canEndExpr || startsNewStatement) {
|
|
494
505
|
if (looksLikeGenericArrowAt(this.input, this.pos) || looksLikeTypeAssertionAt(this.input, this.pos)) {
|
|
495
506
|
this.#jsxStartsStatement = false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vesk/compiler",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.18",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Vesk compiler — lexer, parser, semantic analysis, IR, codegen",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@sveltejs/acorn-typescript": "^1.0.0",
|
|
23
|
-
"@vesk/types": "^0.2.
|
|
23
|
+
"@vesk/types": "^0.2.18",
|
|
24
24
|
"acorn": "^8.15.0",
|
|
25
25
|
"esrap": "^2.3.0",
|
|
26
26
|
"is-reference": "^3.0.0",
|