@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.
Files changed (40) hide show
  1. package/index.ts +4 -4
  2. package/jsr.json +8 -8
  3. package/package.json +2 -1
  4. package/src/babel/handlers/dom/derived.ts +62 -62
  5. package/src/babel/handlers/dom/effect.ts +15 -15
  6. package/src/babel/handlers/dom/globals.ts +105 -105
  7. package/src/babel/handlers/dom/state.ts +107 -107
  8. package/src/babel/handlers/ssr/derived.ts +27 -27
  9. package/src/babel/handlers/ssr/state.ts +11 -11
  10. package/src/babel/index.ts +9 -0
  11. package/src/babel/jsx/anchor-mount.ts +24 -1
  12. package/src/babel/jsx/children.ts +53 -69
  13. package/src/babel/jsx/element.ts +76 -50
  14. package/src/babel/jsx/fragment.ts +8 -31
  15. package/src/babel/jsx/index.ts +2 -2
  16. package/src/babel/jsx/keyed-list.ts +309 -309
  17. package/src/babel/jsx/ssr.ts +351 -324
  18. package/src/babel/jsx/text-node.ts +192 -192
  19. package/src/babel/jsx/utils.ts +179 -179
  20. package/src/babel/util/bind.ts +139 -139
  21. package/src/babel/util/css.ts +151 -151
  22. package/src/babel/util/dead-code.ts +28 -28
  23. package/src/babel/util/helpers.ts +46 -46
  24. package/src/babel/util/magic.ts +11 -11
  25. package/src/bun-plugin.ts +92 -92
  26. package/src/vite/index.ts +58 -58
  27. package/test/async-components.test.ts +59 -59
  28. package/test/components.test.ts +129 -129
  29. package/test/containsSignal.test.ts +288 -288
  30. package/test/fragments.test.ts +90 -90
  31. package/test/helpers/transform.ts +45 -45
  32. package/test/hydration.test.ts +130 -1
  33. package/test/jsx.test.ts +227 -225
  34. package/test/keyed-lists.test.ts +126 -126
  35. package/test/reactivity.test.ts +225 -225
  36. package/test/scoped-css.test.ts +129 -129
  37. package/test/ssr.test.ts +130 -130
  38. package/test/style-object.test.ts +13 -0
  39. package/test/tree-shaking.test.ts +78 -78
  40. package/tsconfig.json +30 -30
@@ -1,179 +1,179 @@
1
- import { types as t } from "@babel/core";
2
-
3
- export const ATTR_MAP: Record<string, string> = {
4
- class: "className",
5
- for: "htmlFor",
6
- };
7
-
8
- export function containsSignal(
9
- expr: t.Expression | t.Node,
10
- signals: Set<string>,
11
- ): boolean {
12
- if (!expr) return false;
13
- if (t.isIdentifier(expr)) return signals.has(expr.name);
14
- if (t.isCallExpression(expr)) {
15
- return (
16
- containsSignal(expr.callee as t.Expression, signals) ||
17
- expr.arguments.some((a) => containsSignal(a as t.Expression, signals))
18
- );
19
- }
20
- if (t.isBinaryExpression(expr)) {
21
- return (
22
- containsSignal(expr.left as t.Expression, signals) ||
23
- containsSignal(expr.right, signals)
24
- );
25
- }
26
- if (t.isMemberExpression(expr)) {
27
- return containsSignal(expr.object as t.Expression, signals);
28
- }
29
- if (t.isLogicalExpression(expr)) {
30
- return (
31
- containsSignal(expr.left as t.Expression, signals) ||
32
- containsSignal(expr.right as t.Expression, signals)
33
- );
34
- }
35
- if (t.isConditionalExpression(expr)) {
36
- return (
37
- containsSignal(expr.test as t.Expression, signals) ||
38
- containsSignal(expr.consequent as t.Expression, signals) ||
39
- containsSignal(expr.alternate as t.Expression, signals)
40
- );
41
- }
42
- if (t.isUnaryExpression(expr)) {
43
- return containsSignal(expr.argument as t.Expression, signals);
44
- }
45
- if (t.isTemplateLiteral(expr)) {
46
- return expr.expressions.some((e) =>
47
- containsSignal(e as t.Expression, signals),
48
- );
49
- }
50
- // ── additions ──
51
- if (t.isArrayExpression(expr)) {
52
- return expr.elements.some(
53
- (e) => e !== null && containsSignal(e as t.Expression, signals),
54
- );
55
- }
56
- if (t.isObjectExpression(expr)) {
57
- return expr.properties.some((p) => {
58
- if (t.isObjectProperty(p)) {
59
- const computedKey = p.computed
60
- ? containsSignal(p.key as t.Expression, signals)
61
- : false;
62
- return computedKey || containsSignal(p.value as t.Expression, signals);
63
- }
64
- if (t.isSpreadElement(p)) return containsSignal(p.argument, signals);
65
- return false;
66
- });
67
- }
68
- if (t.isSequenceExpression(expr)) {
69
- return expr.expressions.some((e) => containsSignal(e, signals));
70
- }
71
- if (t.isAssignmentExpression(expr)) {
72
- return containsSignal(expr.right, signals);
73
- }
74
- if (t.isSpreadElement(expr)) {
75
- return containsSignal(expr.argument, signals);
76
- }
77
- // ArrowFunction/Function — don't traverse, signals inside are captured
78
- // not reactive at the call site (onclick={() => count()} is fine as-is)
79
- if (t.isArrowFunctionExpression(expr) || t.isFunctionExpression(expr)) {
80
- return false;
81
- }
82
- // Babel represents ?. as distinct node types not in t.is* guards
83
- if (t.isOptionalMemberExpression(expr)) {
84
- return containsSignal((expr as any).object, signals);
85
- }
86
- if (t.isOptionalCallExpression(expr)) {
87
- const n = expr as any;
88
- return (
89
- containsSignal(n.callee, signals) ||
90
- n.arguments.some((a: t.Node) => containsSignal(a, signals))
91
- );
92
- }
93
- return false;
94
- }
95
-
96
- export function isPrimitive(expr: t.Expression): boolean {
97
- if (t.isCallExpression(expr) && t.isIdentifier(expr.callee)) return true;
98
- if (t.isBinaryExpression(expr)) return true;
99
- if (t.isTemplateLiteral(expr)) return true;
100
- if (t.isIdentifier(expr)) return true;
101
- // user().name is a MemberExpression after state handler rewrites user → user()
102
- if (t.isMemberExpression(expr)) return true;
103
- return false;
104
- }
105
-
106
- /**
107
- * Generate createElement call. In hydrate mode, claims from node pool.
108
- * When parentVar is provided, passes it to claim() for SPA fallback (append on create).
109
- */
110
- export function getCreateElement(
111
- tag: string,
112
- hydrate: boolean,
113
- nodesVar = "__nodes",
114
- parentVar?: string,
115
- ): t.Expression {
116
- if (hydrate) {
117
- const args: t.Expression[] = [t.identifier(nodesVar), t.stringLiteral(tag)];
118
- if (parentVar) {
119
- args.push(t.identifier(parentVar));
120
- }
121
- return t.callExpression(t.identifier("claim"), args);
122
- }
123
- return t.callExpression(
124
- t.memberExpression(t.identifier("document"), t.identifier("createElement")),
125
- [t.stringLiteral(tag)],
126
- );
127
- }
128
-
129
- /**
130
- * Generate createTextNode call. In hydrate mode, claims from node pool.
131
- * When parentVar is provided, passes it to claimText() for SPA fallback.
132
- */
133
- export function getCreateText(
134
- hydrate: boolean,
135
- nodesVar = "__nodes",
136
- parentVar?: string,
137
- ): t.Expression {
138
- if (hydrate) {
139
- const args: t.Expression[] = [t.identifier(nodesVar)];
140
- if (parentVar) {
141
- args.push(t.identifier(parentVar));
142
- }
143
- return t.callExpression(t.identifier("claimText"), args);
144
- }
145
- return t.callExpression(
146
- t.memberExpression(
147
- t.identifier("document"),
148
- t.identifier("createTextNode"),
149
- ),
150
- [t.stringLiteral("")],
151
- );
152
- }
153
-
154
- /**
155
- * Generate `const <nodesVar> = Array.from(el.childNodes)` for hydration scope.
156
- * Returns the generated variable name for use by children.
157
- */
158
- export function buildHydrationScope(
159
- parentVar: string,
160
- statements: t.Statement[],
161
- nodesVar: string,
162
- ): void {
163
- statements.push(
164
- t.variableDeclaration("const", [
165
- t.variableDeclarator(
166
- t.identifier(nodesVar),
167
- t.callExpression(
168
- t.memberExpression(t.identifier("Array"), t.identifier("from")),
169
- [
170
- t.memberExpression(
171
- t.identifier(parentVar),
172
- t.identifier("childNodes"),
173
- ),
174
- ],
175
- ),
176
- ),
177
- ]),
178
- );
179
- }
1
+ import { types as t } from "@babel/core";
2
+
3
+ export const ATTR_MAP: Record<string, string> = {
4
+ class: "className",
5
+ for: "htmlFor",
6
+ };
7
+
8
+ export function containsSignal(
9
+ expr: t.Expression | t.Node,
10
+ signals: Set<string>,
11
+ ): boolean {
12
+ if (!expr) return false;
13
+ if (t.isIdentifier(expr)) return signals.has(expr.name);
14
+ if (t.isCallExpression(expr)) {
15
+ return (
16
+ containsSignal(expr.callee as t.Expression, signals) ||
17
+ expr.arguments.some((a) => containsSignal(a as t.Expression, signals))
18
+ );
19
+ }
20
+ if (t.isBinaryExpression(expr)) {
21
+ return (
22
+ containsSignal(expr.left as t.Expression, signals) ||
23
+ containsSignal(expr.right, signals)
24
+ );
25
+ }
26
+ if (t.isMemberExpression(expr)) {
27
+ return containsSignal(expr.object as t.Expression, signals);
28
+ }
29
+ if (t.isLogicalExpression(expr)) {
30
+ return (
31
+ containsSignal(expr.left as t.Expression, signals) ||
32
+ containsSignal(expr.right as t.Expression, signals)
33
+ );
34
+ }
35
+ if (t.isConditionalExpression(expr)) {
36
+ return (
37
+ containsSignal(expr.test as t.Expression, signals) ||
38
+ containsSignal(expr.consequent as t.Expression, signals) ||
39
+ containsSignal(expr.alternate as t.Expression, signals)
40
+ );
41
+ }
42
+ if (t.isUnaryExpression(expr)) {
43
+ return containsSignal(expr.argument as t.Expression, signals);
44
+ }
45
+ if (t.isTemplateLiteral(expr)) {
46
+ return expr.expressions.some((e) =>
47
+ containsSignal(e as t.Expression, signals),
48
+ );
49
+ }
50
+ // ── additions ──
51
+ if (t.isArrayExpression(expr)) {
52
+ return expr.elements.some(
53
+ (e) => e !== null && containsSignal(e as t.Expression, signals),
54
+ );
55
+ }
56
+ if (t.isObjectExpression(expr)) {
57
+ return expr.properties.some((p) => {
58
+ if (t.isObjectProperty(p)) {
59
+ const computedKey = p.computed
60
+ ? containsSignal(p.key as t.Expression, signals)
61
+ : false;
62
+ return computedKey || containsSignal(p.value as t.Expression, signals);
63
+ }
64
+ if (t.isSpreadElement(p)) return containsSignal(p.argument, signals);
65
+ return false;
66
+ });
67
+ }
68
+ if (t.isSequenceExpression(expr)) {
69
+ return expr.expressions.some((e) => containsSignal(e, signals));
70
+ }
71
+ if (t.isAssignmentExpression(expr)) {
72
+ return containsSignal(expr.right, signals);
73
+ }
74
+ if (t.isSpreadElement(expr)) {
75
+ return containsSignal(expr.argument, signals);
76
+ }
77
+ // ArrowFunction/Function — don't traverse, signals inside are captured
78
+ // not reactive at the call site (onclick={() => count()} is fine as-is)
79
+ if (t.isArrowFunctionExpression(expr) || t.isFunctionExpression(expr)) {
80
+ return false;
81
+ }
82
+ // Babel represents ?. as distinct node types not in t.is* guards
83
+ if (t.isOptionalMemberExpression(expr)) {
84
+ return containsSignal((expr as any).object, signals);
85
+ }
86
+ if (t.isOptionalCallExpression(expr)) {
87
+ const n = expr as any;
88
+ return (
89
+ containsSignal(n.callee, signals) ||
90
+ n.arguments.some((a: t.Node) => containsSignal(a, signals))
91
+ );
92
+ }
93
+ return false;
94
+ }
95
+
96
+ export function isPrimitive(expr: t.Expression): boolean {
97
+ if (t.isCallExpression(expr) && t.isIdentifier(expr.callee)) return true;
98
+ if (t.isBinaryExpression(expr)) return true;
99
+ if (t.isTemplateLiteral(expr)) return true;
100
+ if (t.isIdentifier(expr)) return true;
101
+ // user().name is a MemberExpression after state handler rewrites user → user()
102
+ if (t.isMemberExpression(expr)) return true;
103
+ return false;
104
+ }
105
+
106
+ /**
107
+ * Generate createElement call. In hydrate mode, claims from node pool.
108
+ * When parentVar is provided, passes it to claim() for SPA fallback (append on create).
109
+ */
110
+ export function getCreateElement(
111
+ tag: string,
112
+ hydrate: boolean,
113
+ nodesVar = "__nodes",
114
+ parentVar?: string,
115
+ ): t.Expression {
116
+ if (hydrate) {
117
+ const args: t.Expression[] = [t.identifier(nodesVar), t.stringLiteral(tag)];
118
+ if (parentVar) {
119
+ args.push(t.identifier(parentVar));
120
+ }
121
+ return t.callExpression(t.identifier("claim"), args);
122
+ }
123
+ return t.callExpression(
124
+ t.memberExpression(t.identifier("document"), t.identifier("createElement")),
125
+ [t.stringLiteral(tag)],
126
+ );
127
+ }
128
+
129
+ /**
130
+ * Generate createTextNode call. In hydrate mode, claims from node pool.
131
+ * When parentVar is provided, passes it to claimText() for SPA fallback.
132
+ */
133
+ export function getCreateText(
134
+ hydrate: boolean,
135
+ nodesVar = "__nodes",
136
+ parentVar?: string,
137
+ ): t.Expression {
138
+ if (hydrate) {
139
+ const args: t.Expression[] = [t.identifier(nodesVar)];
140
+ if (parentVar) {
141
+ args.push(t.identifier(parentVar));
142
+ }
143
+ return t.callExpression(t.identifier("claimText"), args);
144
+ }
145
+ return t.callExpression(
146
+ t.memberExpression(
147
+ t.identifier("document"),
148
+ t.identifier("createTextNode"),
149
+ ),
150
+ [t.stringLiteral("")],
151
+ );
152
+ }
153
+
154
+ /**
155
+ * Generate `const <nodesVar> = Array.from(el.childNodes)` for hydration scope.
156
+ * Returns the generated variable name for use by children.
157
+ */
158
+ export function buildHydrationScope(
159
+ parentVar: string,
160
+ statements: t.Statement[],
161
+ nodesVar: string,
162
+ ): void {
163
+ statements.push(
164
+ t.variableDeclaration("const", [
165
+ t.variableDeclarator(
166
+ t.identifier(nodesVar),
167
+ t.callExpression(
168
+ t.memberExpression(t.identifier("Array"), t.identifier("from")),
169
+ [
170
+ t.memberExpression(
171
+ t.identifier(parentVar),
172
+ t.identifier("childNodes"),
173
+ ),
174
+ ],
175
+ ),
176
+ ),
177
+ ]),
178
+ );
179
+ }
@@ -1,139 +1,139 @@
1
- import { types as t } from "@babel/core";
2
-
3
- const BIND_EVENT: Record<string, string> = {
4
- value: "input",
5
- checked: "change",
6
- innerHTML: "input",
7
- textContent: "input",
8
- group: "change",
9
- };
10
-
11
- export function buildBind(
12
- varName: string,
13
- bindProp: string,
14
- attr: t.JSXAttribute,
15
- statements: t.Statement[],
16
- ): void {
17
- const signal = (attr.value as t.JSXExpressionContainer)
18
- .expression as t.CallExpression; // already text()
19
-
20
- const signalId = signal.callee as t.Identifier; // text — for .set()
21
-
22
- const event = BIND_EVENT[bindProp] ?? "input";
23
-
24
- // createEffect(() => _el.prop = text())
25
- statements.push(
26
- t.expressionStatement(
27
- t.callExpression(t.identifier("createEffect"), [
28
- t.arrowFunctionExpression(
29
- [],
30
- t.blockStatement([
31
- t.expressionStatement(
32
- t.assignmentExpression(
33
- "=",
34
- t.memberExpression(
35
- t.identifier(varName),
36
- t.identifier(bindProp),
37
- ),
38
- signal,
39
- ),
40
- ),
41
- ]),
42
- ),
43
- ]),
44
- ),
45
- );
46
-
47
- // addEventListener(event, e => text.set(e.target.prop))
48
- statements.push(
49
- t.expressionStatement(
50
- t.callExpression(
51
- t.memberExpression(
52
- t.identifier(varName),
53
- t.identifier("addEventListener"),
54
- ),
55
- [
56
- t.stringLiteral(event),
57
- t.arrowFunctionExpression(
58
- [t.identifier("e")],
59
- t.callExpression(
60
- t.memberExpression(signalId, t.identifier("set")),
61
- [
62
- t.memberExpression(
63
- t.memberExpression(t.identifier("e"), t.identifier("target")),
64
- t.identifier(bindProp),
65
- ),
66
- ],
67
- ),
68
- ),
69
- ],
70
- ),
71
- ),
72
- );
73
- }
74
-
75
- export function buildProxyBind(
76
- varName: string,
77
- bindProp: string,
78
- _attr: t.JSXAttribute,
79
- statements: t.Statement[],
80
- signal: t.MemberExpression, // already form().name
81
- ): void {
82
- const event = BIND_EVENT[bindProp] ?? "input";
83
-
84
- // createEffect(() => el.prop = form().name)
85
- statements.push(
86
- t.expressionStatement(
87
- t.callExpression(t.identifier("createEffect"), [
88
- t.arrowFunctionExpression(
89
- [],
90
- t.blockStatement([
91
- t.expressionStatement(
92
- t.assignmentExpression(
93
- "=",
94
- t.memberExpression(
95
- t.identifier(varName),
96
- t.identifier(bindProp),
97
- ),
98
- signal,
99
- ),
100
- ),
101
- ]),
102
- ),
103
- ]),
104
- ),
105
- );
106
-
107
- // addEventListener(event, e => { form().name = e.target.prop })
108
- statements.push(
109
- t.expressionStatement(
110
- t.callExpression(
111
- t.memberExpression(
112
- t.identifier(varName),
113
- t.identifier("addEventListener"),
114
- ),
115
- [
116
- t.stringLiteral(event),
117
- t.arrowFunctionExpression(
118
- [t.identifier("e")],
119
- t.blockStatement([
120
- t.expressionStatement(
121
- t.assignmentExpression(
122
- "=",
123
- signal as unknown as t.LVal, // form().name — valid LVal
124
- t.memberExpression(
125
- t.memberExpression(
126
- t.identifier("e"),
127
- t.identifier("target"),
128
- ),
129
- t.identifier(bindProp),
130
- ),
131
- ),
132
- ),
133
- ]),
134
- ),
135
- ],
136
- ),
137
- ),
138
- );
139
- }
1
+ import { types as t } from "@babel/core";
2
+
3
+ const BIND_EVENT: Record<string, string> = {
4
+ value: "input",
5
+ checked: "change",
6
+ innerHTML: "input",
7
+ textContent: "input",
8
+ group: "change",
9
+ };
10
+
11
+ export function buildBind(
12
+ varName: string,
13
+ bindProp: string,
14
+ attr: t.JSXAttribute,
15
+ statements: t.Statement[],
16
+ ): void {
17
+ const signal = (attr.value as t.JSXExpressionContainer)
18
+ .expression as t.CallExpression; // already text()
19
+
20
+ const signalId = signal.callee as t.Identifier; // text — for .set()
21
+
22
+ const event = BIND_EVENT[bindProp] ?? "input";
23
+
24
+ // createEffect(() => _el.prop = text())
25
+ statements.push(
26
+ t.expressionStatement(
27
+ t.callExpression(t.identifier("createEffect"), [
28
+ t.arrowFunctionExpression(
29
+ [],
30
+ t.blockStatement([
31
+ t.expressionStatement(
32
+ t.assignmentExpression(
33
+ "=",
34
+ t.memberExpression(
35
+ t.identifier(varName),
36
+ t.identifier(bindProp),
37
+ ),
38
+ signal,
39
+ ),
40
+ ),
41
+ ]),
42
+ ),
43
+ ]),
44
+ ),
45
+ );
46
+
47
+ // addEventListener(event, e => text.set(e.target.prop))
48
+ statements.push(
49
+ t.expressionStatement(
50
+ t.callExpression(
51
+ t.memberExpression(
52
+ t.identifier(varName),
53
+ t.identifier("addEventListener"),
54
+ ),
55
+ [
56
+ t.stringLiteral(event),
57
+ t.arrowFunctionExpression(
58
+ [t.identifier("e")],
59
+ t.callExpression(
60
+ t.memberExpression(signalId, t.identifier("set")),
61
+ [
62
+ t.memberExpression(
63
+ t.memberExpression(t.identifier("e"), t.identifier("target")),
64
+ t.identifier(bindProp),
65
+ ),
66
+ ],
67
+ ),
68
+ ),
69
+ ],
70
+ ),
71
+ ),
72
+ );
73
+ }
74
+
75
+ export function buildProxyBind(
76
+ varName: string,
77
+ bindProp: string,
78
+ _attr: t.JSXAttribute,
79
+ statements: t.Statement[],
80
+ signal: t.MemberExpression, // already form().name
81
+ ): void {
82
+ const event = BIND_EVENT[bindProp] ?? "input";
83
+
84
+ // createEffect(() => el.prop = form().name)
85
+ statements.push(
86
+ t.expressionStatement(
87
+ t.callExpression(t.identifier("createEffect"), [
88
+ t.arrowFunctionExpression(
89
+ [],
90
+ t.blockStatement([
91
+ t.expressionStatement(
92
+ t.assignmentExpression(
93
+ "=",
94
+ t.memberExpression(
95
+ t.identifier(varName),
96
+ t.identifier(bindProp),
97
+ ),
98
+ signal,
99
+ ),
100
+ ),
101
+ ]),
102
+ ),
103
+ ]),
104
+ ),
105
+ );
106
+
107
+ // addEventListener(event, e => { form().name = e.target.prop })
108
+ statements.push(
109
+ t.expressionStatement(
110
+ t.callExpression(
111
+ t.memberExpression(
112
+ t.identifier(varName),
113
+ t.identifier("addEventListener"),
114
+ ),
115
+ [
116
+ t.stringLiteral(event),
117
+ t.arrowFunctionExpression(
118
+ [t.identifier("e")],
119
+ t.blockStatement([
120
+ t.expressionStatement(
121
+ t.assignmentExpression(
122
+ "=",
123
+ signal as unknown as t.LVal, // form().name — valid LVal
124
+ t.memberExpression(
125
+ t.memberExpression(
126
+ t.identifier("e"),
127
+ t.identifier("target"),
128
+ ),
129
+ t.identifier(bindProp),
130
+ ),
131
+ ),
132
+ ),
133
+ ]),
134
+ ),
135
+ ],
136
+ ),
137
+ ),
138
+ );
139
+ }