sigmx 0.1.1 → 0.2.0

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 (165) hide show
  1. package/README.md +10 -10
  2. package/dist/kernel/compile.d.ts +8 -9
  3. package/dist/kernel/compile.js +102 -67
  4. package/dist/kernel/compile.js.map +2 -2
  5. package/dist/kernel/contracts.d.ts +11 -5
  6. package/dist/kernel/index.d.ts +2 -2
  7. package/dist/kernel/index.js +5 -6
  8. package/dist/kernel/index.js.map +2 -2
  9. package/dist/kernel/precompile.d.ts +8 -10
  10. package/dist/kernel/precompile.js +25 -75
  11. package/dist/kernel/precompile.js.map +2 -2
  12. package/dist/kernel/precompiled.d.ts +7 -4
  13. package/dist/kernel/precompiled.js +5 -5
  14. package/dist/kernel/precompiled.js.map +2 -2
  15. package/dist/kernel/reactive.d.ts +8 -2
  16. package/dist/kernel/reactive.js +91 -62
  17. package/dist/kernel/reactive.js.map +3 -3
  18. package/dist/kernel/runtime.js +78 -68
  19. package/dist/kernel/runtime.js.map +3 -3
  20. package/dist/kernel/scan.d.ts +1 -1
  21. package/dist/kernel/scan.js +3 -3
  22. package/dist/kernel/scan.js.map +2 -2
  23. package/dist/kernel/state.d.ts +4 -3
  24. package/dist/kernel/state.js +91 -80
  25. package/dist/kernel/state.js.map +2 -2
  26. package/dist/kernel/strict-csp.d.ts +0 -5
  27. package/dist/kernel/strict-csp.js +2 -2
  28. package/dist/kernel/strict-csp.js.map +2 -2
  29. package/dist/lib/casing.d.ts +4 -1
  30. package/dist/lib/casing.js +8 -1
  31. package/dist/lib/casing.js.map +2 -2
  32. package/dist/lib/objects.d.ts +5 -3
  33. package/dist/lib/objects.js +10 -5
  34. package/dist/lib/objects.js.map +2 -2
  35. package/dist/lib/schedule.d.ts +3 -3
  36. package/dist/lib/schedule.js +12 -10
  37. package/dist/lib/schedule.js.map +2 -2
  38. package/dist/lib/sse.d.ts +10 -0
  39. package/dist/lib/sse.js +32 -29
  40. package/dist/lib/sse.js.map +2 -2
  41. package/dist/plugins/def.d.ts +5 -0
  42. package/dist/plugins/def.js +22 -0
  43. package/dist/plugins/def.js.map +7 -0
  44. package/dist/plugins/directives/attribute-sync.js +8 -12
  45. package/dist/plugins/directives/attribute-sync.js.map +2 -2
  46. package/dist/plugins/directives/boost.d.ts +1 -1
  47. package/dist/plugins/directives/boost.js +34 -31
  48. package/dist/plugins/directives/boost.js.map +2 -2
  49. package/dist/plugins/directives/busy-flag.js +13 -16
  50. package/dist/plugins/directives/busy-flag.js.map +2 -2
  51. package/dist/plugins/directives/class-toggle.js +17 -16
  52. package/dist/plugins/directives/class-toggle.js.map +2 -2
  53. package/dist/plugins/directives/cloak.js +2 -7
  54. package/dist/plugins/directives/cloak.js.map +2 -2
  55. package/dist/plugins/directives/collapse.js +36 -37
  56. package/dist/plugins/directives/collapse.js.map +2 -2
  57. package/dist/plugins/directives/declare.js +5 -8
  58. package/dist/plugins/directives/declare.js.map +2 -2
  59. package/dist/plugins/directives/derive.js +9 -18
  60. package/dist/plugins/directives/derive.js.map +2 -2
  61. package/dist/plugins/directives/element-ref.js +8 -8
  62. package/dist/plugins/directives/element-ref.js.map +2 -2
  63. package/dist/plugins/directives/event-listener.js +18 -26
  64. package/dist/plugins/directives/event-listener.js.map +2 -2
  65. package/dist/plugins/directives/frame-loop.js +9 -14
  66. package/dist/plugins/directives/frame-loop.js.map +2 -2
  67. package/dist/plugins/directives/html.js +6 -7
  68. package/dist/plugins/directives/html.js.map +2 -2
  69. package/dist/plugins/directives/inline-style.js +17 -20
  70. package/dist/plugins/directives/inline-style.js.map +2 -2
  71. package/dist/plugins/directives/mask.js +19 -23
  72. package/dist/plugins/directives/mask.js.map +2 -2
  73. package/dist/plugins/directives/media-query.d.ts +4 -1
  74. package/dist/plugins/directives/media-query.js +13 -13
  75. package/dist/plugins/directives/media-query.js.map +2 -2
  76. package/dist/plugins/directives/on-mount.js +4 -12
  77. package/dist/plugins/directives/on-mount.js.map +2 -2
  78. package/dist/plugins/directives/patch-listener.js +24 -19
  79. package/dist/plugins/directives/patch-listener.js.map +2 -2
  80. package/dist/plugins/directives/remove-me.js +7 -8
  81. package/dist/plugins/directives/remove-me.js.map +2 -2
  82. package/dist/plugins/directives/scroll-to.d.ts +4 -1
  83. package/dist/plugins/directives/scroll-to.js +8 -14
  84. package/dist/plugins/directives/scroll-to.js.map +2 -2
  85. package/dist/plugins/directives/size-watch.js +7 -12
  86. package/dist/plugins/directives/size-watch.js.map +2 -2
  87. package/dist/plugins/directives/state-dump.js +6 -10
  88. package/dist/plugins/directives/state-dump.js.map +2 -2
  89. package/dist/plugins/directives/storage-sync.js +13 -9
  90. package/dist/plugins/directives/storage-sync.js.map +2 -2
  91. package/dist/plugins/directives/teleport.js +8 -14
  92. package/dist/plugins/directives/teleport.js.map +2 -2
  93. package/dist/plugins/directives/text-content.js +6 -7
  94. package/dist/plugins/directives/text-content.js.map +2 -2
  95. package/dist/plugins/directives/timer.js +7 -13
  96. package/dist/plugins/directives/timer.js.map +2 -2
  97. package/dist/plugins/directives/transition.d.ts +1 -2
  98. package/dist/plugins/directives/transition.js +32 -34
  99. package/dist/plugins/directives/transition.js.map +2 -2
  100. package/dist/plugins/directives/tween.d.ts +2 -2
  101. package/dist/plugins/directives/tween.js +34 -50
  102. package/dist/plugins/directives/tween.js.map +2 -2
  103. package/dist/plugins/directives/two-way-bind.d.ts +1 -1
  104. package/dist/plugins/directives/two-way-bind.js +70 -97
  105. package/dist/plugins/directives/two-way-bind.js.map +2 -2
  106. package/dist/plugins/directives/url-params.js +35 -59
  107. package/dist/plugins/directives/url-params.js.map +3 -3
  108. package/dist/plugins/directives/url-replace.js +6 -7
  109. package/dist/plugins/directives/url-replace.js.map +2 -2
  110. package/dist/plugins/directives/validity.js +6 -9
  111. package/dist/plugins/directives/validity.js.map +2 -2
  112. package/dist/plugins/directives/viewport.js +18 -26
  113. package/dist/plugins/directives/viewport.js.map +2 -2
  114. package/dist/plugins/directives/visibility.js +9 -14
  115. package/dist/plugins/directives/visibility.js.map +2 -2
  116. package/dist/plugins/directives/watch.js +2 -8
  117. package/dist/plugins/directives/watch.js.map +2 -2
  118. package/dist/plugins/functions/bulk-set.js +7 -6
  119. package/dist/plugins/functions/bulk-set.js.map +2 -2
  120. package/dist/plugins/functions/bulk-toggle.js +7 -6
  121. package/dist/plugins/functions/bulk-toggle.js.map +2 -2
  122. package/dist/plugins/functions/confirm.js +2 -2
  123. package/dist/plugins/functions/confirm.js.map +2 -2
  124. package/dist/plugins/functions/copy-text.js +5 -5
  125. package/dist/plugins/functions/copy-text.js.map +2 -2
  126. package/dist/plugins/functions/dispatch.js +5 -5
  127. package/dist/plugins/functions/dispatch.js.map +2 -2
  128. package/dist/plugins/functions/locale-format.d.ts +4 -1
  129. package/dist/plugins/functions/locale-format.js +9 -16
  130. package/dist/plugins/functions/locale-format.js.map +2 -2
  131. package/dist/plugins/functions/range-map.js +6 -6
  132. package/dist/plugins/functions/range-map.js.map +2 -2
  133. package/dist/plugins/functions/request.d.ts +7 -3
  134. package/dist/plugins/functions/request.js +77 -86
  135. package/dist/plugins/functions/request.js.map +2 -2
  136. package/dist/plugins/functions/untracked-read.js +3 -2
  137. package/dist/plugins/functions/untracked-read.js.map +2 -2
  138. package/dist/plugins/functions/websocket.d.ts +3 -3
  139. package/dist/plugins/functions/websocket.js +13 -24
  140. package/dist/plugins/functions/websocket.js.map +2 -2
  141. package/dist/plugins/index.d.ts +0 -2
  142. package/dist/plugins/index.js +0 -4
  143. package/dist/plugins/index.js.map +2 -2
  144. package/dist/plugins/server-events/apply-elements.d.ts +1 -1
  145. package/dist/plugins/server-events/apply-elements.js +41 -43
  146. package/dist/plugins/server-events/apply-elements.js.map +2 -2
  147. package/dist/plugins/server-events/apply-state.js +9 -12
  148. package/dist/plugins/server-events/apply-state.js.map +2 -2
  149. package/dist/plugins/server-events/morph.js +46 -66
  150. package/dist/plugins/server-events/morph.js.map +3 -3
  151. package/dist/presets/all.js +0 -4
  152. package/dist/presets/all.js.map +2 -2
  153. package/dist/server.d.ts +10 -4
  154. package/dist/server.js +42 -14
  155. package/dist/server.js.map +2 -2
  156. package/dist/sigmx.standalone.js +1 -1
  157. package/dist/vite.js +17 -7
  158. package/dist/vite.js.map +2 -2
  159. package/package.json +4 -2
  160. package/dist/plugins/directives/transition-name.d.ts +0 -1
  161. package/dist/plugins/directives/transition-name.js +0 -11
  162. package/dist/plugins/directives/transition-name.js.map +0 -7
  163. package/dist/plugins/directives/trap.d.ts +0 -5
  164. package/dist/plugins/directives/trap.js +0 -54
  165. package/dist/plugins/directives/trap.js.map +0 -7
package/README.md CHANGED
@@ -8,11 +8,11 @@
8
8
 
9
9
  Your server renders HTML, as it always has. The browser asks for more of it with `@get` and `@post`, and sigmx merges the response into the page with a morph that keeps focus, typed input and scroll position. When you want live updates, the same request can return a stream of patches instead. Signals, two-way binding and reactive attributes cover the client-side state. You write `data-*` attributes and register only the plugins you use.
10
10
 
11
- - **Tiny.** 4.1 KB core, 8.8 KB with the essentials, 12.1 KB with all 52 plugins (brotli). Zero runtime dependencies.
11
+ - **Tiny.** 4.5 KB core, 9.0 KB with the essentials, 11.5 KB with all 50 plugins (brotli). Zero runtime dependencies.
12
12
  - **Server-driven.** Return plain HTML from any endpoint and it is morphed into the page by id, or targeted with a header. Return JSON to merge signals, or an event stream to push many patches over one request, with `Last-Event-ID` reconnects.
13
13
  - **Reactive where it matters.** Signals declared in markup; `bind`, `show`, `class`, computed values and effects keep the page in step. No component model, no virtual DOM.
14
14
  - **Pay for what you use.** Every directive and function is an opt-in plugin, and auto mode scans your source to bundle exactly the set it finds.
15
- - **Batteries from the ecosystem.** The Alpine plugins (`collapse`, `mask`, `trap`, `teleport`, …) and htmx extensions (`boost`, `ws`, `remove-me`, …) people bolt on are built in as plugins.
15
+ - **Batteries from the ecosystem.** The Alpine plugins (`collapse`, `mask`, `teleport`, …) and htmx extensions (`boost`, `ws`, `remove-me`, …) people bolt on are built in as plugins.
16
16
  - **Datastar-compatible markup.** Existing Datastar templates run unchanged.
17
17
 
18
18
  ```html
@@ -53,7 +53,7 @@ Attributes are `data-<plugin>[:key][__modifier.arg]="expression"`. Expressions a
53
53
  <button data-on:click__debounce.300ms="@get('/search')">Search</button>
54
54
  <li data-class:active="$page === 3"></li>
55
55
  <input data-bind:user.email />
56
- <p data-text="`${$items.length} items, ${@intl('number', $total, { style: 'currency', currency: 'EUR' })}`"></p>
56
+ <p data-text="`${$items.length} items, ${@intl('NumberFormat', $total, { style: 'currency', currency: 'EUR' })}`"></p>
57
57
  ```
58
58
 
59
59
  State is one JSON-like store addressed by dotted paths and patched with merge-patch semantics: objects merge, `null` removes, arrays notify on `push`. Requests send the store (minus `_`-prefixed paths) and apply whatever comes back, decided by the response's content type:
@@ -81,10 +81,10 @@ data: elements <li>Validating…</li>
81
81
  | kind | plugins |
82
82
  | --------- | -------------------------------------------------------------------------------------------------------------- |
83
83
  | state | `signals` `computed` `effect` `ref` `persist` `query-string` |
84
- | rendering | `text` `html` `show` `class` `style` `attr` `animate` `transition` `collapse` `view-transition` `cloak` |
84
+ | rendering | `text` `html` `show` `class` `style` `attr` `animate` `transition` `collapse` `cloak` |
85
85
  | forms | `bind` `custom-validity` `mask` |
86
86
  | events | `on` `init` `on-interval` `on-intersect` `on-resize` `on-raf` `on-signal-patch` `match-media` |
87
- | layout | `teleport` `trap` `scroll-into-view` `remove-me` |
87
+ | layout | `teleport` `scroll-into-view` `remove-me` |
88
88
  | server | `@get` `@post` `@put` `@patch` `@delete` `@ws` `boost` `indicator` `patch-elements` `patch-signals` |
89
89
  | utilities | `@peek` `@setAll` `@toggleAll` `@fit` `@clipboard` `@intl` `@dispatch` `@confirm` `json-signals` `replace-url` |
90
90
 
@@ -96,14 +96,14 @@ Brotli, measured from the real source by `npm run size` and `npm run compare` (n
96
96
 
97
97
  | | brotli |
98
98
  | ----------------------------------------------------------- | ------: |
99
- | sigmx core | 4.1 KB |
100
- | sigmx essentials (state, rendering, forms, requests, morph) | 8.8 KB |
101
- | sigmx everything, 52 plugins | 12.1 KB |
99
+ | sigmx core | 4.5 KB |
100
+ | sigmx essentials (state, rendering, forms, requests, morph) | 9.0 KB |
101
+ | sigmx everything, 50 plugins | 11.5 KB |
102
102
  | htmx 2.0.10 | 14.6 KB |
103
- | Datastar 1.0.3, free bundle (23 plugins) | 11.8 KB |
103
+ | Datastar 1.0.3 (23 plugins) | 11.8 KB |
104
104
  | Alpine.js 3.17.1 | 17.6 KB |
105
105
 
106
- Precompiling expressions at build time removes the runtime compiler and any use of `new Function`, taking the core to 3.7 KB and making strict CSP trivial.
106
+ Precompiling expressions at build time removes the runtime compiler and any use of `new Function`, taking the core to 3.8 KB and making strict CSP trivial.
107
107
 
108
108
  ## Auto mode
109
109
 
@@ -2,15 +2,14 @@ import type { ExpressionCompiler } from './contracts.js';
2
2
  export type Compiler = (params: string[], body: string) => (...args: any[]) => any;
3
3
  /** Default compiler: `new Function`. Not usable under a strict CSP; see `cspCompiler`. */
4
4
  export declare const functionCompiler: Compiler;
5
- /** Replace `@name(` with `__a.name(` outside strings and comments. */
6
- export declare const rewriteActions: (src: string) => string;
7
- /** Split on top-level semicolons. */
8
- export declare const splitStatements: (src: string) => string[];
5
+ /** `$count + @fit($x)` `$.count + __a.fit($.x)`; strings, template text, comments and regexes untouched. */
6
+ export declare const transform: (src: string) => string;
9
7
  /**
10
- * Compile an expression to `(store, actions, ...params) => any`: the body runs inside
11
- * `with (store.scope)`, so `$name` resolves through the store. With `returns`, the value of the
12
- * last statement is returned, so `$a = 1; $a * 2` works. Compiled functions are cached by source.
8
+ * Compile `src` with `compiler` as `($, __a, ...params) => any`, returning the body that compiled
9
+ * together with the function. Shared by the runtime and the build-time precompiler.
13
10
  */
14
- export declare const compile: (compiler: Compiler, src: string, params: string[], returns: boolean) => ((...args: any[]) => any);
15
- /** The default pipeline: compile at runtime inside `with (scope)` using `compiler` (`new Function` or CSP). */
11
+ export declare const compileBody: (compiler: Compiler, src: string, params: string[]) => [string, (...args: any[]) => any];
12
+ /** Compile with a cache keyed by parameter names and source. */
13
+ export declare const compile: (compiler: Compiler, src: string, params: string[]) => ((...args: any[]) => any);
14
+ /** The default pipeline: compile at runtime with `compiler` (`new Function` or the CSP compiler). */
16
15
  export declare const runtimeExpressions: (compiler: Compiler) => ExpressionCompiler;
@@ -1,96 +1,131 @@
1
1
  const functionCompiler = (params, body) => Function(...params, body);
2
- const cache = /* @__PURE__ */ new Map();
3
- const isIdent = (c) => /[A-Za-z0-9_$]/.test(c);
4
- const scan = (src, onCode) => {
5
- let i = 0;
2
+ const START = /[A-Za-z_$]/;
3
+ const IDENT = /[\w$]/;
4
+ const BEFORE_REGEX = /(^|[(,=:[!&|?{};+\-*%<>~^]|\b(?:return|typeof|case|in|of|do|else|void|delete|throw|new))$/;
5
+ const rewrite = (src, from, hole) => {
6
+ let out = "";
7
+ let i = from;
8
+ let depth = 0;
6
9
  const n = src.length;
10
+ const code = () => out.trimEnd();
7
11
  while (i < n) {
8
12
  const c = src[i];
9
- const next = src[i + 1];
10
- if (c === '"' || c === "'" || c === "`") {
13
+ const d = src[i + 1];
14
+ if (c === '"' || c === "'") {
11
15
  let j = i + 1;
12
16
  while (j < n && src[j] !== c) j += src[j] === "\\" ? 2 : 1;
13
- onCode(src.slice(i, j + 1), i);
17
+ out += src.slice(i, j + 1);
14
18
  i = j + 1;
15
- } else if (c === "/" && next === "/") {
19
+ } else if (c === "`") {
20
+ out += c;
21
+ i++;
22
+ while (i < n && src[i] !== "`") {
23
+ if (src[i] === "\\") {
24
+ out += src.slice(i, i + 2);
25
+ i += 2;
26
+ } else if (src[i] === "$" && src[i + 1] === "{") {
27
+ const [inner, j] = rewrite(src, i + 2, true);
28
+ out += `\${${inner}}`;
29
+ i = j;
30
+ } else out += src[i++];
31
+ }
32
+ out += "`";
33
+ i++;
34
+ } else if (c === "/" && d === "/") {
16
35
  const j = src.indexOf("\n", i);
17
36
  i = j < 0 ? n : j;
18
- } else if (c === "/" && next === "*") {
37
+ } else if (c === "/" && d === "*") {
19
38
  const j = src.indexOf("*/", i + 2);
20
39
  i = j < 0 ? n : j + 2;
21
- } else {
22
- const skip = onCode(c, i);
23
- i += skip || 1;
24
- }
25
- }
26
- };
27
- const rewriteActions = (src) => {
28
- let out = "";
29
- scan(src, (c, i) => {
30
- if (c === "@") {
40
+ } else if (c === "/" && BEFORE_REGEX.test(code())) {
41
+ let j = i + 1;
42
+ let cls = false;
43
+ for (; j < n && (cls || src[j] !== "/"); j++) {
44
+ if (src[j] === "\\") j++;
45
+ else if (src[j] === "[") cls = true;
46
+ else if (src[j] === "]") cls = false;
47
+ }
48
+ for (j++; j < n && /[a-z]/i.test(src[j]); j++) ;
49
+ out += src.slice(i, j);
50
+ i = j;
51
+ } else if (hole && (c === "{" || c === "}")) {
52
+ if (c === "{") depth++;
53
+ else if (depth) depth--;
54
+ else return [out, i + 1];
55
+ out += c;
56
+ i++;
57
+ } else if (c === "$" && START.test(d ?? "") && d !== "$" && !IDENT.test(out.at(-1) ?? "")) {
58
+ const before = code();
59
+ if (before.endsWith(".") && !before.endsWith("...")) {
60
+ out += c;
61
+ i++;
62
+ continue;
63
+ }
64
+ let j = i + 1;
65
+ while (j < n && IDENT.test(src[j])) j++;
66
+ out += `$.${src.slice(i + 1, j)}`;
67
+ i = j;
68
+ } else if (c === "@" && START.test(d ?? "")) {
31
69
  let j = i + 1;
32
- while (j < src.length && isIdent(src[j])) j++;
70
+ while (j < n && IDENT.test(src[j])) j++;
33
71
  let k = j;
34
- while (k < src.length && /\s/.test(src[k])) k++;
35
- if (j > i + 1 && src[k] === "(") {
72
+ while (k < n && /\s/.test(src[k])) k++;
73
+ if (src[k] === "(") {
36
74
  out += `__a.${src.slice(i + 1, j)}(`;
37
- return k + 1 - i;
75
+ i = k + 1;
76
+ } else {
77
+ out += c;
78
+ i++;
38
79
  }
80
+ } else {
81
+ out += c;
82
+ i++;
39
83
  }
40
- out += c;
41
- });
42
- return out;
84
+ }
85
+ return [out, i];
43
86
  };
44
- const splitStatements = (src) => {
45
- const parts = [];
46
- let depth = 0;
47
- let start = 0;
48
- scan(src, (c, i) => {
49
- if (c.length > 1) return;
50
- if ("([{".includes(c)) depth++;
51
- else if (")]}".includes(c)) depth--;
52
- else if (c === ";" && !depth) {
53
- parts.push(src.slice(start, i));
54
- start = i + 1;
55
- }
56
- });
57
- parts.push(src.slice(start));
58
- return parts.filter((p) => p.trim());
87
+ const transform = (src) => rewrite(src, 0, false)[0];
88
+ const candidates = (code) => {
89
+ const out = [`return(${code}
90
+ )`];
91
+ for (let i = code.lastIndexOf(";"); i >= 0; i = code.lastIndexOf(";", i - 1))
92
+ out.push(`${code.slice(0, i)};return(${code.slice(i + 1)}
93
+ )`);
94
+ out.push(code);
95
+ return out.map((b) => `"use strict";${b}`);
59
96
  };
60
- const compile = (compiler, src, params, returns) => {
61
- const key = `${+returns}|${params.join()}|${src}`;
62
- const hit = cache.get(key);
63
- if (hit) return hit;
64
- const code = rewriteActions(src.trim());
65
- const make = (body) => compiler(["$", "__a", ...params], `with($.scope){${body}
66
- }`);
67
- let fn;
68
- if (!returns) {
69
- fn = make(code);
70
- } else {
97
+ const compileBody = (compiler, src, params) => {
98
+ let error;
99
+ for (const body of candidates(transform(src.trim()))) {
71
100
  try {
72
- fn = make(`return(${code}
73
- )`);
74
- } catch {
75
- const parts = splitStatements(code);
76
- const last = parts.pop() ?? "";
77
- try {
78
- fn = make(`${parts.join(";")};return(${last}
79
- )`);
80
- } catch {
81
- fn = make(code);
82
- }
101
+ return [body, compiler(["$", "__a", ...params], body)];
102
+ } catch (e) {
103
+ error = e;
83
104
  }
84
105
  }
85
- cache.set(key, fn);
106
+ throw error;
107
+ };
108
+ const cache = /* @__PURE__ */ new Map();
109
+ const CACHE_LIMIT = 2e3;
110
+ const compile = (compiler, src, params) => {
111
+ const key = `${params.join()}|${src}`;
112
+ let fn = cache.get(key);
113
+ if (!fn) {
114
+ fn = compileBody(compiler, src, params)[1];
115
+ if (cache.size >= CACHE_LIMIT) cache.delete(cache.keys().next().value);
116
+ cache.set(key, fn);
117
+ }
86
118
  return fn;
87
119
  };
88
- const runtimeExpressions = (compiler) => (src, params, returns) => compile(compiler, src, params, returns);
120
+ const runtimeExpressions = (compiler) => (src, params) => {
121
+ const fn = compile(compiler, src, params);
122
+ return (store, actions, ...rest) => fn(store.$, actions, ...rest);
123
+ };
89
124
  export {
90
125
  compile,
126
+ compileBody,
91
127
  functionCompiler,
92
- rewriteActions,
93
128
  runtimeExpressions,
94
- splitStatements
129
+ transform
95
130
  };
96
131
  //# sourceMappingURL=compile.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/kernel/compile.ts"],
4
- "sourcesContent": ["// Expression compiler. Signals need no rewriting: the body runs inside `with (scope)`, where\n// `$count`, `$user.name` and `$` resolve through the store's scope proxy. Only `@action(` is\n// rewritten, into a call on the per-evaluation actions object.\n\nimport type { ExpressionCompiler } from './contracts.js';\n\nexport type Compiler = (params: string[], body: string) => (...args: any[]) => any;\n\n/** Default compiler: `new Function`. Not usable under a strict CSP; see `cspCompiler`. */\nexport const functionCompiler: Compiler = (params, body) => Function(...params, body) as (...args: any[]) => any;\n\nconst cache = new Map<string, (...args: any[]) => any>();\n\nconst isIdent = (c: string): boolean => /[A-Za-z0-9_$]/.test(c);\n\n/** Walk source text, skipping string, template and comment contents; `emit` receives code chars. */\n// biome-ignore lint/suspicious/noConfusingVoidType: callers may return nothing to keep scanning\nconst scan = (src: string, onCode: (c: string, i: number) => number | void): void => {\n let i = 0;\n const n = src.length;\n while (i < n) {\n const c = src[i];\n const next = src[i + 1];\n if (c === '\"' || c === \"'\" || c === '`') {\n let j = i + 1;\n while (j < n && src[j] !== c) j += src[j] === '\\\\' ? 2 : 1;\n onCode(src.slice(i, j + 1), i);\n i = j + 1;\n } else if (c === '/' && next === '/') {\n const j = src.indexOf('\\n', i);\n i = j < 0 ? n : j;\n } else if (c === '/' && next === '*') {\n const j = src.indexOf('*/', i + 2);\n i = j < 0 ? n : j + 2;\n } else {\n const skip = onCode(c, i);\n i += skip || 1;\n }\n }\n};\n\n/** Replace `@name(` with `__a.name(` outside strings and comments. */\nexport const rewriteActions = (src: string): string => {\n let out = '';\n scan(src, (c, i) => {\n if (c === '@') {\n let j = i + 1;\n while (j < src.length && isIdent(src[j])) j++;\n let k = j;\n while (k < src.length && /\\s/.test(src[k])) k++;\n if (j > i + 1 && src[k] === '(') {\n out += `__a.${src.slice(i + 1, j)}(`;\n return k + 1 - i;\n }\n }\n out += c;\n });\n return out;\n};\n\n/** Split on top-level semicolons. */\nexport const splitStatements = (src: string): string[] => {\n const parts: string[] = [];\n let depth = 0;\n let start = 0;\n scan(src, (c, i) => {\n if (c.length > 1) return;\n if ('([{'.includes(c)) depth++;\n else if (')]}'.includes(c)) depth--;\n else if (c === ';' && !depth) {\n parts.push(src.slice(start, i));\n start = i + 1;\n }\n });\n parts.push(src.slice(start));\n return parts.filter((p) => p.trim());\n};\n\n/**\n * Compile an expression to `(store, actions, ...params) => any`: the body runs inside\n * `with (store.scope)`, so `$name` resolves through the store. With `returns`, the value of the\n * last statement is returned, so `$a = 1; $a * 2` works. Compiled functions are cached by source.\n */\nexport const compile = (\n compiler: Compiler,\n src: string,\n params: string[],\n returns: boolean,\n): ((...args: any[]) => any) => {\n const key = `${+returns}|${params.join()}|${src}`;\n const hit = cache.get(key);\n if (hit) return hit;\n const code = rewriteActions(src.trim());\n const make = (body: string) => compiler(['$', '__a', ...params], `with($.scope){${body}\\n}`);\n let fn: (...args: any[]) => any;\n if (!returns) {\n fn = make(code);\n } else {\n try {\n fn = make(`return(${code}\\n)`);\n } catch {\n const parts = splitStatements(code);\n const last = parts.pop() ?? '';\n try {\n fn = make(`${parts.join(';')};return(${last}\\n)`);\n } catch {\n fn = make(code);\n }\n }\n }\n cache.set(key, fn);\n return fn;\n};\n\n/** The default pipeline: compile at runtime inside `with (scope)` using `compiler` (`new Function` or CSP). */\nexport const runtimeExpressions =\n (compiler: Compiler): ExpressionCompiler =>\n (src, params, returns) =>\n compile(compiler, src, params, returns);\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["// Expression compiler. An attribute expression is rewritten once, `$name` \u2192 `$.name` and\n// `@name(` \u2192 `__a.name(`, then compiled as a strict-mode function of `($, __a, el, evt, ...args)`\n// where `$` is the store's root proxy. The build-time precompiler emits exactly the same bodies,\n// so precompiled and runtime expressions behave identically.\n\nimport type { ExpressionCompiler } from './contracts.js';\n\nexport type Compiler = (params: string[], body: string) => (...args: any[]) => any;\n\n/** Default compiler: `new Function`. Not usable under a strict CSP; see `cspCompiler`. */\nexport const functionCompiler: Compiler = (params, body) => Function(...params, body) as (...args: any[]) => any;\n\nconst START = /[A-Za-z_$]/;\nconst IDENT = /[\\w$]/;\n/** After these a `/` starts a regular expression rather than a division. */\nconst BEFORE_REGEX = /(^|[(,=:[!&|?{};+\\-*%<>~^]|\\b(?:return|typeof|case|in|of|do|else|void|delete|throw|new))$/;\n\n/**\n * Tokenizer-based rewrite. Copies string literals, template text, and regular expressions verbatim,\n * drops comments, recurses into `${\u2026}` holes, and rewrites signals and actions in code. In hole\n * mode it stops at the unmatched `}` and reports the index after it.\n */\nconst rewrite = (src: string, from: number, hole: boolean): [string, number] => {\n let out = '';\n let i = from;\n let depth = 0;\n const n = src.length;\n const code = () => out.trimEnd();\n while (i < n) {\n const c = src[i];\n const d = src[i + 1];\n if (c === '\"' || c === \"'\") {\n let j = i + 1;\n while (j < n && src[j] !== c) j += src[j] === '\\\\' ? 2 : 1;\n out += src.slice(i, j + 1);\n i = j + 1;\n } else if (c === '`') {\n out += c;\n i++;\n while (i < n && src[i] !== '`') {\n if (src[i] === '\\\\') {\n out += src.slice(i, i + 2);\n i += 2;\n } else if (src[i] === '$' && src[i + 1] === '{') {\n const [inner, j] = rewrite(src, i + 2, true);\n out += `\\${${inner}}`;\n i = j;\n } else out += src[i++];\n }\n out += '`';\n i++;\n } else if (c === '/' && d === '/') {\n const j = src.indexOf('\\n', i);\n i = j < 0 ? n : j;\n } else if (c === '/' && d === '*') {\n const j = src.indexOf('*/', i + 2);\n i = j < 0 ? n : j + 2;\n } else if (c === '/' && BEFORE_REGEX.test(code())) {\n let j = i + 1;\n let cls = false;\n for (; j < n && (cls || src[j] !== '/'); j++) {\n if (src[j] === '\\\\') j++;\n else if (src[j] === '[') cls = true;\n else if (src[j] === ']') cls = false;\n }\n for (j++; j < n && /[a-z]/i.test(src[j]); j++);\n out += src.slice(i, j);\n i = j;\n } else if (hole && (c === '{' || c === '}')) {\n if (c === '{') depth++;\n else if (depth) depth--;\n else return [out, i + 1];\n out += c;\n i++;\n } else if (c === '$' && START.test(d ?? '') && d !== '$' && !IDENT.test(out.at(-1) ?? '')) {\n const before = code();\n // `obj.$x` is a member access and stays; `...$x` is a spread and is a signal.\n if (before.endsWith('.') && !before.endsWith('...')) {\n out += c;\n i++;\n continue;\n }\n let j = i + 1;\n while (j < n && IDENT.test(src[j])) j++;\n out += `$.${src.slice(i + 1, j)}`;\n i = j;\n } else if (c === '@' && START.test(d ?? '')) {\n let j = i + 1;\n while (j < n && IDENT.test(src[j])) j++;\n let k = j;\n while (k < n && /\\s/.test(src[k])) k++;\n if (src[k] === '(') {\n out += `__a.${src.slice(i + 1, j)}(`;\n i = k + 1;\n } else {\n out += c;\n i++;\n }\n } else {\n out += c;\n i++;\n }\n }\n return [out, i];\n};\n\n/** `$count + @fit($x)` \u2192 `$.count + __a.fit($.x)`; strings, template text, comments and regexes untouched. */\nexport const transform = (src: string): string => rewrite(src, 0, false)[0];\n\n/**\n * The strict-mode bodies to try, best first: the whole source as an expression, then each split at a\n * top-level-looking `;` from the end (statements followed by a returned value), then plain statements.\n * A split inside a string or bracket does not compile and the next candidate is tried.\n */\nconst candidates = (code: string): string[] => {\n const out = [`return(${code}\\n)`];\n for (let i = code.lastIndexOf(';'); i >= 0; i = code.lastIndexOf(';', i - 1))\n out.push(`${code.slice(0, i)};return(${code.slice(i + 1)}\\n)`);\n out.push(code);\n return out.map((b) => `\"use strict\";${b}`);\n};\n\n/**\n * Compile `src` with `compiler` as `($, __a, ...params) => any`, returning the body that compiled\n * together with the function. Shared by the runtime and the build-time precompiler.\n */\nexport const compileBody = (compiler: Compiler, src: string, params: string[]): [string, (...args: any[]) => any] => {\n let error: unknown;\n for (const body of candidates(transform(src.trim()))) {\n try {\n return [body, compiler(['$', '__a', ...params], body)];\n } catch (e) {\n error = e;\n }\n }\n throw error;\n};\n\nconst cache = new Map<string, (...args: any[]) => any>();\nconst CACHE_LIMIT = 2000; // a long-lived page streaming unique expressions must not grow without bound\n\n/** Compile with a cache keyed by parameter names and source. */\nexport const compile = (compiler: Compiler, src: string, params: string[]): ((...args: any[]) => any) => {\n const key = `${params.join()}|${src}`;\n let fn = cache.get(key);\n if (!fn) {\n fn = compileBody(compiler, src, params)[1];\n if (cache.size >= CACHE_LIMIT) cache.delete(cache.keys().next().value as string);\n cache.set(key, fn);\n }\n return fn;\n};\n\n/** The default pipeline: compile at runtime with `compiler` (`new Function` or the CSP compiler). */\nexport const runtimeExpressions =\n (compiler: Compiler): ExpressionCompiler =>\n (src, params) => {\n const fn = compile(compiler, src, params);\n return (store, actions, ...rest) => fn(store.$, actions, ...rest);\n };\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -6,7 +6,7 @@ export type Mods = Map<string, string[]>;
6
6
  /** A compiled attribute expression. `store` is the instance store; `actions` resolves `@name(...)`. */
7
7
  export type Evaluator = (store: Store, actions: any, el: El, evt: Event | undefined, ...args: any[]) => any;
8
8
  /** Turns expression source into an evaluator. The runtime compiler and the precompiled table both implement this. */
9
- export type ExpressionCompiler = (src: string, params: string[], returns: boolean) => Evaluator;
9
+ export type ExpressionCompiler = (src: string, params: string[]) => Evaluator;
10
10
  export interface Runtime {
11
11
  /** Attribute prefixes being scanned; the first is primary. */
12
12
  readonly prefixes: readonly string[];
@@ -42,8 +42,11 @@ export interface Ctx {
42
42
  effect(fn: () => void): void;
43
43
  /** addEventListener with automatic removal on cleanup. */
44
44
  listen(target: EventTarget, type: string, fn: (e: any) => void, options?: AddEventListenerOptions): void;
45
- cleanup(fn: () => void): void;
45
+ /** Run `fn` when the attribute unmounts. Returns a function that unregisters it (for work that finished early). */
46
+ cleanup(fn: () => void): () => void;
46
47
  error(message: string, extra?: Record<string, unknown>): Error;
48
+ /** Route an error that happened later (a callback, a promise) to `onError` with this attribute's info. */
49
+ report(error: unknown): void;
47
50
  store: Store;
48
51
  runtime: Runtime;
49
52
  }
@@ -53,8 +56,10 @@ export interface ActionCtx {
53
56
  store: Store;
54
57
  runtime: Runtime;
55
58
  error(message: string, extra?: Record<string, unknown>): Error;
56
- /** Run when the attribute that invoked the action is torn down. */
57
- cleanup(fn: () => void): void;
59
+ /** Route an error that happened later (a callback, a promise) to `onError` with this attribute's info. */
60
+ report(error: unknown): void;
61
+ /** Run when the attribute that invoked the action is torn down. Returns a function that unregisters it. */
62
+ cleanup(fn: () => void): () => void;
58
63
  }
59
64
  export interface AttributePlugin {
60
65
  type: 'attribute';
@@ -62,7 +67,8 @@ export interface AttributePlugin {
62
67
  key?: 'required' | 'forbidden';
63
68
  value?: 'required' | 'forbidden';
64
69
  /** Whether the expression yields a value (default) or is a statement body. */
65
- returns?: boolean;
70
+ /** The value is a literal (a mask, a selector, a delay) rather than an expression; `__dynamic` makes it one. */
71
+ literal?: boolean;
66
72
  /** Extra parameter names the expression can reference, in the order passed to `evaluate`. */
67
73
  args?: string[];
68
74
  mount(ctx: Ctx): void | (() => void);
@@ -1,9 +1,9 @@
1
1
  export { camel, kebab, pascal, recase, snake } from '../lib/casing.js';
2
2
  export type { Filter } from '../lib/objects.js';
3
3
  export { expand, isPlain, toPredicate } from '../lib/objects.js';
4
- export { debounce, delay, throttle, toMs, withTiming, withViewTransition } from '../lib/schedule.js';
4
+ export { debounce, delay, throttle, toMs, withTiming } from '../lib/schedule.js';
5
5
  export type { Compiler } from './compile.js';
6
- export { compile, functionCompiler, rewriteActions, runtimeExpressions, splitStatements } from './compile.js';
6
+ export { compile, compileBody, functionCompiler, runtimeExpressions, transform } from './compile.js';
7
7
  export type * from './contracts.js';
8
8
  export type { ExpressionTable } from './precompiled.js';
9
9
  export { expressionKey, precompiled } from './precompiled.js';
@@ -1,7 +1,7 @@
1
1
  import { camel, kebab, pascal, recase, snake } from "../lib/casing.js";
2
2
  import { expand, isPlain, toPredicate } from "../lib/objects.js";
3
- import { debounce, delay, throttle, toMs, withTiming, withViewTransition } from "../lib/schedule.js";
4
- import { compile, functionCompiler, rewriteActions, runtimeExpressions, splitStatements } from "./compile.js";
3
+ import { debounce, delay, throttle, toMs, withTiming } from "../lib/schedule.js";
4
+ import { compile, compileBody, functionCompiler, runtimeExpressions, transform } from "./compile.js";
5
5
  import { expressionKey, precompiled } from "./precompiled.js";
6
6
  import { batch, Computed, computed, effect, flush, onSettled, Signal, signal, untracked } from "./reactive.js";
7
7
  import { createRuntime, parseAttr } from "./runtime.js";
@@ -23,6 +23,7 @@ export {
23
23
  batch,
24
24
  camel,
25
25
  compile,
26
+ compileBody,
26
27
  computed,
27
28
  createRuntime,
28
29
  createSigmx,
@@ -42,16 +43,14 @@ export {
42
43
  pascal,
43
44
  precompiled,
44
45
  recase,
45
- rewriteActions,
46
46
  runtimeExpressions,
47
47
  signal,
48
48
  snake,
49
- splitStatements,
50
49
  throttle,
51
50
  toMs,
52
51
  toPredicate,
52
+ transform,
53
53
  untracked,
54
- withTiming,
55
- withViewTransition
54
+ withTiming
56
55
  };
57
56
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/kernel/index.ts"],
4
- "sourcesContent": ["export { camel, kebab, pascal, recase, snake } from '../lib/casing.js';\nexport type { Filter } from '../lib/objects.js';\nexport { expand, isPlain, toPredicate } from '../lib/objects.js';\nexport { debounce, delay, throttle, toMs, withTiming, withViewTransition } from '../lib/schedule.js';\nexport type { Compiler } from './compile.js';\nexport { compile, functionCompiler, rewriteActions, runtimeExpressions, splitStatements } from './compile.js';\nexport type * from './contracts.js';\nexport type { ExpressionTable } from './precompiled.js';\nexport { expressionKey, precompiled } from './precompiled.js';\nexport { batch, Computed, computed, effect, flush, onSettled, Signal, signal, untracked } from './reactive.js';\nexport { createRuntime, parseAttr } from './runtime.js';\nexport type { Patch, Store } from './state.js';\nexport { createStore } from './state.js';\n\nimport { functionCompiler, runtimeExpressions } from './compile.js';\nimport type { ActionPlugin, AttributePlugin, HandlerPlugin, Sigmx, SigmxOptions } from './contracts.js';\nimport { createRuntime } from './runtime.js';\n\n/** Create an instance. Expressions compile at runtime unless you pass `expressions` (see `precompiled`). */\nexport const createSigmx = (options: SigmxOptions = {}): Sigmx =>\n createRuntime({\n ...options,\n expressions: options.expressions ?? runtimeExpressions(options.compile ?? functionCompiler),\n });\n\nexport const attribute = (p: Omit<AttributePlugin, 'type'>): AttributePlugin => ({ type: 'attribute', ...p });\nexport const action = (p: Omit<ActionPlugin, 'type'>): ActionPlugin => ({ type: 'action', ...p });\nexport const handler = (p: Omit<HandlerPlugin, 'type'>): HandlerPlugin => ({ type: 'handler', ...p });\n"],
5
- "mappings": "AAAA,SAAS,OAAO,OAAO,QAAQ,QAAQ,aAAa;AAEpD,SAAS,QAAQ,SAAS,mBAAmB;AAC7C,SAAS,UAAU,OAAO,UAAU,MAAM,YAAY,0BAA0B;AAEhF,SAAS,SAAS,kBAAkB,gBAAgB,oBAAoB,uBAAuB;AAG/F,SAAS,eAAe,mBAAmB;AAC3C,SAAS,OAAO,UAAU,UAAU,QAAQ,OAAO,WAAW,QAAQ,QAAQ,iBAAiB;AAC/F,SAAS,eAAe,iBAAiB;AAEzC,SAAS,mBAAmB;AAE5B,SAAS,oBAAAA,mBAAkB,sBAAAC,2BAA0B;AAErD,SAAS,iBAAAC,sBAAqB;AAGvB,MAAM,cAAc,CAAC,UAAwB,CAAC,MACnDA,eAAc;AAAA,EACZ,GAAG;AAAA,EACH,aAAa,QAAQ,eAAeD,oBAAmB,QAAQ,WAAWD,iBAAgB;AAC5F,CAAC;AAEI,MAAM,YAAY,CAAC,OAAuD,EAAE,MAAM,aAAa,GAAG,EAAE;AACpG,MAAM,SAAS,CAAC,OAAiD,EAAE,MAAM,UAAU,GAAG,EAAE;AACxF,MAAM,UAAU,CAAC,OAAmD,EAAE,MAAM,WAAW,GAAG,EAAE;",
4
+ "sourcesContent": ["export { camel, kebab, pascal, recase, snake } from '../lib/casing.js';\nexport type { Filter } from '../lib/objects.js';\nexport { expand, isPlain, toPredicate } from '../lib/objects.js';\nexport { debounce, delay, throttle, toMs, withTiming } from '../lib/schedule.js';\nexport type { Compiler } from './compile.js';\nexport { compile, compileBody, functionCompiler, runtimeExpressions, transform } from './compile.js';\nexport type * from './contracts.js';\nexport type { ExpressionTable } from './precompiled.js';\nexport { expressionKey, precompiled } from './precompiled.js';\nexport { batch, Computed, computed, effect, flush, onSettled, Signal, signal, untracked } from './reactive.js';\nexport { createRuntime, parseAttr } from './runtime.js';\nexport type { Patch, Store } from './state.js';\nexport { createStore } from './state.js';\n\nimport { functionCompiler, runtimeExpressions } from './compile.js';\nimport type { ActionPlugin, AttributePlugin, HandlerPlugin, Sigmx, SigmxOptions } from './contracts.js';\nimport { createRuntime } from './runtime.js';\n\n/** Create an instance. Expressions compile at runtime unless you pass `expressions` (see `precompiled`). */\nexport const createSigmx = (options: SigmxOptions = {}): Sigmx =>\n createRuntime({\n ...options,\n expressions: options.expressions ?? runtimeExpressions(options.compile ?? functionCompiler),\n });\n\nexport const attribute = (p: Omit<AttributePlugin, 'type'>): AttributePlugin => ({ type: 'attribute', ...p });\nexport const action = (p: Omit<ActionPlugin, 'type'>): ActionPlugin => ({ type: 'action', ...p });\nexport const handler = (p: Omit<HandlerPlugin, 'type'>): HandlerPlugin => ({ type: 'handler', ...p });\n"],
5
+ "mappings": "AAAA,SAAS,OAAO,OAAO,QAAQ,QAAQ,aAAa;AAEpD,SAAS,QAAQ,SAAS,mBAAmB;AAC7C,SAAS,UAAU,OAAO,UAAU,MAAM,kBAAkB;AAE5D,SAAS,SAAS,aAAa,kBAAkB,oBAAoB,iBAAiB;AAGtF,SAAS,eAAe,mBAAmB;AAC3C,SAAS,OAAO,UAAU,UAAU,QAAQ,OAAO,WAAW,QAAQ,QAAQ,iBAAiB;AAC/F,SAAS,eAAe,iBAAiB;AAEzC,SAAS,mBAAmB;AAE5B,SAAS,oBAAAA,mBAAkB,sBAAAC,2BAA0B;AAErD,SAAS,iBAAAC,sBAAqB;AAGvB,MAAM,cAAc,CAAC,UAAwB,CAAC,MACnDA,eAAc;AAAA,EACZ,GAAG;AAAA,EACH,aAAa,QAAQ,eAAeD,oBAAmB,QAAQ,WAAWD,iBAAgB;AAC5F,CAAC;AAEI,MAAM,YAAY,CAAC,OAAuD,EAAE,MAAM,aAAa,GAAG,EAAE;AACpG,MAAM,SAAS,CAAC,OAAiD,EAAE,MAAM,UAAU,GAAG,EAAE;AACxF,MAAM,UAAU,CAAC,OAAmD,EAAE,MAAM,WAAW,GAAG,EAAE;",
6
6
  "names": ["functionCompiler", "runtimeExpressions", "createRuntime"]
7
7
  }
@@ -1,23 +1,21 @@
1
- /**
2
- * Rewrite `$name` and `$a.b` into `$.name` / `$.a.b` so the expression runs in strict mode without
3
- * `with`. Skips strings and comments, recurses into template-literal interpolations, and leaves
4
- * `$` alone, `$[...]` alone, and `obj.$prop` alone.
5
- */
6
- export declare const rewriteSignals: (src: string) => string;
7
- export declare const compileBody: (src: string, returns: boolean) => string;
8
1
  export type Extracted = {
9
2
  src: string;
10
3
  params: string[];
11
- returns: boolean;
12
4
  };
13
5
  export declare const generateTable: (items: Extracted[]) => string;
14
6
  export type PluginMeta = {
15
7
  name: string;
16
- returns?: boolean;
17
8
  args?: string[];
9
+ literal?: boolean;
18
10
  };
19
11
  /**
20
12
  * Find directive attributes with literal values in template source. Values must be plain quoted
21
- * strings; anything interpolated at render time is left to the runtime fallback.
13
+ * strings; anything interpolated at render time is left to the runtime fallback. Plugins whose value
14
+ * is a literal (mask, teleport, …) are skipped unless the attribute carries `__dynamic`.
22
15
  */
23
16
  export declare const extractExpressions: (source: string, plugins: PluginMeta[], prefixes?: string[]) => Extracted[];
17
+ /**
18
+ * Keyed directive attributes whose key contains an uppercase letter. HTML lowercases attribute names,
19
+ * so `data-bind:firstName` reaches the runtime as `firstname`: write `data-bind:first-name`.
20
+ */
21
+ export declare const lintSource: (source: string, prefixes?: string[]) => string[];
@@ -1,80 +1,15 @@
1
- import { rewriteActions, splitStatements } from "./compile.js";
1
+ import { compileBody, functionCompiler } from "./compile.js";
2
2
  import { expressionKey } from "./precompiled.js";
3
- const ident = /[A-Za-z0-9_$]/;
4
- const rewriteSignals = (src) => {
5
- let out = "";
6
- let i = 0;
7
- const n = src.length;
8
- const prevCode = () => {
9
- for (let j = out.length - 1; j >= 0; j--) if (!/\s/.test(out[j])) return out[j];
10
- return "";
11
- };
12
- while (i < n) {
13
- const c = src[i];
14
- const next = src[i + 1];
15
- if (c === '"' || c === "'") {
16
- let j = i + 1;
17
- while (j < n && src[j] !== c) j += src[j] === "\\" ? 2 : 1;
18
- out += src.slice(i, j + 1);
19
- i = j + 1;
20
- } else if (c === "`") {
21
- out += c;
22
- i++;
23
- while (i < n && src[i] !== "`") {
24
- if (src[i] === "\\") {
25
- out += src.slice(i, i + 2);
26
- i += 2;
27
- } else if (src[i] === "$" && src[i + 1] === "{") {
28
- let depth = 1;
29
- let j = i + 2;
30
- while (j < n && depth) {
31
- if (src[j] === "{") depth++;
32
- else if (src[j] === "}") depth--;
33
- if (depth) j++;
34
- }
35
- out += `\${${rewriteSignals(src.slice(i + 2, j))}}`;
36
- i = j + 1;
37
- } else out += src[i++];
38
- }
39
- out += "`";
40
- i++;
41
- } else if (c === "/" && next === "/") {
42
- const j = src.indexOf("\n", i);
43
- i = j < 0 ? n : j;
44
- } else if (c === "/" && next === "*") {
45
- const j = src.indexOf("*/", i + 2);
46
- i = j < 0 ? n : j + 2;
47
- } else if (c === "$" && next !== void 0 && ident.test(next) && next !== "$" && prevCode() !== "." && !ident.test(out[out.length - 1] ?? "")) {
48
- let j = i + 1;
49
- while (j < n && ident.test(src[j])) j++;
50
- out += `$.${src.slice(i + 1, j)}`;
51
- i = j;
52
- } else {
53
- out += c;
54
- i++;
55
- }
56
- }
57
- return out;
58
- };
59
- const compileBody = (src, returns) => {
60
- const code = rewriteSignals(rewriteActions(src.trim()));
61
- if (!returns) return code;
62
- const parts = splitStatements(code);
63
- const last = parts.pop() ?? "";
64
- return `${parts.length ? `${parts.join(";")};` : ""}return (${last}
65
- )`;
66
- };
67
3
  const generateTable = (items) => {
68
4
  const seen = /* @__PURE__ */ new Set();
69
5
  const lines = [];
70
- for (const { src, params, returns } of items) {
71
- const key = expressionKey(src, params, returns);
6
+ for (const { src, params } of items) {
7
+ const key = expressionKey(src);
72
8
  if (seen.has(key)) continue;
73
9
  seen.add(key);
74
10
  let body;
75
11
  try {
76
- body = compileBody(src, returns);
77
- Function("$", "__a", ...params, body);
12
+ [body] = compileBody(functionCompiler, src, params);
78
13
  } catch {
79
14
  continue;
80
15
  }
@@ -93,7 +28,15 @@ const entities = {
93
28
  "&gt;": ">",
94
29
  "&amp;": "&"
95
30
  };
96
- const decode = (s) => s.replace(/&(quot|#39|apos|lt|gt|amp);/g, (m) => entities[m]);
31
+ const decode = (s) => s.replace(
32
+ /&(?:quot|apos|lt|gt|amp|#\d+|#x[\da-f]+);/gi,
33
+ (m) => m[1] === "#" ? String.fromCodePoint(
34
+ Number.parseInt(
35
+ m[2] === "x" || m[2] === "X" ? m.slice(3, -1) : m.slice(2, -1),
36
+ m[2] === "x" || m[2] === "X" ? 16 : 10
37
+ )
38
+ ) : entities[m.toLowerCase()] ?? m
39
+ );
97
40
  const extractExpressions = (source, plugins, prefixes = ["data-"]) => {
98
41
  const byName = new Map(plugins.map((p) => [p.name, p]));
99
42
  const out = [];
@@ -103,16 +46,23 @@ const extractExpressions = (source, plugins, prefixes = ["data-"]) => {
103
46
  const value = decode(m[2] ?? m[3] ?? "");
104
47
  const prefix = prefixes.find((p) => attr.startsWith(p));
105
48
  if (!prefix || !value.trim()) continue;
106
- const plugin = byName.get(attr.slice(prefix.length).split("__")[0].split(":")[0]);
107
- if (!plugin) continue;
108
- out.push({ src: value, params: ["el", "evt", ...plugin.args ?? []], returns: plugin.returns ?? true });
49
+ const [head, ...mods] = attr.slice(prefix.length).split("__");
50
+ const plugin = byName.get(head.split(":")[0]);
51
+ if (!plugin || plugin.literal && !mods.some((x) => x.split(".")[0] === "dynamic")) continue;
52
+ out.push({ src: value, params: ["el", "evt", ...plugin.args ?? []] });
109
53
  }
110
54
  return out;
111
55
  };
56
+ const lintSource = (source, prefixes = ["data-"]) => {
57
+ const out = [];
58
+ for (const p of prefixes)
59
+ for (const m of source.matchAll(new RegExp(`(?<![\\w-])(${p}[a-z-]+:[^\\s="'<>]*[A-Z][^\\s="'<>]*)`, "g")))
60
+ out.push(`${m[1]}: attribute names are lowercased by HTML; write the key in kebab-case`);
61
+ return out;
62
+ };
112
63
  export {
113
- compileBody,
114
64
  extractExpressions,
115
65
  generateTable,
116
- rewriteSignals
66
+ lintSource
117
67
  };
118
68
  //# sourceMappingURL=precompile.js.map