@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
package/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- // packages/compiler/index.ts
2
-
3
- export { default as sigilPlugin } from "./src/babel/index.ts";
4
- export { sigil } from "./src/vite/index.ts";
1
+ // packages/compiler/index.ts
2
+
3
+ export { default as sigilPlugin } from "./src/babel/index.ts";
4
+ export { sigil } from "./src/vite/index.ts";
package/jsr.json CHANGED
@@ -1,8 +1,8 @@
1
- {
2
- "name": "@sigil-dev/compiler",
3
- "version": "0.6.0",
4
- "exports": "./index.ts",
5
- "publish": {
6
- "exclude": ["src/**/*.test.ts"]
7
- }
8
- }
1
+ {
2
+ "name": "@sigil-dev/compiler",
3
+ "version": "0.6.0",
4
+ "exports": "./index.ts",
5
+ "publish": {
6
+ "exclude": ["src/**/*.test.ts"]
7
+ }
8
+ }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@sigil-dev/compiler",
3
3
  "module": "index.ts",
4
4
  "type": "module",
5
- "version": "0.9.4",
5
+ "version": "0.9.6",
6
6
  "private": false,
7
7
  "description": "Compiler for the Sigil framework",
8
8
  "peerDependencies": {
@@ -11,6 +11,7 @@
11
11
  "exports": {
12
12
  ".": "./index.ts",
13
13
  "./babel": "./src/babel/index.ts",
14
+ "./css": "./src/babel/util/css.ts",
14
15
  "./vite": "./src/vite/index.ts",
15
16
  "./bun": "./src/bun-plugin.ts"
16
17
  },
@@ -1,62 +1,62 @@
1
- import type { NodePath } from "@babel/core";
2
- import { types as t } from "@babel/core";
3
- import { MAGIC } from "../../util/magic.ts";
4
-
5
- const setCall = (name: string, value: t.Expression) =>
6
- t.callExpression(
7
- t.memberExpression(t.identifier(name), t.identifier("set")),
8
- [value],
9
- );
10
-
11
- export function handleDerived(
12
- declaration: NodePath<t.VariableDeclaration>,
13
- declarator: NodePath<t.VariableDeclarator>,
14
- init: NodePath<t.CallExpression>,
15
- signals: Set<string>,
16
- ): void {
17
- const { id } = declarator.node;
18
- if (!t.isIdentifier(id))
19
- throw new Error("Destructured $derived not supported");
20
- const name = id.name;
21
- signals.add(id.name);
22
-
23
- const binding = declaration.scope.getBinding(name);
24
- if (!binding) return;
25
- const refs = [...binding.referencePaths];
26
- const violations = [...binding.constantViolations];
27
-
28
- init.replaceWith(
29
- t.callExpression(t.identifier(MAGIC.createMemo), [
30
- t.arrowFunctionExpression([], init.node.arguments[0] as t.Expression),
31
- ]),
32
- );
33
- declaration.node.kind = "let";
34
-
35
- // A6: $derived reassign — convert assignments to .set() calls
36
- for (const violation of violations) {
37
- if (violation.isAssignmentExpression()) {
38
- const { operator, right } = violation.node;
39
- if (operator === "=") {
40
- violation.replaceWith(setCall(name, right));
41
- } else {
42
- const baseOp = operator.slice(0, -1);
43
- violation.replaceWith(
44
- setCall(
45
- name,
46
- t.binaryExpression(
47
- baseOp as t.BinaryExpression["operator"],
48
- t.callExpression(t.identifier(name), []),
49
- right,
50
- ),
51
- ),
52
- );
53
- }
54
- }
55
- }
56
-
57
- for (const ref of refs) {
58
- if (!ref.isIdentifier()) continue;
59
- if (ref.parentPath?.isUpdateExpression()) continue;
60
- ref.replaceWith(t.callExpression(ref.node, []));
61
- }
62
- }
1
+ import type { NodePath } from "@babel/core";
2
+ import { types as t } from "@babel/core";
3
+ import { MAGIC } from "../../util/magic.ts";
4
+
5
+ const setCall = (name: string, value: t.Expression) =>
6
+ t.callExpression(
7
+ t.memberExpression(t.identifier(name), t.identifier("set")),
8
+ [value],
9
+ );
10
+
11
+ export function handleDerived(
12
+ declaration: NodePath<t.VariableDeclaration>,
13
+ declarator: NodePath<t.VariableDeclarator>,
14
+ init: NodePath<t.CallExpression>,
15
+ signals: Set<string>,
16
+ ): void {
17
+ const { id } = declarator.node;
18
+ if (!t.isIdentifier(id))
19
+ throw new Error("Destructured $derived not supported");
20
+ const name = id.name;
21
+ signals.add(id.name);
22
+
23
+ const binding = declaration.scope.getBinding(name);
24
+ if (!binding) return;
25
+ const refs = [...binding.referencePaths];
26
+ const violations = [...binding.constantViolations];
27
+
28
+ init.replaceWith(
29
+ t.callExpression(t.identifier(MAGIC.createMemo), [
30
+ t.arrowFunctionExpression([], init.node.arguments[0] as t.Expression),
31
+ ]),
32
+ );
33
+ declaration.node.kind = "let";
34
+
35
+ // A6: $derived reassign — convert assignments to .set() calls
36
+ for (const violation of violations) {
37
+ if (violation.isAssignmentExpression()) {
38
+ const { operator, right } = violation.node;
39
+ if (operator === "=") {
40
+ violation.replaceWith(setCall(name, right));
41
+ } else {
42
+ const baseOp = operator.slice(0, -1);
43
+ violation.replaceWith(
44
+ setCall(
45
+ name,
46
+ t.binaryExpression(
47
+ baseOp as t.BinaryExpression["operator"],
48
+ t.callExpression(t.identifier(name), []),
49
+ right,
50
+ ),
51
+ ),
52
+ );
53
+ }
54
+ }
55
+ }
56
+
57
+ for (const ref of refs) {
58
+ if (!ref.isIdentifier()) continue;
59
+ if (ref.parentPath?.isUpdateExpression()) continue;
60
+ ref.replaceWith(t.callExpression(ref.node, []));
61
+ }
62
+ }
@@ -1,15 +1,15 @@
1
- import type { NodePath, types as t } from "@babel/core";
2
- import { MAGIC } from "../../util/magic.ts";
3
-
4
- export function handleEffect(statement: NodePath<t.ExpressionStatement>): void {
5
- const expr = statement.get("expression");
6
- if (!expr.isCallExpression()) return;
7
- const callee = expr.get("callee");
8
- if (!callee.isIdentifier()) return;
9
- if (callee.node.name === "$effect") callee.node.name = MAGIC.createEffect;
10
- else if (callee.node.name === "$derived") {
11
- throw statement.buildCodeFrameError(
12
- "$derived must be assigned to a variable: let doubled = $derived(count*2)",
13
- );
14
- }
15
- }
1
+ import type { NodePath, types as t } from "@babel/core";
2
+ import { MAGIC } from "../../util/magic.ts";
3
+
4
+ export function handleEffect(statement: NodePath<t.ExpressionStatement>): void {
5
+ const expr = statement.get("expression");
6
+ if (!expr.isCallExpression()) return;
7
+ const callee = expr.get("callee");
8
+ if (!callee.isIdentifier()) return;
9
+ if (callee.node.name === "$effect") callee.node.name = MAGIC.createEffect;
10
+ else if (callee.node.name === "$derived") {
11
+ throw statement.buildCodeFrameError(
12
+ "$derived must be assigned to a variable: let doubled = $derived(count*2)",
13
+ );
14
+ }
15
+ }
@@ -1,105 +1,105 @@
1
- import { types as t } from "@babel/core";
2
-
3
- const GLOBAL_TARGETS: Record<string, t.Expression> = {
4
- Window: t.identifier("window"),
5
- Document: t.identifier("document"),
6
- Body: t.memberExpression(t.identifier("document"), t.identifier("body")),
7
- };
8
-
9
- export function isGlobalComponent(tag: string): boolean {
10
- return tag in GLOBAL_TARGETS;
11
- }
12
-
13
- export function handleGlobalComponent(
14
- tag: string,
15
- node: t.JSXElement,
16
- statements: t.Statement[],
17
- genId: () => string,
18
- ): string {
19
- const varName = genId();
20
- const targetExpr = GLOBAL_TARGETS[tag];
21
-
22
- for (const attr of node.openingElement.attributes) {
23
- if (!t.isJSXAttribute(attr)) continue;
24
-
25
- let attrName: string;
26
- if (t.isJSXNamespacedName(attr.name)) {
27
- attrName = attr.name.name.name;
28
- } else {
29
- attrName = (attr.name as t.JSXIdentifier).name;
30
- }
31
-
32
- if (/^on[A-Z]/.test(attrName)) {
33
- const event = attrName.slice(2).toLowerCase();
34
- const handler = (attr.value as t.JSXExpressionContainer)
35
- .expression as t.Expression;
36
-
37
- // createEffect(() => {
38
- // if (typeof window === "undefined") return;
39
- // target.addEventListener(ev, handler);
40
- // return () => target.removeEventListener(ev, handler);
41
- // })
42
- statements.push(
43
- t.expressionStatement(
44
- t.callExpression(t.identifier("createEffect"), [
45
- t.arrowFunctionExpression(
46
- [],
47
- t.blockStatement([
48
- t.ifStatement(
49
- t.binaryExpression(
50
- "===",
51
- //@ts-expect-error i dont think it can be undefined here
52
- t.unaryExpression("typeof", targetExpr),
53
- t.stringLiteral("undefined"),
54
- ),
55
- t.returnStatement(),
56
- ),
57
- t.expressionStatement(
58
- t.callExpression(
59
- t.memberExpression(
60
- //@ts-expect-error i dont think it can be undefined here
61
- targetExpr,
62
- t.identifier("addEventListener"),
63
- ),
64
- [t.stringLiteral(event), handler],
65
- ),
66
- ),
67
- t.returnStatement(
68
- t.arrowFunctionExpression(
69
- [],
70
- t.callExpression(
71
- t.memberExpression(
72
- //@ts-expect-error i dont think it can be undefined here
73
- targetExpr,
74
- t.identifier("removeEventListener"),
75
- ),
76
- [t.stringLiteral(event), handler],
77
- ),
78
- ),
79
- ),
80
- ]),
81
- ),
82
- ]),
83
- ),
84
- );
85
- }
86
- }
87
-
88
- // Placeholder comment node for the component's return value
89
- statements.push(
90
- t.variableDeclaration("const", [
91
- t.variableDeclarator(
92
- t.identifier(varName),
93
- t.callExpression(
94
- t.memberExpression(
95
- t.identifier("document"),
96
- t.identifier("createComment"),
97
- ),
98
- [t.stringLiteral(tag.toLowerCase())],
99
- ),
100
- ),
101
- ]),
102
- );
103
-
104
- return varName;
105
- }
1
+ import { types as t } from "@babel/core";
2
+
3
+ const GLOBAL_TARGETS: Record<string, t.Expression> = {
4
+ Window: t.identifier("window"),
5
+ Document: t.identifier("document"),
6
+ Body: t.memberExpression(t.identifier("document"), t.identifier("body")),
7
+ };
8
+
9
+ export function isGlobalComponent(tag: string): boolean {
10
+ return tag in GLOBAL_TARGETS;
11
+ }
12
+
13
+ export function handleGlobalComponent(
14
+ tag: string,
15
+ node: t.JSXElement,
16
+ statements: t.Statement[],
17
+ genId: () => string,
18
+ ): string {
19
+ const varName = genId();
20
+ const targetExpr = GLOBAL_TARGETS[tag];
21
+
22
+ for (const attr of node.openingElement.attributes) {
23
+ if (!t.isJSXAttribute(attr)) continue;
24
+
25
+ let attrName: string;
26
+ if (t.isJSXNamespacedName(attr.name)) {
27
+ attrName = attr.name.name.name;
28
+ } else {
29
+ attrName = (attr.name as t.JSXIdentifier).name;
30
+ }
31
+
32
+ if (/^on[A-Z]/.test(attrName)) {
33
+ const event = attrName.slice(2).toLowerCase();
34
+ const handler = (attr.value as t.JSXExpressionContainer)
35
+ .expression as t.Expression;
36
+
37
+ // createEffect(() => {
38
+ // if (typeof window === "undefined") return;
39
+ // target.addEventListener(ev, handler);
40
+ // return () => target.removeEventListener(ev, handler);
41
+ // })
42
+ statements.push(
43
+ t.expressionStatement(
44
+ t.callExpression(t.identifier("createEffect"), [
45
+ t.arrowFunctionExpression(
46
+ [],
47
+ t.blockStatement([
48
+ t.ifStatement(
49
+ t.binaryExpression(
50
+ "===",
51
+ //@ts-expect-error i dont think it can be undefined here
52
+ t.unaryExpression("typeof", targetExpr),
53
+ t.stringLiteral("undefined"),
54
+ ),
55
+ t.returnStatement(),
56
+ ),
57
+ t.expressionStatement(
58
+ t.callExpression(
59
+ t.memberExpression(
60
+ //@ts-expect-error i dont think it can be undefined here
61
+ targetExpr,
62
+ t.identifier("addEventListener"),
63
+ ),
64
+ [t.stringLiteral(event), handler],
65
+ ),
66
+ ),
67
+ t.returnStatement(
68
+ t.arrowFunctionExpression(
69
+ [],
70
+ t.callExpression(
71
+ t.memberExpression(
72
+ //@ts-expect-error i dont think it can be undefined here
73
+ targetExpr,
74
+ t.identifier("removeEventListener"),
75
+ ),
76
+ [t.stringLiteral(event), handler],
77
+ ),
78
+ ),
79
+ ),
80
+ ]),
81
+ ),
82
+ ]),
83
+ ),
84
+ );
85
+ }
86
+ }
87
+
88
+ // Placeholder comment node for the component's return value
89
+ statements.push(
90
+ t.variableDeclaration("const", [
91
+ t.variableDeclarator(
92
+ t.identifier(varName),
93
+ t.callExpression(
94
+ t.memberExpression(
95
+ t.identifier("document"),
96
+ t.identifier("createComment"),
97
+ ),
98
+ [t.stringLiteral(tag.toLowerCase())],
99
+ ),
100
+ ),
101
+ ]),
102
+ );
103
+
104
+ return varName;
105
+ }
@@ -1,107 +1,107 @@
1
- import type { NodePath } from "@babel/core";
2
- import { types as t } from "@babel/core";
3
- import { MAGIC } from "../../util/magic.ts";
4
-
5
- export function handleState(
6
- declaration: NodePath<t.VariableDeclaration>,
7
- declarator: NodePath<t.VariableDeclarator>,
8
- init: NodePath<t.CallExpression>,
9
- signals: Set<string>,
10
- dev?: boolean,
11
- fileKey?: string, // e.g. "src/routes/counter.tsx"
12
- ): void {
13
- const { id } = declarator.node;
14
- if (!t.isIdentifier(id)) throw new Error("Destructured $state not supported");
15
- const name = id.name;
16
-
17
- // check for // @sigil:reset escape comment
18
- const hasReset = declaration.node.leadingComments?.some(
19
- (c) => c.value.trim() === "@sigil:reset",
20
- );
21
-
22
- const setCall = (value: t.Expression) =>
23
- t.callExpression(
24
- t.memberExpression(t.identifier(name), t.identifier("set")),
25
- [value],
26
- );
27
- const binding = declaration.scope.getBinding(name);
28
- if (!binding) return;
29
- const refs = [...binding.referencePaths];
30
- const violations = [...binding.constantViolations];
31
-
32
- const originalArgs = init.node.arguments;
33
-
34
- if (dev && fileKey && !hasReset) {
35
- const key = `${fileKey}#${name}`;
36
- // createSignal(__hmrAdopt__("file#count", original))
37
- init.replaceWith(
38
- t.callExpression(t.identifier(MAGIC.createSignal), [
39
- t.callExpression(t.identifier("__hmrAdopt__"), [
40
- t.stringLiteral(key),
41
- ...(originalArgs as t.Expression[]),
42
- ]),
43
- ]),
44
- );
45
- // __hmrRegister__("file#count", count) — inserted after declaration
46
- declaration.insertAfter(
47
- t.expressionStatement(
48
- t.callExpression(t.identifier("__hmrRegister__"), [
49
- t.stringLiteral(key),
50
- t.identifier(name),
51
- ]),
52
- ),
53
- );
54
- } else {
55
- init.replaceWith(
56
- t.callExpression(
57
- t.identifier(MAGIC.createSignal),
58
- originalArgs as t.Expression[],
59
- ),
60
- );
61
- }
62
-
63
- declaration.node.kind = "const";
64
- // ... rest of rewrites unchanged
65
- // rewrite writes
66
- // violations go before refs becase UpdateExpression
67
- // is both a read and a write and this prevents count()++
68
- for (const violation of violations) {
69
- if (violation.isUpdateExpression()) {
70
- const op = violation.node.operator === "++" ? "+" : "-";
71
- violation.replaceWith(
72
- setCall(
73
- t.binaryExpression(
74
- op,
75
- t.callExpression(t.identifier(name), []),
76
- t.numericLiteral(1),
77
- ),
78
- ),
79
- );
80
- } else if (violation.isAssignmentExpression()) {
81
- const { operator, right } = violation.node;
82
- if (operator === "=") {
83
- // count = 5 -> count.set(5)
84
- violation.replaceWith(setCall(right));
85
- } else {
86
- // count += 2 -> count.set(count() + 2)
87
- const baseOp = operator.slice(0, -1);
88
- violation.replaceWith(
89
- setCall(
90
- t.binaryExpression(
91
- baseOp as t.BinaryExpression["operator"],
92
- t.callExpression(t.identifier(name), []),
93
- right,
94
- ),
95
- ),
96
- );
97
- }
98
- }
99
- }
100
-
101
- for (const ref of refs) {
102
- if (!ref.isIdentifier()) continue;
103
- // weird syntax, but just swaps the read with a call.
104
- ref.replaceWith(t.callExpression(ref.node, []));
105
- }
106
- signals.add(name);
107
- }
1
+ import type { NodePath } from "@babel/core";
2
+ import { types as t } from "@babel/core";
3
+ import { MAGIC } from "../../util/magic.ts";
4
+
5
+ export function handleState(
6
+ declaration: NodePath<t.VariableDeclaration>,
7
+ declarator: NodePath<t.VariableDeclarator>,
8
+ init: NodePath<t.CallExpression>,
9
+ signals: Set<string>,
10
+ dev?: boolean,
11
+ fileKey?: string, // e.g. "src/routes/counter.tsx"
12
+ ): void {
13
+ const { id } = declarator.node;
14
+ if (!t.isIdentifier(id)) throw new Error("Destructured $state not supported");
15
+ const name = id.name;
16
+
17
+ // check for // @sigil:reset escape comment
18
+ const hasReset = declaration.node.leadingComments?.some(
19
+ (c) => c.value.trim() === "@sigil:reset",
20
+ );
21
+
22
+ const setCall = (value: t.Expression) =>
23
+ t.callExpression(
24
+ t.memberExpression(t.identifier(name), t.identifier("set")),
25
+ [value],
26
+ );
27
+ const binding = declaration.scope.getBinding(name);
28
+ if (!binding) return;
29
+ const refs = [...binding.referencePaths];
30
+ const violations = [...binding.constantViolations];
31
+
32
+ const originalArgs = init.node.arguments;
33
+
34
+ if (dev && fileKey && !hasReset) {
35
+ const key = `${fileKey}#${name}`;
36
+ // createSignal(__hmrAdopt__("file#count", original))
37
+ init.replaceWith(
38
+ t.callExpression(t.identifier(MAGIC.createSignal), [
39
+ t.callExpression(t.identifier("__hmrAdopt__"), [
40
+ t.stringLiteral(key),
41
+ ...(originalArgs as t.Expression[]),
42
+ ]),
43
+ ]),
44
+ );
45
+ // __hmrRegister__("file#count", count) — inserted after declaration
46
+ declaration.insertAfter(
47
+ t.expressionStatement(
48
+ t.callExpression(t.identifier("__hmrRegister__"), [
49
+ t.stringLiteral(key),
50
+ t.identifier(name),
51
+ ]),
52
+ ),
53
+ );
54
+ } else {
55
+ init.replaceWith(
56
+ t.callExpression(
57
+ t.identifier(MAGIC.createSignal),
58
+ originalArgs as t.Expression[],
59
+ ),
60
+ );
61
+ }
62
+
63
+ declaration.node.kind = "const";
64
+ // ... rest of rewrites unchanged
65
+ // rewrite writes
66
+ // violations go before refs becase UpdateExpression
67
+ // is both a read and a write and this prevents count()++
68
+ for (const violation of violations) {
69
+ if (violation.isUpdateExpression()) {
70
+ const op = violation.node.operator === "++" ? "+" : "-";
71
+ violation.replaceWith(
72
+ setCall(
73
+ t.binaryExpression(
74
+ op,
75
+ t.callExpression(t.identifier(name), []),
76
+ t.numericLiteral(1),
77
+ ),
78
+ ),
79
+ );
80
+ } else if (violation.isAssignmentExpression()) {
81
+ const { operator, right } = violation.node;
82
+ if (operator === "=") {
83
+ // count = 5 -> count.set(5)
84
+ violation.replaceWith(setCall(right));
85
+ } else {
86
+ // count += 2 -> count.set(count() + 2)
87
+ const baseOp = operator.slice(0, -1);
88
+ violation.replaceWith(
89
+ setCall(
90
+ t.binaryExpression(
91
+ baseOp as t.BinaryExpression["operator"],
92
+ t.callExpression(t.identifier(name), []),
93
+ right,
94
+ ),
95
+ ),
96
+ );
97
+ }
98
+ }
99
+ }
100
+
101
+ for (const ref of refs) {
102
+ if (!ref.isIdentifier()) continue;
103
+ // weird syntax, but just swaps the read with a call.
104
+ ref.replaceWith(t.callExpression(ref.node, []));
105
+ }
106
+ signals.add(name);
107
+ }