@sigil-dev/compiler 0.9.10 → 0.9.12

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.9.10",
5
+ "version": "0.9.12",
6
6
  "private": false,
7
7
  "description": "Compiler for the Sigil framework",
8
8
  "peerDependencies": {
@@ -23,8 +23,10 @@
23
23
  "@babel/types": "8.0.0-rc.6"
24
24
  },
25
25
  "dependencies": {
26
+ "@babel/core": "8.0.0-rc.6",
26
27
  "@babel/generator": "^7.29.7",
27
28
  "@babel/parser": "^7.29.7",
29
+ "@babel/plugin-syntax-jsx": "8.0.0-rc.6",
28
30
  "@babel/plugin-transform-typescript": "8.0.0-rc.6",
29
31
  "@babel/preset-typescript": "8.0.0-rc.6"
30
32
  }
@@ -9,7 +9,9 @@ export function handleState(
9
9
  signals: Set<string>,
10
10
  dev?: boolean,
11
11
  fileKey?: string, // e.g. "src/routes/counter.tsx"
12
+ raw = false, // $state.raw: same rewrites, no deep proxy
12
13
  ): void {
14
+ const factory = raw ? MAGIC.createRawSignal : MAGIC.createSignal;
13
15
  const { id } = declarator.node;
14
16
  if (!t.isIdentifier(id)) throw new Error("Destructured $state not supported");
15
17
  const name = id.name;
@@ -35,7 +37,7 @@ export function handleState(
35
37
  const key = `${fileKey}#${name}`;
36
38
  // createSignal(__hmrAdopt__("file#count", original))
37
39
  init.replaceWith(
38
- t.callExpression(t.identifier(MAGIC.createSignal), [
40
+ t.callExpression(t.identifier(factory), [
39
41
  t.callExpression(t.identifier("__hmrAdopt__"), [
40
42
  t.stringLiteral(key),
41
43
  ...(originalArgs as t.Expression[]),
@@ -53,10 +55,7 @@ export function handleState(
53
55
  );
54
56
  } else {
55
57
  init.replaceWith(
56
- t.callExpression(
57
- t.identifier(MAGIC.createSignal),
58
- originalArgs as t.Expression[],
59
- ),
58
+ t.callExpression(t.identifier(factory), originalArgs as t.Expression[]),
60
59
  );
61
60
  }
62
61
 
@@ -9,6 +9,13 @@ import { processElementSSR, processFragmentSSR } from "./jsx/ssr.ts";
9
9
  import { warnDeadReactivity } from "./util/dead-code.ts";
10
10
  import { getMacro } from "./util/helpers.ts";
11
11
  import { MAGIC } from "./util/magic.ts";
12
+ import {
13
+ collectImportRoots,
14
+ importRoots,
15
+ liftDestructuredProps,
16
+ propObjects,
17
+ rewriteImportReads,
18
+ } from "./util/props.ts";
12
19
 
13
20
  const SSR_HELPERS = template.statements.ast(`
14
21
  const __SAFE = Symbol.for('sigil.safe');
@@ -25,25 +32,53 @@ const SSR_HELPERS = template.statements.ast(`
25
32
  if (v === false || v === null || v === undefined) return '';
26
33
  return String(v).replace(new RegExp('</(' + tag + ')', 'gi'), '<\\\\/$1');
27
34
  };
28
- const __b = (n, v) => {
29
- if (v === true) return ' ' + n;
30
- if (v === false || v === null || v === undefined) return '';
31
- return ' ' + n + '="' + __e(v) + '"';
32
- };
33
35
  const __e = (v) => {
34
36
  if (v?.[__SAFE]) return v.v;
35
37
  if (Array.isArray(v)) return v.map(x => x?.[__SAFE] ? x.v : __e(x)).join('');
36
38
  if (v === false || v === null || v === undefined) return '';
37
- if (typeof v === 'object' && v.constructor === Object) {
38
- 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']);
39
- return Object.entries(v).map(([k, val]) => {
40
- const prop = k.replace(/([A-Z])/g, '-$1').toLowerCase();
41
- if (typeof val === 'number' && !UNITLESS.has(k)) return prop + ':' + val + 'px';
42
- return prop + ':' + val;
43
- }).join(';');
44
- }
45
39
  return String(v).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
46
40
  };
41
+ // Keep in sync with cssName/cssValue/UNITLESS in @sigil-dev/runtime.
42
+ const __UNITLESS = new Set(['animation-iteration-count','aspect-ratio','border-image-outset','border-image-slice','border-image-width','column-count','columns','fill-opacity','flex','flex-grow','flex-shrink','flood-opacity','font-weight','grid-area','grid-column','grid-column-end','grid-column-start','grid-row','grid-row-end','grid-row-start','line-clamp','line-height','opacity','order','orphans','scale','stop-opacity','stroke-dasharray','stroke-dashoffset','stroke-miterlimit','stroke-opacity','stroke-width','tab-size','widows','z-index','zoom']);
43
+ const __css = (v) => {
44
+ if (v === null || v === undefined || v === false) return '';
45
+ if (typeof v !== 'object') return String(v);
46
+ return Object.entries(v).map(([k, val]) => {
47
+ let name = k.startsWith('--') ? k : k.replace(/[A-Z]/g, c => '-' + c.toLowerCase());
48
+ if (name.startsWith('ms-')) name = '-' + name;
49
+ if (val === null || val === undefined || val === false || val === '') return '';
50
+ if (typeof val === 'number' && val !== 0 && !name.startsWith('--') && !__UNITLESS.has(name)) val = val + 'px';
51
+ return name + ':' + val;
52
+ }).filter(Boolean).join(';');
53
+ };
54
+ // One attribute. null/undefined drop it; booleans are presence for plain
55
+ // names (checked, hidden) and spelled out for dashed ones (aria-hidden).
56
+ const __a = (n, v) => {
57
+ if (v === null || v === undefined) return '';
58
+ if (n === 'style') { const c = __css(v); return c ? ' style="' + __e(c) + '"' : ''; }
59
+ if (typeof v === 'boolean') return n.includes('-') ? ' ' + n + '="' + v + '"' : v ? ' ' + n : '';
60
+ return ' ' + n + '="' + __e(v) + '"';
61
+ };
62
+ const __SPREAD_ATTR = { className: 'class', htmlFor: 'for' };
63
+ // <div {...props}>: what the client's spread() would set, as HTML.
64
+ const __s = (o) => {
65
+ if (!o) return '';
66
+ let out = '';
67
+ for (const [k, v] of Object.entries(o)) {
68
+ if (typeof v === 'function' || k === 'children' || k === 'key' || k === 'innerHTML') continue;
69
+ out += __a(__SPREAD_ATTR[k] ?? k, v);
70
+ }
71
+ return out;
72
+ };
73
+ // <select value={v}>: mark the matching <option>s selected.
74
+ const __sel = (html, v) => {
75
+ if (v === null || v === undefined) return html;
76
+ const want = new Set((Array.isArray(v) ? v : [v]).map(__e));
77
+ return html.replace(/<option\\b[^>]*>/g, (tag) => {
78
+ const m = / value="([^"]*)"/.exec(tag);
79
+ return m && want.has(m[1]) && !/ selected[ >=]/.test(tag) ? tag.slice(0, -1) + ' selected>' : tag;
80
+ });
81
+ };
47
82
  `);
48
83
  /**
49
84
  * Recursively convert key={expr} to data-key={expr} in JSX tree.
@@ -106,6 +141,8 @@ export default function sigilPlugin(): PluginObject {
106
141
  enter(path, state) {
107
142
  signals.clear();
108
143
  storeSignals.clear();
144
+ propObjects.clear();
145
+ importRoots.clear();
109
146
  const opts = (state.opts || {}) as SigilOptions;
110
147
  scopedHash = opts.hash;
111
148
  didTransform = false;
@@ -123,9 +160,17 @@ export default function sigilPlugin(): PluginObject {
123
160
  path.unshiftContainer("body", SSR_HELPERS);
124
161
  return;
125
162
  }
163
+ liftDestructuredProps(path, signals);
164
+ collectImportRoots(path);
165
+ if (rewriteImportReads(path)) didTransform = true;
126
166
  },
127
167
  exit(path, state) {
128
- warnDeadReactivity(path, signals, storeSignals, state.filename);
168
+ warnDeadReactivity(
169
+ path,
170
+ signals,
171
+ new Set([...storeSignals, ...propObjects]),
172
+ state.filename,
173
+ );
129
174
 
130
175
  if (asyncDefaultPath && !isSSR) {
131
176
  let fnNode: t.FunctionDeclaration;
@@ -207,6 +252,11 @@ export default function sigilPlugin(): PluginObject {
207
252
  "mountTransition",
208
253
  "removeAnimated",
209
254
  "setProp",
255
+ "setStyleProp",
256
+ "spread",
257
+ "isClaimed",
258
+ "liveSlot",
259
+ "readStore",
210
260
  ...(asyncDefaultPath ? ["__asyncBoundary"] : []),
211
261
  ];
212
262
  const runtimeImport = path.node.body.find(
@@ -260,18 +310,17 @@ export default function sigilPlugin(): PluginObject {
260
310
  : null;
261
311
 
262
312
  if (name === "$state" || name === "$store" || name === "$state.raw") {
263
- if (name === "$state.raw") {
264
- if (!isSSR) {
265
- const args = init.node.arguments;
266
- init.replaceWith(
267
- t.callExpression(t.identifier(MAGIC.createRawSignal), args),
268
- );
269
- }
270
- continue;
271
- }
272
313
  isSSR
273
314
  ? handleStateSSR(path, declarator, init, signals)
274
- : handleState(path, declarator, init, signals, isDev, filename); // ← add
315
+ : handleState(
316
+ path,
317
+ declarator,
318
+ init,
319
+ signals,
320
+ isDev,
321
+ filename,
322
+ name === "$state.raw",
323
+ );
275
324
  if (name === "$store" && varName) {
276
325
  storeSignals.add(varName);
277
326
  }
@@ -462,7 +511,7 @@ export default function sigilPlugin(): PluginObject {
462
511
  convertKeyToDataKey(path.node);
463
512
  path.replaceWith(
464
513
  t.callExpression(t.identifier("__h"), [
465
- processElementSSR(path.node, signals),
514
+ processElementSSR(path.node, signals, scopedHash),
466
515
  ]),
467
516
  );
468
517
  } catch (e) {
@@ -520,7 +569,7 @@ export default function sigilPlugin(): PluginObject {
520
569
  if (isSSR) {
521
570
  path.replaceWith(
522
571
  t.callExpression(t.identifier("__h"), [
523
- processFragmentSSR(path.node, signals),
572
+ processFragmentSSR(path.node, signals, scopedHash),
524
573
  ]),
525
574
  );
526
575
  return;
@@ -1,6 +1,7 @@
1
1
  import { types as t } from "@babel/core";
2
2
  import { processElement } from "./element";
3
3
  import { processFragment } from "./fragment";
4
+ import { containsSignal, hasJSX, jsxText } from "./utils";
4
5
 
5
6
  /**
6
7
  * Collect children from a JSX element into a children expression.
@@ -29,8 +30,8 @@ export function collectChildren(
29
30
 
30
31
  for (const child of node.children) {
31
32
  if (t.isJSXText(child)) {
32
- const text = child.value.trim();
33
- if (!text) continue;
33
+ const text = jsxText(child);
34
+ if (text === null) continue;
34
35
  childrenElements.push(t.stringLiteral(text));
35
36
  continue;
36
37
  }
@@ -43,6 +44,13 @@ export function collectChildren(
43
44
  if (t.isJSXEmptyExpression(expr)) continue;
44
45
  if (t.isJSXElement(expr) || t.isJSXFragment(expr)) {
45
46
  jsx = expr;
47
+ } else if (containsSignal(expr, signals) || (hydrate && hasJSX(expr))) {
48
+ // <Box>{on ? <A /> : <B />}</Box>: compile it as <>{…}</> so it
49
+ // stays reactive (a plain value is read once, when the call is
50
+ // built) and, like JSX children, claims inside the component.
51
+ jsx = t.jsxFragment(t.jsxOpeningFragment(), t.jsxClosingFragment(), [
52
+ child,
53
+ ]);
46
54
  } else {
47
55
  // Plain expression child — pass the value straight through.
48
56
  hasOnlyText = false;