@sigil-dev/compiler 0.9.4 → 0.9.6
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/index.ts +4 -4
- package/jsr.json +8 -8
- package/package.json +2 -1
- package/src/babel/handlers/dom/derived.ts +62 -62
- package/src/babel/handlers/dom/effect.ts +15 -15
- package/src/babel/handlers/dom/globals.ts +105 -105
- package/src/babel/handlers/dom/state.ts +107 -107
- package/src/babel/handlers/ssr/derived.ts +27 -27
- package/src/babel/handlers/ssr/state.ts +11 -11
- package/src/babel/index.ts +9 -0
- package/src/babel/jsx/anchor-mount.ts +24 -1
- package/src/babel/jsx/children.ts +53 -69
- package/src/babel/jsx/element.ts +76 -50
- package/src/babel/jsx/fragment.ts +8 -31
- package/src/babel/jsx/index.ts +2 -2
- package/src/babel/jsx/keyed-list.ts +309 -309
- package/src/babel/jsx/ssr.ts +351 -324
- package/src/babel/jsx/text-node.ts +192 -192
- package/src/babel/jsx/utils.ts +179 -179
- package/src/babel/util/bind.ts +139 -139
- package/src/babel/util/css.ts +151 -151
- package/src/babel/util/dead-code.ts +28 -28
- package/src/babel/util/helpers.ts +46 -46
- package/src/babel/util/magic.ts +11 -11
- package/src/bun-plugin.ts +92 -92
- package/src/vite/index.ts +58 -58
- package/test/async-components.test.ts +59 -59
- package/test/components.test.ts +129 -129
- package/test/containsSignal.test.ts +288 -288
- package/test/fragments.test.ts +90 -90
- package/test/helpers/transform.ts +45 -45
- package/test/hydration.test.ts +130 -1
- package/test/jsx.test.ts +227 -225
- package/test/keyed-lists.test.ts +126 -126
- package/test/reactivity.test.ts +225 -225
- package/test/scoped-css.test.ts +129 -129
- package/test/ssr.test.ts +130 -130
- package/test/style-object.test.ts +13 -0
- package/test/tree-shaking.test.ts +78 -78
- package/tsconfig.json +30 -30
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
import { type NodePath, types as t } from "@babel/core";
|
|
2
|
-
|
|
3
|
-
export function handleDerivedSSR(
|
|
4
|
-
declaration: NodePath<t.VariableDeclaration>,
|
|
5
|
-
declarator: NodePath<t.VariableDeclarator>,
|
|
6
|
-
init: NodePath<t.CallExpression>,
|
|
7
|
-
_signals?: Set<string>, // SSR has no reactivity, intentionally unused
|
|
8
|
-
): void {
|
|
9
|
-
const { id } = declarator.node;
|
|
10
|
-
if (!t.isIdentifier(id))
|
|
11
|
-
throw new Error("Destructured $derived not supported");
|
|
12
|
-
|
|
13
|
-
// Replace $derived(expr) with () => expr (lazy evaluation)
|
|
14
|
-
init.replaceWith(
|
|
15
|
-
t.arrowFunctionExpression([], init.node.arguments[0] as t.Expression),
|
|
16
|
-
);
|
|
17
|
-
declaration.node.kind = "const";
|
|
18
|
-
|
|
19
|
-
// Replace all references to this variable with () calls
|
|
20
|
-
const binding = declaration.scope.getBinding(id.name);
|
|
21
|
-
if (!binding) return;
|
|
22
|
-
for (const ref of [...binding.referencePaths]) {
|
|
23
|
-
if (!ref.isIdentifier()) continue;
|
|
24
|
-
if (ref.parentPath?.isUpdateExpression()) continue;
|
|
25
|
-
ref.replaceWith(t.callExpression(ref.node, []));
|
|
26
|
-
}
|
|
27
|
-
}
|
|
1
|
+
import { type NodePath, types as t } from "@babel/core";
|
|
2
|
+
|
|
3
|
+
export function handleDerivedSSR(
|
|
4
|
+
declaration: NodePath<t.VariableDeclaration>,
|
|
5
|
+
declarator: NodePath<t.VariableDeclarator>,
|
|
6
|
+
init: NodePath<t.CallExpression>,
|
|
7
|
+
_signals?: Set<string>, // SSR has no reactivity, intentionally unused
|
|
8
|
+
): void {
|
|
9
|
+
const { id } = declarator.node;
|
|
10
|
+
if (!t.isIdentifier(id))
|
|
11
|
+
throw new Error("Destructured $derived not supported");
|
|
12
|
+
|
|
13
|
+
// Replace $derived(expr) with () => expr (lazy evaluation)
|
|
14
|
+
init.replaceWith(
|
|
15
|
+
t.arrowFunctionExpression([], init.node.arguments[0] as t.Expression),
|
|
16
|
+
);
|
|
17
|
+
declaration.node.kind = "const";
|
|
18
|
+
|
|
19
|
+
// Replace all references to this variable with () calls
|
|
20
|
+
const binding = declaration.scope.getBinding(id.name);
|
|
21
|
+
if (!binding) return;
|
|
22
|
+
for (const ref of [...binding.referencePaths]) {
|
|
23
|
+
if (!ref.isIdentifier()) continue;
|
|
24
|
+
if (ref.parentPath?.isUpdateExpression()) continue;
|
|
25
|
+
ref.replaceWith(t.callExpression(ref.node, []));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type { NodePath, types as t } from "@babel/core";
|
|
2
|
-
|
|
3
|
-
export function handleStateSSR(
|
|
4
|
-
declaration: NodePath<t.VariableDeclaration>,
|
|
5
|
-
_declarator: NodePath<t.VariableDeclarator>,
|
|
6
|
-
init: NodePath<t.CallExpression>,
|
|
7
|
-
_signals?: Set<string>, // SSR has no reactivity. Left in for future reference
|
|
8
|
-
): void {
|
|
9
|
-
init.replaceWith(init.node.arguments[0] as t.Expression);
|
|
10
|
-
declaration.node.kind = "let";
|
|
11
|
-
}
|
|
1
|
+
import type { NodePath, types as t } from "@babel/core";
|
|
2
|
+
|
|
3
|
+
export function handleStateSSR(
|
|
4
|
+
declaration: NodePath<t.VariableDeclaration>,
|
|
5
|
+
_declarator: NodePath<t.VariableDeclarator>,
|
|
6
|
+
init: NodePath<t.CallExpression>,
|
|
7
|
+
_signals?: Set<string>, // SSR has no reactivity. Left in for future reference
|
|
8
|
+
): void {
|
|
9
|
+
init.replaceWith(init.node.arguments[0] as t.Expression);
|
|
10
|
+
declaration.node.kind = "let";
|
|
11
|
+
}
|
package/src/babel/index.ts
CHANGED
|
@@ -25,6 +25,14 @@ const SSR_HELPERS = template.statements.ast(`
|
|
|
25
25
|
if (v?.[__SAFE]) return v.v;
|
|
26
26
|
if (Array.isArray(v)) return v.map(x => x?.[__SAFE] ? x.v : __e(x)).join('');
|
|
27
27
|
if (v === false || v === null || v === undefined) return '';
|
|
28
|
+
if (typeof v === 'object' && v.constructor === Object) {
|
|
29
|
+
const UNITLESS = new Set(['animationIterationCount','borderImageOutset','borderImageSlice','borderImageWidth','boxFlex','boxFlexGroup','boxSnapAfterPatch','boxSupress','columnCount','columnFlex','columnFlexGrow','columns','flexGrow','flexPositive','flexShrink','flexNegative','fontWeight','gridArea','gridRow','gridRowEnd','gridRowSpan','gridRowStart','gridColumn','gridColumnEnd','gridColumnSpan','gridColumnStart','lineClamp','lineHeight','opacity','order','orphans','tabSize','widows','zIndex','zoom','fillOpacity','floodOpacity','stopOpacity','strokeDasharray','strokeDashoffset','strokeMiterlimit','strokeOpacity','strokeWidth']);
|
|
30
|
+
return Object.entries(v).map(([k, val]) => {
|
|
31
|
+
const prop = k.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
32
|
+
if (typeof val === 'number' && !UNITLESS.has(k)) return prop + ':' + val + 'px';
|
|
33
|
+
return prop + ':' + val;
|
|
34
|
+
}).join(';');
|
|
35
|
+
}
|
|
28
36
|
return String(v).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
29
37
|
};
|
|
30
38
|
`);
|
|
@@ -174,6 +182,7 @@ export default function sigilPlugin(): PluginObject {
|
|
|
174
182
|
"claimText",
|
|
175
183
|
"claimComment",
|
|
176
184
|
"hydrateKeyedList",
|
|
185
|
+
"hydrateSlot",
|
|
177
186
|
"insert",
|
|
178
187
|
"applyDirective",
|
|
179
188
|
"getHydrationNodes",
|
|
@@ -178,8 +178,24 @@ export function buildAnchorMount(
|
|
|
178
178
|
);
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
// typeof x === "function" ? x() : x — hydrate-mode component children are
|
|
182
|
+
// thunks, so a dynamic slot must call them instead of stringifying them.
|
|
183
|
+
const unwrapThunk = (name: string): t.Expression =>
|
|
184
|
+
t.conditionalExpression(
|
|
185
|
+
t.binaryExpression(
|
|
186
|
+
"===",
|
|
187
|
+
t.unaryExpression("typeof", t.identifier(name)),
|
|
188
|
+
t.stringLiteral("function"),
|
|
189
|
+
),
|
|
190
|
+
t.callExpression(t.identifier(name), []),
|
|
191
|
+
t.identifier(name),
|
|
192
|
+
);
|
|
193
|
+
|
|
181
194
|
// function _mountFn(_v, _a) { ... }
|
|
182
195
|
const mountBody = t.blockStatement([
|
|
196
|
+
t.expressionStatement(
|
|
197
|
+
t.assignmentExpression("=", t.identifier("_v"), unwrapThunk("_v")),
|
|
198
|
+
),
|
|
183
199
|
t.variableDeclaration("const", [
|
|
184
200
|
t.variableDeclarator(t.identifier("_nodes"), t.arrayExpression([])),
|
|
185
201
|
]),
|
|
@@ -190,11 +206,18 @@ export function buildAnchorMount(
|
|
|
190
206
|
),
|
|
191
207
|
t.blockStatement([
|
|
192
208
|
t.forOfStatement(
|
|
193
|
-
t.variableDeclaration("
|
|
209
|
+
t.variableDeclaration("let", [
|
|
194
210
|
t.variableDeclarator(t.identifier("_item")),
|
|
195
211
|
]),
|
|
196
212
|
t.identifier("_v"),
|
|
197
213
|
t.blockStatement([
|
|
214
|
+
t.expressionStatement(
|
|
215
|
+
t.assignmentExpression(
|
|
216
|
+
"=",
|
|
217
|
+
t.identifier("_item"),
|
|
218
|
+
unwrapThunk("_item"),
|
|
219
|
+
),
|
|
220
|
+
),
|
|
198
221
|
t.variableDeclaration("const", [
|
|
199
222
|
t.variableDeclarator(
|
|
200
223
|
t.identifier("_n"),
|
|
@@ -5,6 +5,13 @@ import { processFragment } from "./fragment";
|
|
|
5
5
|
/**
|
|
6
6
|
* Collect children from a JSX element into a children expression.
|
|
7
7
|
* Returns null if no meaningful children exist.
|
|
8
|
+
*
|
|
9
|
+
* DOM mode evaluates each JSX child immediately — the component receives real
|
|
10
|
+
* nodes. Hydrate mode emits thunks instead: a component's children live inside
|
|
11
|
+
* *its* root element, a subtree the call site cannot see, so they must claim
|
|
12
|
+
* from the pool that `hydrateSlot` pushes at the `{children}` site rather than
|
|
13
|
+
* from the caller's pool. Evaluating them eagerly here made them steal the
|
|
14
|
+
* component's own SSR root and re-render it (double render).
|
|
8
15
|
*/
|
|
9
16
|
export function collectChildren(
|
|
10
17
|
node: t.JSXElement,
|
|
@@ -17,95 +24,72 @@ export function collectChildren(
|
|
|
17
24
|
): t.Expression | null {
|
|
18
25
|
const childrenElements: t.Expression[] = [];
|
|
19
26
|
let hasOnlyText = true;
|
|
27
|
+
// Deferred children open their own scope from the hydration stack.
|
|
28
|
+
const childNodesVar = hydrate ? "__nodes" : nodesVar;
|
|
20
29
|
|
|
21
30
|
for (const child of node.children) {
|
|
22
31
|
if (t.isJSXText(child)) {
|
|
23
32
|
const text = child.value.trim();
|
|
24
33
|
if (!text) continue;
|
|
25
34
|
childrenElements.push(t.stringLiteral(text));
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let jsx: t.JSXElement | t.JSXFragment | null = null;
|
|
39
|
+
if (t.isJSXElement(child) || t.isJSXFragment(child)) {
|
|
40
|
+
jsx = child;
|
|
41
|
+
} else if (t.isJSXExpressionContainer(child)) {
|
|
42
|
+
const expr = child.expression;
|
|
43
|
+
if (t.isJSXEmptyExpression(expr)) continue;
|
|
44
|
+
if (t.isJSXElement(expr) || t.isJSXFragment(expr)) {
|
|
45
|
+
jsx = expr;
|
|
46
|
+
} else {
|
|
47
|
+
// Plain expression child — pass the value straight through.
|
|
48
|
+
hasOnlyText = false;
|
|
49
|
+
childrenElements.push(expr);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
hasOnlyText = false;
|
|
57
|
+
const childStatements: t.Statement[] = [];
|
|
58
|
+
const childVar = t.isJSXElement(jsx)
|
|
59
|
+
? processElement(
|
|
60
|
+
jsx,
|
|
33
61
|
childStatements,
|
|
34
62
|
genId,
|
|
35
63
|
signals,
|
|
36
64
|
hash,
|
|
37
65
|
hydrate,
|
|
38
|
-
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
child,
|
|
66
|
+
childNodesVar,
|
|
67
|
+
)
|
|
68
|
+
: processFragment(
|
|
69
|
+
jsx,
|
|
43
70
|
childStatements,
|
|
44
71
|
genId,
|
|
45
72
|
signals,
|
|
46
73
|
hash,
|
|
47
74
|
hydrate,
|
|
48
|
-
|
|
75
|
+
childNodesVar,
|
|
49
76
|
);
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
]),
|
|
77
|
+
|
|
78
|
+
const body: t.Statement[] = [];
|
|
79
|
+
if (hydrate) {
|
|
80
|
+
// const __nodes = getHydrationNodes() — resolved when the thunk runs
|
|
81
|
+
body.push(
|
|
82
|
+
t.variableDeclaration("const", [
|
|
83
|
+
t.variableDeclarator(
|
|
84
|
+
t.identifier("__nodes"),
|
|
85
|
+
t.callExpression(t.identifier("getHydrationNodes"), []),
|
|
60
86
|
),
|
|
61
|
-
|
|
62
|
-
),
|
|
87
|
+
]),
|
|
63
88
|
);
|
|
64
|
-
} else if (t.isJSXExpressionContainer(child)) {
|
|
65
|
-
const expr = child.expression as t.Expression;
|
|
66
|
-
if (t.isJSXElement(expr) || t.isJSXFragment(expr)) {
|
|
67
|
-
hasOnlyText = false;
|
|
68
|
-
const childStatements: t.Statement[] = [];
|
|
69
|
-
let childVar: string;
|
|
70
|
-
if (t.isJSXElement(expr)) {
|
|
71
|
-
childVar = processElement(
|
|
72
|
-
expr,
|
|
73
|
-
childStatements,
|
|
74
|
-
genId,
|
|
75
|
-
signals,
|
|
76
|
-
hash,
|
|
77
|
-
hydrate,
|
|
78
|
-
nodesVar,
|
|
79
|
-
);
|
|
80
|
-
} else {
|
|
81
|
-
childVar = processFragment(
|
|
82
|
-
expr,
|
|
83
|
-
childStatements,
|
|
84
|
-
genId,
|
|
85
|
-
signals,
|
|
86
|
-
hash,
|
|
87
|
-
hydrate,
|
|
88
|
-
nodesVar,
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
childrenElements.push(
|
|
92
|
-
t.callExpression(
|
|
93
|
-
t.arrowFunctionExpression(
|
|
94
|
-
[],
|
|
95
|
-
t.blockStatement([
|
|
96
|
-
...childStatements,
|
|
97
|
-
t.returnStatement(t.identifier(childVar)),
|
|
98
|
-
]),
|
|
99
|
-
),
|
|
100
|
-
[],
|
|
101
|
-
),
|
|
102
|
-
);
|
|
103
|
-
} else {
|
|
104
|
-
hasOnlyText = false;
|
|
105
|
-
// For dynamic expressions, just pass the expression directly
|
|
106
|
-
childrenElements.push(expr);
|
|
107
|
-
}
|
|
108
89
|
}
|
|
90
|
+
body.push(...childStatements, t.returnStatement(t.identifier(childVar)));
|
|
91
|
+
const thunk = t.arrowFunctionExpression([], t.blockStatement(body));
|
|
92
|
+
childrenElements.push(hydrate ? thunk : t.callExpression(thunk, []));
|
|
109
93
|
}
|
|
110
94
|
|
|
111
95
|
if (childrenElements.length === 0) return null;
|
package/src/babel/jsx/element.ts
CHANGED
|
@@ -35,6 +35,27 @@ function attrAccess(varName: string, attrName: string): t.MemberExpression {
|
|
|
35
35
|
);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Dashed names (data-*, aria-*, custom attributes) have no IDL property —
|
|
40
|
+
* `el["data-x"] = v` sets a bare JS property the DOM never reflects, so the
|
|
41
|
+
* attribute is invisible to selectors, getAttribute, and CSS. They must go
|
|
42
|
+
* through setAttribute.
|
|
43
|
+
*/
|
|
44
|
+
function isDashedAttr(attrName: string): boolean {
|
|
45
|
+
return attrName.includes("-");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function setAttrCall(
|
|
49
|
+
varName: string,
|
|
50
|
+
attrName: string,
|
|
51
|
+
value: t.Expression,
|
|
52
|
+
): t.CallExpression {
|
|
53
|
+
return t.callExpression(
|
|
54
|
+
t.memberExpression(t.identifier(varName), t.identifier("setAttribute")),
|
|
55
|
+
[t.stringLiteral(attrName), value],
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
38
59
|
/**
|
|
39
60
|
* Build an object property key (identifier for valid names, string literal for dashed).
|
|
40
61
|
*/
|
|
@@ -256,9 +277,7 @@ export function processElement(
|
|
|
256
277
|
t.callExpression(t.identifier("mountTransition"), [
|
|
257
278
|
t.identifier(varName),
|
|
258
279
|
expr,
|
|
259
|
-
t.stringLiteral(
|
|
260
|
-
attrNamespace as "transition" | "in" | "out",
|
|
261
|
-
),
|
|
280
|
+
t.stringLiteral(attrNamespace as "transition" | "in" | "out"),
|
|
262
281
|
]),
|
|
263
282
|
),
|
|
264
283
|
);
|
|
@@ -480,14 +499,35 @@ export function processElement(
|
|
|
480
499
|
`Hint: change to a $state() call.`,
|
|
481
500
|
);
|
|
482
501
|
}
|
|
502
|
+
} else if (attr.value === null) {
|
|
503
|
+
// bare attribute (<input disabled>, <div data-beacon>)
|
|
504
|
+
if (isDashedAttr(realAttr)) {
|
|
505
|
+
statements.push(
|
|
506
|
+
t.expressionStatement(
|
|
507
|
+
setAttrCall(varName, realAttr, t.stringLiteral("")),
|
|
508
|
+
),
|
|
509
|
+
);
|
|
510
|
+
} else {
|
|
511
|
+
statements.push(
|
|
512
|
+
t.expressionStatement(
|
|
513
|
+
t.assignmentExpression(
|
|
514
|
+
"=",
|
|
515
|
+
attrAccess(varName, realAttr),
|
|
516
|
+
t.booleanLiteral(true),
|
|
517
|
+
),
|
|
518
|
+
),
|
|
519
|
+
);
|
|
520
|
+
}
|
|
483
521
|
} else if (t.isStringLiteral(attr.value)) {
|
|
484
522
|
statements.push(
|
|
485
523
|
t.expressionStatement(
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
524
|
+
isDashedAttr(realAttr)
|
|
525
|
+
? setAttrCall(varName, realAttr, t.stringLiteral(attr.value.value))
|
|
526
|
+
: t.assignmentExpression(
|
|
527
|
+
"=",
|
|
528
|
+
attrAccess(varName, realAttr),
|
|
529
|
+
attr.value,
|
|
530
|
+
),
|
|
491
531
|
),
|
|
492
532
|
);
|
|
493
533
|
} else if (t.isJSXExpressionContainer(attr.value)) {
|
|
@@ -571,7 +611,7 @@ export function processElement(
|
|
|
571
611
|
),
|
|
572
612
|
);
|
|
573
613
|
} else {
|
|
574
|
-
// createEffect(() =>
|
|
614
|
+
// createEffect(() => setAttribute/prop = expr)
|
|
575
615
|
statements.push(
|
|
576
616
|
t.expressionStatement(
|
|
577
617
|
t.callExpression(t.identifier("createEffect"), [
|
|
@@ -579,11 +619,13 @@ export function processElement(
|
|
|
579
619
|
[],
|
|
580
620
|
t.blockStatement([
|
|
581
621
|
t.expressionStatement(
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
622
|
+
isDashedAttr(realAttr)
|
|
623
|
+
? setAttrCall(varName, realAttr, expr)
|
|
624
|
+
: t.assignmentExpression(
|
|
625
|
+
"=",
|
|
626
|
+
attrAccess(varName, realAttr),
|
|
627
|
+
expr,
|
|
628
|
+
),
|
|
587
629
|
),
|
|
588
630
|
]),
|
|
589
631
|
),
|
|
@@ -592,10 +634,16 @@ export function processElement(
|
|
|
592
634
|
);
|
|
593
635
|
}
|
|
594
636
|
} else {
|
|
595
|
-
// varName.realAttr = expr
|
|
637
|
+
// setAttribute/varName.realAttr = expr
|
|
596
638
|
statements.push(
|
|
597
639
|
t.expressionStatement(
|
|
598
|
-
|
|
640
|
+
isDashedAttr(realAttr)
|
|
641
|
+
? setAttrCall(varName, realAttr, expr)
|
|
642
|
+
: t.assignmentExpression(
|
|
643
|
+
"=",
|
|
644
|
+
attrAccess(varName, realAttr),
|
|
645
|
+
expr,
|
|
646
|
+
),
|
|
599
647
|
),
|
|
600
648
|
);
|
|
601
649
|
}
|
|
@@ -795,41 +843,19 @@ export function processElement(
|
|
|
795
843
|
} else {
|
|
796
844
|
// non-reactive: set once
|
|
797
845
|
if (hydrate) {
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
t.identifier(childNodesVar!),
|
|
805
|
-
t.identifier("length"),
|
|
806
|
-
);
|
|
807
|
-
// Pool has elements (initial load): SSR content already in DOM, just consume delimiters
|
|
808
|
-
const claimG = t.expressionStatement(
|
|
809
|
-
t.callExpression(t.identifier("claimComment"), claimCommentArgs),
|
|
810
|
-
);
|
|
811
|
-
const claimSlashG = (() => {
|
|
812
|
-
const args: t.Expression[] = [
|
|
813
|
-
t.identifier(childNodesVar!),
|
|
814
|
-
t.stringLiteral("/g"),
|
|
815
|
-
];
|
|
816
|
-
if (varName) args.push(t.identifier(varName));
|
|
817
|
-
return t.expressionStatement(
|
|
818
|
-
t.callExpression(t.identifier("claimComment"), args),
|
|
819
|
-
);
|
|
820
|
-
})();
|
|
821
|
-
// Pool empty (SPA nav): create and insert fresh elements
|
|
822
|
-
const insertCall = t.expressionStatement(
|
|
823
|
-
t.callExpression(t.identifier("insert"), [
|
|
824
|
-
t.identifier(varName),
|
|
825
|
-
expr,
|
|
826
|
-
]),
|
|
827
|
-
);
|
|
846
|
+
// hydrateSlot consumes the <!--g-->…<!--/g--> run and runs the
|
|
847
|
+
// value against the content SSR put inside it, so both deferred
|
|
848
|
+
// component children and inline JSX (`{list.map(…)}`) claim that
|
|
849
|
+
// subtree. The value must stay lazy: evaluating it as an argument
|
|
850
|
+
// would claim from the enclosing pool instead. Empty pool
|
|
851
|
+
// (SPA nav) falls back to a fresh insert.
|
|
828
852
|
statements.push(
|
|
829
|
-
t.
|
|
830
|
-
t.
|
|
831
|
-
|
|
832
|
-
|
|
853
|
+
t.expressionStatement(
|
|
854
|
+
t.callExpression(t.identifier("hydrateSlot"), [
|
|
855
|
+
t.identifier(childNodesVar!),
|
|
856
|
+
t.identifier(varName),
|
|
857
|
+
t.arrowFunctionExpression([], expr),
|
|
858
|
+
]),
|
|
833
859
|
),
|
|
834
860
|
);
|
|
835
861
|
} else {
|
|
@@ -217,41 +217,18 @@ export function processFragment(
|
|
|
217
217
|
}
|
|
218
218
|
} else {
|
|
219
219
|
if (hydrate) {
|
|
220
|
-
// Non-reactive expression in hydrate fragment
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
];
|
|
226
|
-
const poolLen = t.memberExpression(
|
|
227
|
-
t.identifier(currentNodesVar),
|
|
228
|
-
t.identifier("length"),
|
|
229
|
-
);
|
|
230
|
-
const claimG = t.expressionStatement(
|
|
231
|
-
t.callExpression(t.identifier("claimComment"), claimCommentArgs),
|
|
232
|
-
);
|
|
233
|
-
const claimSlashG = t.expressionStatement(
|
|
234
|
-
t.callExpression(t.identifier("claimComment"), [
|
|
235
|
-
t.identifier(currentNodesVar),
|
|
236
|
-
t.stringLiteral("/g"),
|
|
237
|
-
t.identifier(effectiveParent),
|
|
238
|
-
]),
|
|
239
|
-
);
|
|
240
|
-
// SPA navigation: pool empty, insert into effectiveParent
|
|
241
|
-
const elseBranch = t.blockStatement([
|
|
220
|
+
// Non-reactive expression in hydrate fragment: consume the
|
|
221
|
+
// <!--g-->…<!--/g--> run and hydrate its content. Kept lazy so
|
|
222
|
+
// the value claims from the slot, not the enclosing pool;
|
|
223
|
+
// empty pool (SPA nav) inserts fresh.
|
|
224
|
+
statements.push(
|
|
242
225
|
t.expressionStatement(
|
|
243
|
-
t.callExpression(t.identifier("
|
|
226
|
+
t.callExpression(t.identifier("hydrateSlot"), [
|
|
227
|
+
t.identifier(currentNodesVar),
|
|
244
228
|
t.identifier(effectiveParent),
|
|
245
|
-
expr,
|
|
229
|
+
t.arrowFunctionExpression([], expr),
|
|
246
230
|
]),
|
|
247
231
|
),
|
|
248
|
-
]);
|
|
249
|
-
statements.push(
|
|
250
|
-
t.ifStatement(
|
|
251
|
-
t.binaryExpression(">", poolLen, t.numericLiteral(0)),
|
|
252
|
-
t.blockStatement([claimG, claimSlashG]),
|
|
253
|
-
elseBranch,
|
|
254
|
-
),
|
|
255
232
|
);
|
|
256
233
|
} else {
|
|
257
234
|
statements.push(
|
package/src/babel/jsx/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { processElement } from "./element";
|
|
2
|
-
export { processFragment } from "./fragment";
|
|
1
|
+
export { processElement } from "./element";
|
|
2
|
+
export { processFragment } from "./fragment";
|