@sigil-dev/compiler 0.9.7 → 0.9.11

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.7",
5
+ "version": "0.9.11",
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');
@@ -21,25 +28,57 @@ const SSR_HELPERS = template.statements.ast(`
21
28
  if (Array.isArray(v)) return new __H(v.map(x => x?.[__SAFE] ? x.v : __e(x)).join(''));
22
29
  return new __H(String(v ?? ''));
23
30
  };
24
- const __b = (n, v) => {
25
- if (v === true) return ' ' + n;
31
+ const __r = (v, tag) => {
26
32
  if (v === false || v === null || v === undefined) return '';
27
- return ' ' + n + '="' + __e(v) + '"';
33
+ return String(v).replace(new RegExp('</(' + tag + ')', 'gi'), '<\\\\/$1');
28
34
  };
29
35
  const __e = (v) => {
30
36
  if (v?.[__SAFE]) return v.v;
31
37
  if (Array.isArray(v)) return v.map(x => x?.[__SAFE] ? x.v : __e(x)).join('');
32
38
  if (v === false || v === null || v === undefined) return '';
33
- if (typeof v === 'object' && v.constructor === Object) {
34
- 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']);
35
- return Object.entries(v).map(([k, val]) => {
36
- const prop = k.replace(/([A-Z])/g, '-$1').toLowerCase();
37
- if (typeof val === 'number' && !UNITLESS.has(k)) return prop + ':' + val + 'px';
38
- return prop + ':' + val;
39
- }).join(';');
40
- }
41
39
  return String(v).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
42
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
+ };
43
82
  `);
44
83
  /**
45
84
  * Recursively convert key={expr} to data-key={expr} in JSX tree.
@@ -102,6 +141,8 @@ export default function sigilPlugin(): PluginObject {
102
141
  enter(path, state) {
103
142
  signals.clear();
104
143
  storeSignals.clear();
144
+ propObjects.clear();
145
+ importRoots.clear();
105
146
  const opts = (state.opts || {}) as SigilOptions;
106
147
  scopedHash = opts.hash;
107
148
  didTransform = false;
@@ -119,9 +160,17 @@ export default function sigilPlugin(): PluginObject {
119
160
  path.unshiftContainer("body", SSR_HELPERS);
120
161
  return;
121
162
  }
163
+ liftDestructuredProps(path, signals);
164
+ collectImportRoots(path);
165
+ if (rewriteImportReads(path)) didTransform = true;
122
166
  },
123
167
  exit(path, state) {
124
- warnDeadReactivity(path, signals, storeSignals, state.filename);
168
+ warnDeadReactivity(
169
+ path,
170
+ signals,
171
+ new Set([...storeSignals, ...propObjects]),
172
+ state.filename,
173
+ );
125
174
 
126
175
  if (asyncDefaultPath && !isSSR) {
127
176
  let fnNode: t.FunctionDeclaration;
@@ -203,6 +252,11 @@ export default function sigilPlugin(): PluginObject {
203
252
  "mountTransition",
204
253
  "removeAnimated",
205
254
  "setProp",
255
+ "setStyleProp",
256
+ "spread",
257
+ "isClaimed",
258
+ "liveSlot",
259
+ "readStore",
206
260
  ...(asyncDefaultPath ? ["__asyncBoundary"] : []),
207
261
  ];
208
262
  const runtimeImport = path.node.body.find(
@@ -256,18 +310,17 @@ export default function sigilPlugin(): PluginObject {
256
310
  : null;
257
311
 
258
312
  if (name === "$state" || name === "$store" || name === "$state.raw") {
259
- if (name === "$state.raw") {
260
- if (!isSSR) {
261
- const args = init.node.arguments;
262
- init.replaceWith(
263
- t.callExpression(t.identifier(MAGIC.createRawSignal), args),
264
- );
265
- }
266
- continue;
267
- }
268
313
  isSSR
269
314
  ? handleStateSSR(path, declarator, init, signals)
270
- : 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
+ );
271
324
  if (name === "$store" && varName) {
272
325
  storeSignals.add(varName);
273
326
  }
@@ -458,7 +511,7 @@ export default function sigilPlugin(): PluginObject {
458
511
  convertKeyToDataKey(path.node);
459
512
  path.replaceWith(
460
513
  t.callExpression(t.identifier("__h"), [
461
- processElementSSR(path.node, signals),
514
+ processElementSSR(path.node, signals, scopedHash),
462
515
  ]),
463
516
  );
464
517
  } catch (e) {
@@ -516,7 +569,7 @@ export default function sigilPlugin(): PluginObject {
516
569
  if (isSSR) {
517
570
  path.replaceWith(
518
571
  t.callExpression(t.identifier("__h"), [
519
- processFragmentSSR(path.node, signals),
572
+ processFragmentSSR(path.node, signals, scopedHash),
520
573
  ]),
521
574
  );
522
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;