@sigil-dev/compiler 0.7.4 → 0.7.5

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/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.7.4",
5
+ "version": "0.7.5",
6
6
  "private": false,
7
7
  "description": "Compiler for the Sigil framework",
8
8
  "peerDependencies": {
@@ -27,7 +27,6 @@ const SSR_HELPERS = template.statements.ast(`
27
27
  return String(v).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
28
28
  };
29
29
  `);
30
-
31
30
  /**
32
31
  * Recursively convert key={expr} to data-key={expr} in JSX tree.
33
32
  * Needed for SSR output so hydration can map items by key.
@@ -64,11 +63,12 @@ interface SigilOptions {
64
63
  }
65
64
 
66
65
  export default function sigilPlugin(): PluginObject {
67
- const signals = new Set<string>();
68
- const storeSignals = new Set<string>();
69
- let scopedHash: string | undefined;
70
- let isSSR = false;
71
- let isHydrate = false;
66
+ const signals = new Set<string>();
67
+ const storeSignals = new Set<string>();
68
+ let scopedHash: string | undefined;
69
+ let isSSR = false;
70
+ let isHydrate = false;
71
+ let didTransform = false;
72
72
 
73
73
  return {
74
74
  name: "sigil",
@@ -79,88 +79,71 @@ export default function sigilPlugin(): PluginObject {
79
79
  storeSignals.clear();
80
80
  const opts = (state.opts || {}) as SigilOptions;
81
81
  scopedHash = opts.hash;
82
+ didTransform = false;
82
83
  isSSR = opts.mode === "ssr";
83
84
  isHydrate = opts.mode === "hydrate";
84
85
 
85
- // SSR doesn't need any runtime imports except the `escape` utility.
86
86
  if (isSSR) {
87
87
  path.unshiftContainer("body", SSR_HELPERS);
88
88
  return;
89
89
  }
90
90
 
91
- path.unshiftContainer(
92
- "body",
93
- t.importDeclaration(
94
- [
95
- t.importSpecifier(
96
- t.identifier("createSignal"),
97
- t.identifier("createSignal"),
98
- ),
99
- t.importSpecifier(
100
- t.identifier("createEffect"),
101
- t.identifier("createEffect"),
102
- ),
103
- t.importSpecifier(
104
- t.identifier("createMemo"),
105
- t.identifier("createMemo"),
106
- ),
107
- t.importSpecifier(
108
- t.identifier("reconcile"),
109
- t.identifier("reconcile"),
110
- ),
111
- t.importSpecifier(t.identifier("claim"), t.identifier("claim")),
112
- t.importSpecifier(
113
- t.identifier("claimText"),
114
- t.identifier("claimText"),
115
- ),
116
- t.importSpecifier(
117
- t.identifier("claimComment"),
118
- t.identifier("claimComment"),
119
- ),
120
- t.importSpecifier(
121
- t.identifier("hydrateKeyedList"),
122
- t.identifier("hydrateKeyedList"),
123
- ),
124
- t.importSpecifier(
125
- t.identifier("insert"),
126
- t.identifier("insert"),
127
- ),
128
91
 
129
- t.importSpecifier(
130
- t.identifier("getHydrationNodes"),
131
- t.identifier("getHydrationNodes"),
132
- ),
133
- ],
134
- t.stringLiteral("@sigil-dev/runtime"),
135
- ),
136
- );
92
+
137
93
  },
138
94
  exit(path, state) {
139
- warnDeadReactivity(path, signals, storeSignals, state.filename)
95
+ warnDeadReactivity(path, signals, storeSignals, state.filename);
96
+
97
+ // Only inject runtime import if this file actually used sigil transforms
98
+ if (!didTransform || isSSR) return;
99
+
100
+ const hasRuntime = path.node.body.some(
101
+ n => t.isImportDeclaration(n) && n.source.value === "@sigil-dev/runtime"
102
+ );
103
+ if (hasRuntime) return;
104
+
105
+ path.unshiftContainer("body", t.importDeclaration(
106
+ [
107
+ t.importSpecifier(t.identifier("createSignal"), t.identifier("createSignal")),
108
+ t.importSpecifier(t.identifier("createEffect"), t.identifier("createEffect")),
109
+ t.importSpecifier(t.identifier("createMemo"), t.identifier("createMemo")),
110
+ t.importSpecifier(t.identifier("reconcile"), t.identifier("reconcile")),
111
+ t.importSpecifier(t.identifier("claim"), t.identifier("claim")),
112
+ t.importSpecifier(t.identifier("claimText"), t.identifier("claimText")),
113
+ t.importSpecifier(t.identifier("claimComment"), t.identifier("claimComment")),
114
+ t.importSpecifier(t.identifier("hydrateKeyedList"), t.identifier("hydrateKeyedList")),
115
+ t.importSpecifier(t.identifier("insert"), t.identifier("insert")),
116
+ t.importSpecifier(t.identifier("getHydrationNodes"), t.identifier("getHydrationNodes")),
117
+ t.importSpecifier(t.identifier("pushHydrationNodes"), t.identifier("pushHydrationNodes")),
118
+ t.importSpecifier(t.identifier("popHydrationNodes"), t.identifier("popHydrationNodes")),
119
+ ],
120
+ t.stringLiteral("@sigil-dev/runtime"),
121
+ ));
140
122
  }
141
123
  },
142
124
  VariableDeclaration(path) {
143
- for (const declarator of path.get("declarations")) {
144
- const macro = getMacro(declarator);
145
- if (!macro) continue;
146
- const { name, init } = macro;
147
- const varName = t.isIdentifier(declarator.node.id)
148
- ? declarator.node.id.name
149
- : null;
125
+ for (const declarator of path.get("declarations")) {
126
+ const macro = getMacro(declarator);
127
+ if (!macro) continue;
128
+ didTransform = true;
129
+ const { name, init } = macro;
130
+ const varName = t.isIdentifier(declarator.node.id)
131
+ ? declarator.node.id.name
132
+ : null;
150
133
 
151
- if (name === "$state" || name === "$store") {
152
- isSSR
153
- ? handleStateSSR(path, declarator, init)
154
- : handleState(path, declarator, init, signals);
155
- if (name === "$store" && varName) {
156
- storeSignals.add(varName);
157
- }
158
- } else if (name === "$derived") {
159
- isSSR
160
- ? handleDerivedSSR(path, declarator, init)
161
- : handleDerived(path, declarator, init, signals);
134
+ if (name === "$state" || name === "$store") {
135
+ isSSR
136
+ ? handleStateSSR(path, declarator, init)
137
+ : handleState(path, declarator, init, signals);
138
+ if (name === "$store" && varName) {
139
+ storeSignals.add(varName);
140
+ }
141
+ } else if (name === "$derived") {
142
+ isSSR
143
+ ? handleDerivedSSR(path, declarator, init)
144
+ : handleDerived(path, declarator, init, signals);
145
+ }
162
146
  }
163
- }
164
147
  },
165
148
  ExpressionStatement(path) {
166
149
  if (isSSR) {
@@ -169,6 +152,7 @@ export default function sigilPlugin(): PluginObject {
169
152
  if (expr.isCallExpression()) {
170
153
  const callee = expr.get("callee");
171
154
  if (callee.isIdentifier() && callee.node.name === "$effect") {
155
+ didTransform = true;
172
156
  path.remove();
173
157
  }
174
158
  }
@@ -177,9 +161,29 @@ export default function sigilPlugin(): PluginObject {
177
161
  handleEffect(path);
178
162
  },
179
163
  JSXElement(path) {
164
+ didTransform = true;
165
+ // console.log(
166
+ // "[sigil] JSXElement visitor fired, parent:",
167
+ // path.parentPath?.type,
168
+ // "isSSR:", isSSR
169
+ // );
180
170
  if (path.parentPath?.isJSXElement() || path.parentPath?.isJSXFragment())
181
171
  return;
182
- if (isSSR) { /* ... existing ... */ return; }
172
+ if (isSSR) {
173
+ try {
174
+ convertKeyToDataKey(path.node);
175
+ path.replaceWith(
176
+ t.callExpression(t.identifier("__h"), [
177
+ processElementSSR(path.node, signals),
178
+ ]),
179
+ );
180
+ } catch (e) {
181
+ console.error("[sigil] processElementSSR threw:", path.toString());
182
+ console.error(e);
183
+ throw e;
184
+ }
185
+ return;
186
+ }
183
187
 
184
188
  const statements: t.Statement[] = [];
185
189
  const genId = (() => { let i = 0; return () => `_el${i++}`; })();
@@ -215,9 +219,17 @@ export default function sigilPlugin(): PluginObject {
215
219
  );
216
220
  },
217
221
  JSXFragment(path) {
222
+ didTransform = true;
218
223
  if (path.parentPath?.isJSXElement() || path.parentPath?.isJSXFragment())
219
224
  return;
220
- if (isSSR) { /* ... existing ... */ return; }
225
+ if (isSSR) {
226
+ path.replaceWith(
227
+ t.callExpression(t.identifier("__h"), [
228
+ processFragmentSSR(path.node, signals),
229
+ ]),
230
+ );
231
+ return;
232
+ }
221
233
 
222
234
  const statements: t.Statement[] = [];
223
235
  const genId = (() => { let i = 0; return () => `_el${i++}`; })();
package/src/bun-plugin.ts CHANGED
@@ -4,6 +4,30 @@ import sigilPlugin from "./babel/index.ts";
4
4
  import { computeHash, scopeCSS } from "./babel/util/css.ts";
5
5
 
6
6
  const STYLE_RE = /<style[^>]*>([\s\S]*?)<\/style>/gi;
7
+ const STYLE_TEST_RE = /<style[^>]*>[\s\S]*?<\/style>/i;
8
+ const SOURCE_RE = /\.[jt]sx?$/;
9
+ const JSX_RE = /\.[jt]sx$/;
10
+ const SIGIL_MACRO_RE = /\$(?:state|store|derived|effect)\s*\(/;
11
+
12
+ function normalizePath(path: string): string {
13
+ return path.replace(/\\/g, "/");
14
+ }
15
+
16
+ function shouldTransform(path: string): boolean {
17
+ const normalized = normalizePath(path);
18
+ if (!SOURCE_RE.test(normalized)) return false;
19
+ if (normalized.includes("/.grimoire/")) return false;
20
+ if (normalized.includes("/node_modules/")) return false;
21
+ if (normalized.includes("/packages/runtime/")) return false;
22
+ if (normalized.includes("/packages/grimoire/")) return false;
23
+ if (normalized.includes("/packages/compiler/")) return false;
24
+ return true;
25
+ }
26
+
27
+ function shouldTransformSource(path: string, code: string): boolean {
28
+ const normalized = normalizePath(path);
29
+ return JSX_RE.test(normalized) || SIGIL_MACRO_RE.test(code) || STYLE_TEST_RE.test(code);
30
+ }
7
31
 
8
32
  export function sigil(options?: {
9
33
  mode?: "dom" | "ssr" | "hydrate";
@@ -12,8 +36,11 @@ export function sigil(options?: {
12
36
  name: "sigil",
13
37
  setup(build) {
14
38
  const transpiler = new Bun.Transpiler({ loader: "tsx" });
15
- build.onLoad({ filter: /\.[jt]sx$/ }, async ({ path }) => {
39
+ build.onLoad({ filter: SOURCE_RE }, async ({ path }) => {
40
+ if (!shouldTransform(path)) return;
16
41
  let code = await Bun.file(path).text();
42
+ if (!shouldTransformSource(path, code)) return;
43
+ // console.log("[bun-plugin] intercepting:", path);
17
44
  const hash = computeHash(path);
18
45
  let scopedCSS = "";
19
46
  code = code.replace(STYLE_RE, (_, css) => {