sigmx 0.1.0 → 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.
- package/README.md +54 -43
- package/dist/cli.js +3 -1
- package/dist/cli.js.map +2 -2
- package/dist/kernel/compile.d.ts +8 -9
- package/dist/kernel/compile.js +102 -67
- package/dist/kernel/compile.js.map +2 -2
- package/dist/kernel/contracts.d.ts +12 -13
- package/dist/kernel/index.d.ts +11 -12
- package/dist/kernel/index.js +14 -12
- package/dist/kernel/index.js.map +3 -3
- package/dist/kernel/precompile.d.ts +8 -12
- package/dist/kernel/precompile.js +33 -76
- package/dist/kernel/precompile.js.map +2 -2
- package/dist/kernel/precompiled.d.ts +7 -4
- package/dist/kernel/precompiled.js +5 -5
- package/dist/kernel/precompiled.js.map +2 -2
- package/dist/kernel/reactive.d.ts +13 -11
- package/dist/kernel/reactive.js +96 -71
- package/dist/kernel/reactive.js.map +3 -3
- package/dist/kernel/runtime.js +85 -63
- package/dist/kernel/runtime.js.map +3 -3
- package/dist/kernel/scan.d.ts +2 -11
- package/dist/kernel/scan.js +26 -9
- package/dist/kernel/scan.js.map +2 -2
- package/dist/kernel/state.d.ts +5 -8
- package/dist/kernel/state.js +114 -76
- package/dist/kernel/state.js.map +2 -2
- package/dist/kernel/strict-csp.d.ts +0 -5
- package/dist/kernel/strict-csp.js +2 -2
- package/dist/kernel/strict-csp.js.map +2 -2
- package/dist/lib/casing.d.ts +4 -1
- package/dist/lib/casing.js +8 -1
- package/dist/lib/casing.js.map +2 -2
- package/dist/lib/objects.d.ts +5 -4
- package/dist/lib/objects.js +13 -5
- package/dist/lib/objects.js.map +2 -2
- package/dist/lib/schedule.d.ts +3 -5
- package/dist/lib/schedule.js +12 -10
- package/dist/lib/schedule.js.map +2 -2
- package/dist/lib/sse.d.ts +10 -1
- package/dist/lib/sse.js +32 -28
- package/dist/lib/sse.js.map +2 -2
- package/dist/plugins/def.d.ts +5 -0
- package/dist/plugins/def.js +22 -0
- package/dist/plugins/def.js.map +7 -0
- package/dist/plugins/directives/attribute-sync.js +8 -12
- package/dist/plugins/directives/attribute-sync.js.map +2 -2
- package/dist/plugins/directives/boost.d.ts +1 -1
- package/dist/plugins/directives/boost.js +34 -30
- package/dist/plugins/directives/boost.js.map +2 -2
- package/dist/plugins/directives/busy-flag.js +13 -16
- package/dist/plugins/directives/busy-flag.js.map +2 -2
- package/dist/plugins/directives/class-toggle.js +17 -16
- package/dist/plugins/directives/class-toggle.js.map +2 -2
- package/dist/plugins/directives/cloak.js +2 -7
- package/dist/plugins/directives/cloak.js.map +2 -2
- package/dist/plugins/directives/collapse.js +36 -37
- package/dist/plugins/directives/collapse.js.map +2 -2
- package/dist/plugins/directives/declare.js +5 -8
- package/dist/plugins/directives/declare.js.map +2 -2
- package/dist/plugins/directives/derive.js +9 -15
- package/dist/plugins/directives/derive.js.map +2 -2
- package/dist/plugins/directives/element-ref.js +8 -8
- package/dist/plugins/directives/element-ref.js.map +2 -2
- package/dist/plugins/directives/event-listener.js +18 -23
- package/dist/plugins/directives/event-listener.js.map +2 -2
- package/dist/plugins/directives/frame-loop.js +9 -14
- package/dist/plugins/directives/frame-loop.js.map +2 -2
- package/dist/plugins/directives/html.js +6 -7
- package/dist/plugins/directives/html.js.map +2 -2
- package/dist/plugins/directives/inline-style.js +17 -20
- package/dist/plugins/directives/inline-style.js.map +2 -2
- package/dist/plugins/directives/mask.js +19 -23
- package/dist/plugins/directives/mask.js.map +2 -2
- package/dist/plugins/directives/media-query.d.ts +4 -1
- package/dist/plugins/directives/media-query.js +13 -12
- package/dist/plugins/directives/media-query.js.map +2 -2
- package/dist/plugins/directives/on-mount.js +4 -9
- package/dist/plugins/directives/on-mount.js.map +2 -2
- package/dist/plugins/directives/patch-listener.js +24 -19
- package/dist/plugins/directives/patch-listener.js.map +2 -2
- package/dist/plugins/directives/remove-me.js +7 -8
- package/dist/plugins/directives/remove-me.js.map +2 -2
- package/dist/plugins/directives/scroll-to.d.ts +4 -1
- package/dist/plugins/directives/scroll-to.js +8 -14
- package/dist/plugins/directives/scroll-to.js.map +2 -2
- package/dist/plugins/directives/size-watch.js +7 -12
- package/dist/plugins/directives/size-watch.js.map +2 -2
- package/dist/plugins/directives/state-dump.js +6 -10
- package/dist/plugins/directives/state-dump.js.map +2 -2
- package/dist/plugins/directives/storage-sync.js +13 -9
- package/dist/plugins/directives/storage-sync.js.map +2 -2
- package/dist/plugins/directives/teleport.js +8 -14
- package/dist/plugins/directives/teleport.js.map +2 -2
- package/dist/plugins/directives/text-content.js +6 -7
- package/dist/plugins/directives/text-content.js.map +2 -2
- package/dist/plugins/directives/timer.js +7 -13
- package/dist/plugins/directives/timer.js.map +2 -2
- package/dist/plugins/directives/transition.d.ts +1 -2
- package/dist/plugins/directives/transition.js +32 -34
- package/dist/plugins/directives/transition.js.map +2 -2
- package/dist/plugins/directives/tween.d.ts +2 -2
- package/dist/plugins/directives/tween.js +34 -50
- package/dist/plugins/directives/tween.js.map +2 -2
- package/dist/plugins/directives/two-way-bind.d.ts +1 -1
- package/dist/plugins/directives/two-way-bind.js +70 -67
- package/dist/plugins/directives/two-way-bind.js.map +2 -2
- package/dist/plugins/directives/url-params.js +36 -59
- package/dist/plugins/directives/url-params.js.map +3 -3
- package/dist/plugins/directives/url-replace.js +6 -7
- package/dist/plugins/directives/url-replace.js.map +2 -2
- package/dist/plugins/directives/validity.js +6 -9
- package/dist/plugins/directives/validity.js.map +2 -2
- package/dist/plugins/directives/viewport.js +18 -23
- package/dist/plugins/directives/viewport.js.map +2 -2
- package/dist/plugins/directives/visibility.js +9 -14
- package/dist/plugins/directives/visibility.js.map +2 -2
- package/dist/plugins/directives/watch.d.ts +0 -1
- package/dist/plugins/directives/watch.js +2 -8
- package/dist/plugins/directives/watch.js.map +2 -2
- package/dist/plugins/functions/bulk-set.js +10 -7
- package/dist/plugins/functions/bulk-set.js.map +2 -2
- package/dist/plugins/functions/bulk-toggle.js +10 -7
- package/dist/plugins/functions/bulk-toggle.js.map +2 -2
- package/dist/plugins/functions/confirm.js +2 -2
- package/dist/plugins/functions/confirm.js.map +2 -2
- package/dist/plugins/functions/copy-text.js +5 -5
- package/dist/plugins/functions/copy-text.js.map +2 -2
- package/dist/plugins/functions/dispatch.js +5 -5
- package/dist/plugins/functions/dispatch.js.map +2 -2
- package/dist/plugins/functions/locale-format.d.ts +4 -1
- package/dist/plugins/functions/locale-format.js +9 -16
- package/dist/plugins/functions/locale-format.js.map +2 -2
- package/dist/plugins/functions/range-map.js +6 -6
- package/dist/plugins/functions/range-map.js.map +2 -2
- package/dist/plugins/functions/request.d.ts +7 -3
- package/dist/plugins/functions/request.js +85 -80
- package/dist/plugins/functions/request.js.map +2 -2
- package/dist/plugins/functions/untracked-read.js +3 -2
- package/dist/plugins/functions/untracked-read.js.map +2 -2
- package/dist/plugins/functions/websocket.d.ts +3 -3
- package/dist/plugins/functions/websocket.js +18 -25
- package/dist/plugins/functions/websocket.js.map +2 -2
- package/dist/plugins/index.d.ts +34 -36
- package/dist/plugins/index.js +31 -35
- package/dist/plugins/index.js.map +2 -2
- package/dist/plugins/server-events/apply-elements.d.ts +1 -1
- package/dist/plugins/server-events/apply-elements.js +41 -40
- package/dist/plugins/server-events/apply-elements.js.map +2 -2
- package/dist/plugins/server-events/apply-state.js +9 -12
- package/dist/plugins/server-events/apply-state.js.map +2 -2
- package/dist/plugins/server-events/morph.d.ts +0 -2
- package/dist/plugins/server-events/morph.js +58 -55
- package/dist/plugins/server-events/morph.js.map +3 -3
- package/dist/presets/all.d.ts +0 -1
- package/dist/presets/all.js +22 -26
- package/dist/presets/all.js.map +2 -2
- package/dist/presets/essentials.d.ts +0 -1
- package/dist/presets/essentials.js +27 -9
- package/dist/presets/essentials.js.map +2 -2
- package/dist/presets/minimal.d.ts +0 -1
- package/dist/presets/minimal.js +2 -2
- package/dist/presets/minimal.js.map +2 -2
- package/dist/server.d.ts +108 -0
- package/dist/server.js +218 -0
- package/dist/server.js.map +7 -0
- package/dist/sigmx.standalone.js +1 -1
- package/dist/standalone.js.map +2 -2
- package/dist/vite.d.ts +2 -2
- package/dist/vite.js +48 -15
- package/dist/vite.js.map +2 -2
- package/package.json +21 -2
- package/dist/auto.d.ts +0 -1
- package/dist/auto.js +0 -7
- package/dist/auto.js.map +0 -7
- package/dist/core/csp.d.ts +0 -7
- package/dist/core/csp.js +0 -20
- package/dist/core/csp.js.map +0 -7
- package/dist/core/engine.d.ts +0 -10
- package/dist/core/engine.js +0 -200
- package/dist/core/engine.js.map +0 -7
- package/dist/core/expr.d.ts +0 -12
- package/dist/core/expr.js +0 -94
- package/dist/core/expr.js.map +0 -7
- package/dist/core/index.d.ts +0 -16
- package/dist/core/index.js +0 -46
- package/dist/core/index.js.map +0 -7
- package/dist/core/signals.d.ts +0 -42
- package/dist/core/signals.js +0 -195
- package/dist/core/signals.js.map +0 -7
- package/dist/core/store.d.ts +0 -32
- package/dist/core/store.js +0 -185
- package/dist/core/store.js.map +0 -7
- package/dist/core/types.d.ts +0 -101
- package/dist/core/types.js +0 -1
- package/dist/core/types.js.map +0 -7
- package/dist/plugins/actions/clipboard.d.ts +0 -2
- package/dist/plugins/actions/clipboard.js +0 -11
- package/dist/plugins/actions/clipboard.js.map +0 -7
- package/dist/plugins/actions/fit.d.ts +0 -2
- package/dist/plugins/actions/fit.js +0 -14
- package/dist/plugins/actions/fit.js.map +0 -7
- package/dist/plugins/actions/intl.d.ts +0 -2
- package/dist/plugins/actions/intl.js +0 -21
- package/dist/plugins/actions/intl.js.map +0 -7
- package/dist/plugins/actions/peek.d.ts +0 -2
- package/dist/plugins/actions/peek.js +0 -6
- package/dist/plugins/actions/peek.js.map +0 -7
- package/dist/plugins/actions/setAll.d.ts +0 -2
- package/dist/plugins/actions/setAll.js +0 -11
- package/dist/plugins/actions/setAll.js.map +0 -7
- package/dist/plugins/actions/toggleAll.d.ts +0 -2
- package/dist/plugins/actions/toggleAll.js +0 -11
- package/dist/plugins/actions/toggleAll.js.map +0 -7
- package/dist/plugins/attributes/attr.d.ts +0 -2
- package/dist/plugins/attributes/attr.js +0 -20
- package/dist/plugins/attributes/attr.js.map +0 -7
- package/dist/plugins/attributes/bind.d.ts +0 -6
- package/dist/plugins/attributes/bind.js +0 -81
- package/dist/plugins/attributes/bind.js.map +0 -7
- package/dist/plugins/attributes/class.d.ts +0 -2
- package/dist/plugins/attributes/class.js +0 -22
- package/dist/plugins/attributes/class.js.map +0 -7
- package/dist/plugins/attributes/computed.d.ts +0 -2
- package/dist/plugins/attributes/computed.js +0 -20
- package/dist/plugins/attributes/computed.js.map +0 -7
- package/dist/plugins/attributes/customValidity.d.ts +0 -2
- package/dist/plugins/attributes/customValidity.js +0 -14
- package/dist/plugins/attributes/customValidity.js.map +0 -7
- package/dist/plugins/attributes/effect.d.ts +0 -2
- package/dist/plugins/attributes/effect.js +0 -12
- package/dist/plugins/attributes/effect.js.map +0 -7
- package/dist/plugins/attributes/indicator.d.ts +0 -2
- package/dist/plugins/attributes/indicator.js +0 -20
- package/dist/plugins/attributes/indicator.js.map +0 -7
- package/dist/plugins/attributes/init.d.ts +0 -2
- package/dist/plugins/attributes/init.js +0 -14
- package/dist/plugins/attributes/init.js.map +0 -7
- package/dist/plugins/attributes/jsonSignals.d.ts +0 -2
- package/dist/plugins/attributes/jsonSignals.js +0 -15
- package/dist/plugins/attributes/jsonSignals.js.map +0 -7
- package/dist/plugins/attributes/matchMedia.d.ts +0 -2
- package/dist/plugins/attributes/matchMedia.js +0 -19
- package/dist/plugins/attributes/matchMedia.js.map +0 -7
- package/dist/plugins/attributes/on.d.ts +0 -5
- package/dist/plugins/attributes/on.js +0 -28
- package/dist/plugins/attributes/on.js.map +0 -7
- package/dist/plugins/attributes/onIntersect.d.ts +0 -2
- package/dist/plugins/attributes/onIntersect.js +0 -28
- package/dist/plugins/attributes/onIntersect.js.map +0 -7
- package/dist/plugins/attributes/onInterval.d.ts +0 -2
- package/dist/plugins/attributes/onInterval.js +0 -18
- package/dist/plugins/attributes/onInterval.js.map +0 -7
- package/dist/plugins/attributes/onRaf.d.ts +0 -1
- package/dist/plugins/attributes/onRaf.js +0 -19
- package/dist/plugins/attributes/onRaf.js.map +0 -7
- package/dist/plugins/attributes/onResize.d.ts +0 -1
- package/dist/plugins/attributes/onResize.js +0 -17
- package/dist/plugins/attributes/onResize.js.map +0 -7
- package/dist/plugins/attributes/onSignalPatch.d.ts +0 -5
- package/dist/plugins/attributes/onSignalPatch.js +0 -35
- package/dist/plugins/attributes/onSignalPatch.js.map +0 -7
- package/dist/plugins/attributes/persist.d.ts +0 -6
- package/dist/plugins/attributes/persist.js +0 -16
- package/dist/plugins/attributes/persist.js.map +0 -7
- package/dist/plugins/attributes/queryString.d.ts +0 -5
- package/dist/plugins/attributes/queryString.js +0 -54
- package/dist/plugins/attributes/queryString.js.map +0 -7
- package/dist/plugins/attributes/ref.d.ts +0 -2
- package/dist/plugins/attributes/ref.js +0 -13
- package/dist/plugins/attributes/ref.js.map +0 -7
- package/dist/plugins/attributes/replaceUrl.d.ts +0 -1
- package/dist/plugins/attributes/replaceUrl.js +0 -11
- package/dist/plugins/attributes/replaceUrl.js.map +0 -7
- package/dist/plugins/attributes/scrollIntoView.d.ts +0 -2
- package/dist/plugins/attributes/scrollIntoView.js +0 -19
- package/dist/plugins/attributes/scrollIntoView.js.map +0 -7
- package/dist/plugins/attributes/show.d.ts +0 -2
- package/dist/plugins/attributes/show.js +0 -19
- package/dist/plugins/attributes/show.js.map +0 -7
- package/dist/plugins/attributes/signals.d.ts +0 -2
- package/dist/plugins/attributes/signals.js +0 -13
- package/dist/plugins/attributes/signals.js.map +0 -7
- package/dist/plugins/attributes/style.d.ts +0 -2
- package/dist/plugins/attributes/style.js +0 -25
- package/dist/plugins/attributes/style.js.map +0 -7
- package/dist/plugins/attributes/text.d.ts +0 -1
- package/dist/plugins/attributes/text.js +0 -13
- package/dist/plugins/attributes/text.js.map +0 -7
- package/dist/plugins/attributes/viewTransition.d.ts +0 -1
- package/dist/plugins/attributes/viewTransition.js +0 -11
- package/dist/plugins/attributes/viewTransition.js.map +0 -7
- package/dist/plugins/directives/transition-name.d.ts +0 -1
- package/dist/plugins/directives/transition-name.js +0 -11
- package/dist/plugins/directives/transition-name.js.map +0 -7
- package/dist/plugins/directives/trap.d.ts +0 -5
- package/dist/plugins/directives/trap.js +0 -48
- package/dist/plugins/directives/trap.js.map +0 -7
- package/dist/sigmx.auto.js +0 -5
- package/dist/utils/object.d.ts +0 -9
- package/dist/utils/object.js +0 -26
- package/dist/utils/object.js.map +0 -7
- package/dist/utils/text.d.ts +0 -7
- package/dist/utils/text.js +0 -15
- package/dist/utils/text.js.map +0 -7
- package/dist/utils/timing.d.ts +0 -14
- package/dist/utils/timing.js +0 -58
- package/dist/utils/timing.js.map +0 -7
package/README.md
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
# sigmx
|
|
2
2
|
|
|
3
|
+
[](https://github.com/wrux/sigmx/actions/workflows/ci.yml)
|
|
4
|
+
|
|
3
5
|
**htmx on steroids, in a runtime smaller than htmx.**
|
|
4
6
|
|
|
5
7
|
> **Status: an experiment.** sigmx is a personal exploration of what a hypermedia library looks like when it starts from a tiny, plugin-based core. It is inspired by [htmx](https://htmx.org) (the server sends HTML, the browser swaps it in), [Alpine.js](https://alpinejs.dev) (declarative `data-*` attributes with a little reactivity) and [Datastar](https://data-star.dev) (which fused the two and whose attribute syntax sigmx keeps for compatibility). The code is original, the APIs may still change, and it has not been used in production. Try it, break it, and open an issue with what you find.
|
|
6
8
|
|
|
7
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.
|
|
8
10
|
|
|
9
|
-
- **Tiny.** 4.
|
|
11
|
+
- **Tiny.** 4.5 KB core, 9.0 KB with the essentials, 11.5 KB with all 50 plugins (brotli). Zero runtime dependencies.
|
|
10
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.
|
|
11
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.
|
|
12
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.
|
|
13
|
-
- **Batteries from the ecosystem.** The Alpine plugins (`collapse`, `mask`, `
|
|
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.
|
|
14
16
|
- **Datastar-compatible markup.** Existing Datastar templates run unchanged.
|
|
15
17
|
|
|
16
18
|
```html
|
|
17
19
|
<div data-signals="{ count: 0, name: '' }">
|
|
18
|
-
<input data-bind:name placeholder="Your name"
|
|
20
|
+
<input data-bind:name placeholder="Your name" />
|
|
19
21
|
<p data-show="$name">Hello, <span data-text="$name"></span>!</p>
|
|
20
22
|
<button data-on:click="$count++">Clicked <b data-text="$count"></b> times</button>
|
|
21
23
|
<button data-on:click="@post('/save')" data-indicator:saving data-attr:disabled="$saving">Save</button>
|
|
@@ -29,10 +31,10 @@ npm install sigmx
|
|
|
29
31
|
```
|
|
30
32
|
|
|
31
33
|
```ts
|
|
32
|
-
import { createSigmx } from 'sigmx'
|
|
33
|
-
import { essentials } from 'sigmx/presets/essentials'
|
|
34
|
+
import { createSigmx } from 'sigmx';
|
|
35
|
+
import { essentials } from 'sigmx/presets/essentials';
|
|
34
36
|
|
|
35
|
-
createSigmx({ plugins: essentials })
|
|
37
|
+
createSigmx({ plugins: essentials });
|
|
36
38
|
```
|
|
37
39
|
|
|
38
40
|
Or pick plugins by hand (`sigmx/plugins` is tree-shakeable), use `sigmx/presets/minimal` or `sigmx/presets/all`, let [auto mode](#auto-mode) choose, or drop in the script-tag build:
|
|
@@ -41,7 +43,7 @@ Or pick plugins by hand (`sigmx/plugins` is tree-shakeable), use `sigmx/presets/
|
|
|
41
43
|
<script type="module" src="/sigmx.standalone.js"></script>
|
|
42
44
|
```
|
|
43
45
|
|
|
44
|
-
Astro users: `npm install sigmx sigmx
|
|
46
|
+
Astro users: `npm install sigmx @sigmx/astro` and add `sigmx()` to the integrations; see [sdks/astro](sdks/astro). Hono users: `npm install sigmx @sigmx/hono`, then `app.use(sigmx())` gives every handler `c.var.sigmx`; see [sdks/hono](sdks/hono). Everyone else: `sigmx/server` has the same event builders, responses, streams and signal reading as plain Fetch-API code.
|
|
45
47
|
|
|
46
48
|
## How it works
|
|
47
49
|
|
|
@@ -50,17 +52,17 @@ Attributes are `data-<plugin>[:key][__modifier.arg]="expression"`. Expressions a
|
|
|
50
52
|
```html
|
|
51
53
|
<button data-on:click__debounce.300ms="@get('/search')">Search</button>
|
|
52
54
|
<li data-class:active="$page === 3"></li>
|
|
53
|
-
<input data-bind:user.email
|
|
54
|
-
<p data-text="`${$items.length} items, ${@intl('
|
|
55
|
+
<input data-bind:user.email />
|
|
56
|
+
<p data-text="`${$items.length} items, ${@intl('NumberFormat', $total, { style: 'currency', currency: 'EUR' })}`"></p>
|
|
55
57
|
```
|
|
56
58
|
|
|
57
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:
|
|
58
60
|
|
|
59
|
-
| the server returns
|
|
60
|
-
|
|
61
|
-
| `text/html`
|
|
62
|
-
| `application/json`
|
|
63
|
-
| `text/event-stream` | applies each event as it arrives: `patch-elements`, `patch-signals`, or your own handlers
|
|
61
|
+
| the server returns | sigmx does |
|
|
62
|
+
| ------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
63
|
+
| `text/html` | morphs it into the page: top-level elements by `id`, or a target from the `sigmx-selector` and `sigmx-mode` headers |
|
|
64
|
+
| `application/json` | merges it into the signals |
|
|
65
|
+
| `text/event-stream` | applies each event as it arrives: `patch-elements`, `patch-signals`, or your own handlers |
|
|
64
66
|
|
|
65
67
|
The everyday case is the first row: render a partial with whatever templating you already use and return it. The morph keeps every element whose id appears on both sides, so a re-rendered form keeps what the user typed. Streams are there for progress, chat and dashboards, where one request should keep delivering:
|
|
66
68
|
|
|
@@ -76,14 +78,14 @@ data: elements <li>Validating…</li>
|
|
|
76
78
|
|
|
77
79
|
## Plugins
|
|
78
80
|
|
|
79
|
-
| kind
|
|
80
|
-
|
|
81
|
-
| state
|
|
82
|
-
| rendering | `text` `html` `show` `class` `style` `attr` `animate` `transition` `collapse` `
|
|
83
|
-
| forms
|
|
84
|
-
| events
|
|
85
|
-
| layout
|
|
86
|
-
| server
|
|
81
|
+
| kind | plugins |
|
|
82
|
+
| --------- | -------------------------------------------------------------------------------------------------------------- |
|
|
83
|
+
| state | `signals` `computed` `effect` `ref` `persist` `query-string` |
|
|
84
|
+
| rendering | `text` `html` `show` `class` `style` `attr` `animate` `transition` `collapse` `cloak` |
|
|
85
|
+
| forms | `bind` `custom-validity` `mask` |
|
|
86
|
+
| events | `on` `init` `on-interval` `on-intersect` `on-resize` `on-raf` `on-signal-patch` `match-media` |
|
|
87
|
+
| layout | `teleport` `scroll-into-view` `remove-me` |
|
|
88
|
+
| server | `@get` `@post` `@put` `@patch` `@delete` `@ws` `boost` `indicator` `patch-elements` `patch-signals` |
|
|
87
89
|
| utilities | `@peek` `@setAll` `@toggleAll` `@fit` `@clipboard` `@intl` `@dispatch` `@confirm` `json-signals` `replace-url` |
|
|
88
90
|
|
|
89
91
|
Every plugin has a page in the docs with a live example and its measured size.
|
|
@@ -92,16 +94,16 @@ Every plugin has a page in the docs with a live example and its measured size.
|
|
|
92
94
|
|
|
93
95
|
Brotli, measured from the real source by `npm run size` and `npm run compare` (nothing is quoted from memory):
|
|
94
96
|
|
|
95
|
-
|
|
|
96
|
-
|
|
97
|
-
| sigmx core
|
|
98
|
-
| sigmx essentials (state, rendering, forms, requests, morph) |
|
|
99
|
-
| sigmx everything,
|
|
100
|
-
| htmx 2.0.10
|
|
101
|
-
| Datastar 1.0.3
|
|
102
|
-
| Alpine.js 3.17.1
|
|
97
|
+
| | brotli |
|
|
98
|
+
| ----------------------------------------------------------- | ------: |
|
|
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
|
+
| htmx 2.0.10 | 14.6 KB |
|
|
103
|
+
| Datastar 1.0.3 (23 plugins) | 11.8 KB |
|
|
104
|
+
| Alpine.js 3.17.1 | 17.6 KB |
|
|
103
105
|
|
|
104
|
-
Precompiling expressions at build time removes the runtime compiler and any use of `new Function`, taking the core to 3.
|
|
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.
|
|
105
107
|
|
|
106
108
|
## Auto mode
|
|
107
109
|
|
|
@@ -109,14 +111,14 @@ Like a CSS framework scanning for class names, sigmx can scan your source for th
|
|
|
109
111
|
|
|
110
112
|
```js
|
|
111
113
|
// vite.config.js
|
|
112
|
-
import { sigmxAuto } from 'sigmx/vite'
|
|
113
|
-
export default { plugins: [sigmxAuto({ always: ['signals'], custom: { upper: 'src/plugins/upper.ts' } })] }
|
|
114
|
+
import { sigmxAuto } from 'sigmx/vite';
|
|
115
|
+
export default { plugins: [sigmxAuto({ always: ['signals'], custom: { upper: 'src/plugins/upper.ts' } })] };
|
|
114
116
|
```
|
|
115
117
|
|
|
116
118
|
```ts
|
|
117
|
-
import { createSigmx } from 'sigmx'
|
|
118
|
-
import { plugins } from 'virtual:sigmx-plugins'
|
|
119
|
-
createSigmx({ plugins })
|
|
119
|
+
import { createSigmx } from 'sigmx';
|
|
120
|
+
import { plugins } from 'virtual:sigmx-plugins';
|
|
121
|
+
createSigmx({ plugins });
|
|
120
122
|
```
|
|
121
123
|
|
|
122
124
|
Without Vite: `npx sigmx scan src --out src/sigmx-plugins.js`. In Astro: `sigmx({ plugins: 'auto' })`.
|
|
@@ -124,15 +126,17 @@ Without Vite: `npx sigmx scan src --out src/sigmx-plugins.js`. In Astro: `sigmx(
|
|
|
124
126
|
## Writing a plugin
|
|
125
127
|
|
|
126
128
|
```ts
|
|
127
|
-
import { attribute } from 'sigmx'
|
|
129
|
+
import { attribute } from 'sigmx';
|
|
128
130
|
|
|
129
131
|
export const upper = attribute({
|
|
130
132
|
name: 'upper',
|
|
131
133
|
value: 'required',
|
|
132
134
|
mount({ el, evaluate, effect }) {
|
|
133
|
-
effect(() => {
|
|
135
|
+
effect(() => {
|
|
136
|
+
el.textContent = String(evaluate()).toUpperCase();
|
|
137
|
+
});
|
|
134
138
|
},
|
|
135
|
-
})
|
|
139
|
+
});
|
|
136
140
|
```
|
|
137
141
|
|
|
138
142
|
Plugins are plain objects; nothing registers on import. The mount context gives you `evaluate`, `effect`, `listen`, `cleanup`, the store and the runtime, and everything registered through it is torn down when the attribute or element goes away. Functions (`@name()`) and server-event handlers work the same way with `action()` and `handler()`.
|
|
@@ -149,24 +153,31 @@ cd website && npm install && npm run dev
|
|
|
149
153
|
|
|
150
154
|
```
|
|
151
155
|
src/kernel/ reactive graph, store, expression compiler, runtime (DOM-free core)
|
|
156
|
+
src/server.ts sigmx/server: event builders, sse/sseStream/html/json responses, readSignals
|
|
152
157
|
src/plugins/ directives, functions, server-event handlers, the morph
|
|
153
158
|
src/presets/ minimal, essentials, all
|
|
154
|
-
sdks/astro/ sigmx
|
|
159
|
+
sdks/astro/ @sigmx/astro: integration, server helpers, auto mode, precompilation
|
|
160
|
+
sdks/hono/ @sigmx/hono: middleware with signals(), html(), events(), stream(); serveClient()
|
|
161
|
+
examples/ standalone Vite, Express, Hono and Astro projects using the published packages
|
|
155
162
|
website/ documentation site
|
|
156
163
|
scripts/ build, size and comparison measurements, dev server
|
|
157
|
-
tests/ node:test
|
|
164
|
+
tests/ unit (node:test), dom (happy-dom) and browser (Playwright) suites
|
|
158
165
|
```
|
|
159
166
|
|
|
160
167
|
```bash
|
|
161
168
|
npm install
|
|
162
169
|
npm run check # type-check
|
|
163
|
-
npm
|
|
170
|
+
npm run lint # biome: formatting and lint rules
|
|
171
|
+
npm run format # biome: rewrite files in the house style
|
|
172
|
+
npm test # build, then unit tests and happy-dom tests for every plugin
|
|
173
|
+
npm run test:browser # Playwright: transitions, observers, WebSocket, the standalone build
|
|
174
|
+
npm run test:all # everything above plus the SDK suites
|
|
164
175
|
npm run dev # dev server with SSE and WebSocket endpoints; open /tests/browser/index.html
|
|
165
176
|
npm run size # bundle sizes for representative plugin sets
|
|
166
177
|
npm run compare # sizes of htmx, Datastar and Alpine at pinned versions
|
|
167
178
|
```
|
|
168
179
|
|
|
169
|
-
Contributions are welcome. Open an issue first for anything larger than a fix, keep plugins as small as their reference-page size table suggests, and run both test suites before a pull request.
|
|
180
|
+
Contributions are welcome. Open an issue first for anything larger than a fix, keep plugins as small as their reference-page size table suggests, and run `npm run lint` and both test suites before a pull request.
|
|
170
181
|
|
|
171
182
|
## Licence
|
|
172
183
|
|
package/dist/cli.js
CHANGED
|
@@ -9,7 +9,9 @@ const opt = (name) => {
|
|
|
9
9
|
return i < 0 ? void 0 : args.splice(i, 2)[1];
|
|
10
10
|
};
|
|
11
11
|
if (cmd !== "scan") {
|
|
12
|
-
console.log(
|
|
12
|
+
console.log(
|
|
13
|
+
"usage: sigmx scan [dirs...] [--out src/sigmx-plugins.js] [--always signals,httpGet] [--custom upper=src/plugins/upper.ts] [--prefix data-,hx-]"
|
|
14
|
+
);
|
|
13
15
|
process.exit(cmd ? 1 : 0);
|
|
14
16
|
}
|
|
15
17
|
const out = opt("out");
|
package/dist/cli.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/cli.ts"],
|
|
4
|
-
"sourcesContent": ["#!/usr/bin/env node\n// `sigmx scan [dirs...] [--out file] [--always a,b] [--custom name=path,...] [--prefix data-,hx-]`\nimport { writeFileSync } from 'node:fs'
|
|
5
|
-
"mappings": ";AAEA,SAAS,qBAAqB;AAC9B,SAAS,6BAA6B;AACtC,SAAS,mBAAmB;AAE5B,MAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AACjC,MAAM,MAAM,KAAK,MAAM;AACvB,MAAM,MAAM,CAAC,SAAiB;AAC5B,QAAM,IAAI,KAAK,QAAQ,KAAK,IAAI,EAAE;AAClC,SAAO,IAAI,IAAI,SAAY,KAAK,OAAO,GAAG,CAAC,EAAE,CAAC;AAChD;AACA,IAAI,QAAQ,QAAQ;AAClB,UAAQ,
|
|
4
|
+
"sourcesContent": ["#!/usr/bin/env node\n// `sigmx scan [dirs...] [--out file] [--always a,b] [--custom name=path,...] [--prefix data-,hx-]`\nimport { writeFileSync } from 'node:fs';\nimport { generatePluginsModule } from './kernel/scan.js';\nimport { scanProject } from './vite.js';\n\nconst args = process.argv.slice(2);\nconst cmd = args.shift();\nconst opt = (name: string) => {\n const i = args.indexOf(`--${name}`);\n return i < 0 ? undefined : args.splice(i, 2)[1];\n};\nif (cmd !== 'scan') {\n console.log(\n 'usage: sigmx scan [dirs...] [--out src/sigmx-plugins.js] [--always signals,httpGet] [--custom upper=src/plugins/upper.ts] [--prefix data-,hx-]',\n );\n process.exit(cmd ? 1 : 0);\n}\nconst out = opt('out');\nconst always = opt('always')?.split(',').filter(Boolean);\nconst prefixes = opt('prefix')?.split(',').filter(Boolean);\nconst custom = Object.fromEntries((opt('custom')?.split(',') ?? []).map((pair) => pair.split('=') as [string, string]));\nconst sel = scanProject({ include: args.length ? args : undefined, always, prefixes, custom });\nconst code = generatePluginsModule(sel);\nif (out) {\n writeFileSync(out, code);\n console.log(`wrote ${out}: ${sel.plugins.length} plugins, ${sel.unused.length} unused`);\n} else process.stdout.write(code);\nfor (const [exp, why] of Object.entries(sel.reasons)) console.error(` ${exp.padEnd(16)} ${why}`);\n"],
|
|
5
|
+
"mappings": ";AAEA,SAAS,qBAAqB;AAC9B,SAAS,6BAA6B;AACtC,SAAS,mBAAmB;AAE5B,MAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AACjC,MAAM,MAAM,KAAK,MAAM;AACvB,MAAM,MAAM,CAAC,SAAiB;AAC5B,QAAM,IAAI,KAAK,QAAQ,KAAK,IAAI,EAAE;AAClC,SAAO,IAAI,IAAI,SAAY,KAAK,OAAO,GAAG,CAAC,EAAE,CAAC;AAChD;AACA,IAAI,QAAQ,QAAQ;AAClB,UAAQ;AAAA,IACN;AAAA,EACF;AACA,UAAQ,KAAK,MAAM,IAAI,CAAC;AAC1B;AACA,MAAM,MAAM,IAAI,KAAK;AACrB,MAAM,SAAS,IAAI,QAAQ,GAAG,MAAM,GAAG,EAAE,OAAO,OAAO;AACvD,MAAM,WAAW,IAAI,QAAQ,GAAG,MAAM,GAAG,EAAE,OAAO,OAAO;AACzD,MAAM,SAAS,OAAO,aAAa,IAAI,QAAQ,GAAG,MAAM,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,KAAK,MAAM,GAAG,CAAqB,CAAC;AACtH,MAAM,MAAM,YAAY,EAAE,SAAS,KAAK,SAAS,OAAO,QAAW,QAAQ,UAAU,OAAO,CAAC;AAC7F,MAAM,OAAO,sBAAsB,GAAG;AACtC,IAAI,KAAK;AACP,gBAAc,KAAK,IAAI;AACvB,UAAQ,IAAI,SAAS,GAAG,KAAK,IAAI,QAAQ,MAAM,aAAa,IAAI,OAAO,MAAM,SAAS;AACxF,MAAO,SAAQ,OAAO,MAAM,IAAI;AAChC,WAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,IAAI,OAAO,EAAG,SAAQ,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC,IAAI,GAAG,EAAE;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/kernel/compile.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
6
|
-
export declare const
|
|
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
|
|
11
|
-
*
|
|
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
|
|
15
|
-
/**
|
|
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;
|
package/dist/kernel/compile.js
CHANGED
|
@@ -1,96 +1,131 @@
|
|
|
1
1
|
const functionCompiler = (params, body) => Function(...params, body);
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
|
|
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
|
|
10
|
-
if (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
|
-
|
|
17
|
+
out += src.slice(i, j + 1);
|
|
14
18
|
i = j + 1;
|
|
15
|
-
} else if (c === "
|
|
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 === "/" &&
|
|
37
|
+
} else if (c === "/" && d === "*") {
|
|
19
38
|
const j = src.indexOf("*/", i + 2);
|
|
20
39
|
i = j < 0 ? n : j + 2;
|
|
21
|
-
} else {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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 <
|
|
70
|
+
while (j < n && IDENT.test(src[j])) j++;
|
|
33
71
|
let k = j;
|
|
34
|
-
while (k <
|
|
35
|
-
if (
|
|
72
|
+
while (k < n && /\s/.test(src[k])) k++;
|
|
73
|
+
if (src[k] === "(") {
|
|
36
74
|
out += `__a.${src.slice(i + 1, j)}(`;
|
|
37
|
-
|
|
75
|
+
i = k + 1;
|
|
76
|
+
} else {
|
|
77
|
+
out += c;
|
|
78
|
+
i++;
|
|
38
79
|
}
|
|
80
|
+
} else {
|
|
81
|
+
out += c;
|
|
82
|
+
i++;
|
|
39
83
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return out;
|
|
84
|
+
}
|
|
85
|
+
return [out, i];
|
|
43
86
|
};
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
|
61
|
-
|
|
62
|
-
const
|
|
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
|
-
|
|
73
|
-
)
|
|
74
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
5
|
-
"mappings": "
|
|
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
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
+
import type { CaseStyle } from '../lib/casing.js';
|
|
1
2
|
import type { Compiler } from './compile.js';
|
|
2
3
|
import type { Store } from './state.js';
|
|
3
|
-
import type { CaseStyle } from '../lib/casing.js';
|
|
4
4
|
export type El = HTMLElement | SVGElement | MathMLElement;
|
|
5
5
|
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[]
|
|
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[];
|
|
@@ -17,7 +17,6 @@ export interface Runtime {
|
|
|
17
17
|
readonly compiler: Compiler;
|
|
18
18
|
/** Dispatch a `sigmx-<type>` CustomEvent on `document`. */
|
|
19
19
|
emit(type: string, detail?: unknown): void;
|
|
20
|
-
/** Call a registered action by name. */
|
|
21
20
|
call(name: string, ctx: ActionCtx, args: any[]): any;
|
|
22
21
|
/** Event-name prefixes accepted from servers; the first is what sigmx itself uses. */
|
|
23
22
|
readonly eventPrefixes: readonly string[];
|
|
@@ -29,13 +28,10 @@ export interface Runtime {
|
|
|
29
28
|
}
|
|
30
29
|
export interface Ctx {
|
|
31
30
|
el: El;
|
|
32
|
-
/** Plugin name, e.g. 'on'. */
|
|
33
31
|
plugin: string;
|
|
34
32
|
/** Full attribute name as written, e.g. 'data-on:click__debounce.300ms'. */
|
|
35
33
|
attr: string;
|
|
36
|
-
/** The `:key` part, if any. */
|
|
37
34
|
key: string | undefined;
|
|
38
|
-
/** Attribute value (the expression source). */
|
|
39
35
|
value: string;
|
|
40
36
|
mods: Mods;
|
|
41
37
|
/** The key recased per `__case.<style>`, defaulting to `style`. */
|
|
@@ -46,8 +42,11 @@ export interface Ctx {
|
|
|
46
42
|
effect(fn: () => void): void;
|
|
47
43
|
/** addEventListener with automatic removal on cleanup. */
|
|
48
44
|
listen(target: EventTarget, type: string, fn: (e: any) => void, options?: AddEventListenerOptions): void;
|
|
49
|
-
|
|
45
|
+
/** Run `fn` when the attribute unmounts. Returns a function that unregisters it (for work that finished early). */
|
|
46
|
+
cleanup(fn: () => void): () => void;
|
|
50
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;
|
|
51
50
|
store: Store;
|
|
52
51
|
runtime: Runtime;
|
|
53
52
|
}
|
|
@@ -57,8 +56,10 @@ export interface ActionCtx {
|
|
|
57
56
|
store: Store;
|
|
58
57
|
runtime: Runtime;
|
|
59
58
|
error(message: string, extra?: Record<string, unknown>): Error;
|
|
60
|
-
/**
|
|
61
|
-
|
|
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;
|
|
62
63
|
}
|
|
63
64
|
export interface AttributePlugin {
|
|
64
65
|
type: 'attribute';
|
|
@@ -66,7 +67,8 @@ export interface AttributePlugin {
|
|
|
66
67
|
key?: 'required' | 'forbidden';
|
|
67
68
|
value?: 'required' | 'forbidden';
|
|
68
69
|
/** Whether the expression yields a value (default) or is a statement body. */
|
|
69
|
-
|
|
70
|
+
/** The value is a literal (a mask, a selector, a delay) rather than an expression; `__dynamic` makes it one. */
|
|
71
|
+
literal?: boolean;
|
|
70
72
|
/** Extra parameter names the expression can reference, in the order passed to `evaluate`. */
|
|
71
73
|
args?: string[];
|
|
72
74
|
mount(ctx: Ctx): void | (() => void);
|
|
@@ -76,7 +78,6 @@ export interface ActionPlugin {
|
|
|
76
78
|
name: string;
|
|
77
79
|
call(ctx: ActionCtx, ...args: any[]): any;
|
|
78
80
|
}
|
|
79
|
-
/** Handles a named server event (for example a streamed patch). */
|
|
80
81
|
export interface HandlerPlugin {
|
|
81
82
|
type: 'handler';
|
|
82
83
|
name: string;
|
|
@@ -93,7 +94,6 @@ export interface RuntimeOptions {
|
|
|
93
94
|
prefix?: string | string[];
|
|
94
95
|
/** Server event-name prefixes to accept, add a second prefix while migrating from another library. Default 'sigmx-'. */
|
|
95
96
|
eventPrefix?: string | string[];
|
|
96
|
-
/** Share a store between instances. */
|
|
97
97
|
store?: Store;
|
|
98
98
|
/** Called for any plugin or expression error. Default: console.error. */
|
|
99
99
|
onError?: (error: unknown, info: {
|
|
@@ -113,7 +113,6 @@ export interface Sigmx {
|
|
|
113
113
|
store: Store;
|
|
114
114
|
/** Root namespace proxy for use from JavaScript: `sigmx.$.count++`. */
|
|
115
115
|
$: any;
|
|
116
|
-
/** Mount plugins under `root` and observe it for changes. */
|
|
117
116
|
apply(root?: El | ShadowRoot, observe?: boolean): void;
|
|
118
117
|
/** Register more plugins; attribute plugins are applied to observed roots immediately. */
|
|
119
118
|
use(...plugins: Plugin[]): void;
|