@tschk/moonshine-svelte 0.0.0-stage → 0.4.4
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 +473 -2
- package/package.json +57 -4
- package/src/await.ts +123 -0
- package/src/binding.ts +115 -0
- package/src/class.ts +21 -0
- package/src/const-derived.ts +47 -0
- package/src/derived.ts +276 -0
- package/src/directives.ts +129 -0
- package/src/each.ts +306 -0
- package/src/effect.ts +243 -0
- package/src/framework.ts +1 -0
- package/src/history.ts +104 -0
- package/src/index.ts +55 -0
- package/src/mount-types.ts +46 -0
- package/src/mount.ts +100 -0
- package/src/pre.ts +671 -0
- package/src/private-class-state.ts +54 -0
- package/src/prop.ts +64 -0
- package/src/raw-snippet-dom.ts +214 -0
- package/src/raw-snippet-ssr.ts +75 -0
- package/src/raw-snippet.ts +36 -0
- package/src/raw-ssr-lifecycle.ts +8 -0
- package/src/raw-state.ts +19 -0
- package/src/runes.ts +11 -0
- package/src/server-mount.ts +31 -0
- package/src/server-prop.ts +29 -0
- package/src/server-store.ts +113 -0
- package/src/spread-props.ts +153 -0
- package/src/ssr.ts +50 -0
- package/src/state.ts +492 -0
- package/src/store-binding.ts +125 -0
- package/src/store-internal.ts +34 -0
- package/src/store-write.ts +38 -0
- package/src/store.ts +208 -0
- package/src/svelte.ts +152 -0
- package/src/tick-queue.ts +31 -0
package/README.md
CHANGED
|
@@ -1,3 +1,474 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@tschk/moonshine-svelte`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Independent stores, native component context/events, lifecycle helpers, and Moonshine signal bridges.
|
|
4
|
+
No Svelte runtime, React, WASM, or parser is required. The framework peer is
|
|
5
|
+
optional at runtime and loaded only when using the compatibility renderer.
|
|
6
|
+
|
|
7
|
+
Strict TypeScript checking of the root entry requires installing
|
|
8
|
+
`@tschk/moonshine-framework`, even when importing only a bridge: the root also
|
|
9
|
+
exports `crepusRenderer` with the framework's `Renderer` contract. The framework
|
|
10
|
+
remains optional for runtime imports and bridge-only browser/Bun/Node bundles.
|
|
11
|
+
This requirement does not apply to the signal-only `@tschk/moonshine` core entry.
|
|
12
|
+
|
|
13
|
+
| Subpath | Provides |
|
|
14
|
+
| ---------- | ------------------------------------------------------------------- |
|
|
15
|
+
| `.` | signal bridges and lazy native SSR compatibility renderer |
|
|
16
|
+
| `./svelte` | lifecycle, context, typed `createContext`, component event dispatch |
|
|
17
|
+
| `./class` | opt-in `classValue` normalization for source class expressions |
|
|
18
|
+
| `./store` | independent `writable`, `readable`, `derived`, and `get` |
|
|
19
|
+
|
|
20
|
+
`classValue` flattens nested class arrays and includes truthy object keys. Empty
|
|
21
|
+
results return `undefined`, omitting the DOM/SSR attribute. Top-level primitive
|
|
22
|
+
values retain Svelte 5.57 stringification; nested falsy values are omitted. This
|
|
23
|
+
helper does not change ordinary native `h()` attributes or implement class/style
|
|
24
|
+
directives.
|
|
25
|
+
|
|
26
|
+
The optional `/runes` entry exports compiler-facing `createState`, `createDerived`
|
|
27
|
+
and `createRuneEffect`.
|
|
28
|
+
|
|
29
|
+
Compiler-facing `/runes` also exports `spreadProps(...layers)` for live component
|
|
30
|
+
props. Layers are objects, nullish values, or object getters; later layers win.
|
|
31
|
+
Source getters keep their receiver and are read lazily. Layer getters are not
|
|
32
|
+
cached: the compiler must memoize call expressions where required. Nullish
|
|
33
|
+
layers are skipped during setter lookup, avoiding the pinned host's null-layer
|
|
34
|
+
assignment error. Pass component
|
|
35
|
+
children through the named props object to preserve source precedence.
|
|
36
|
+
|
|
37
|
+
The separate `/ssr` `spreadServerProps(layers)` copies enumerable own string-key
|
|
38
|
+
descriptors once per render pass. Getters remain lazy and read from the merged
|
|
39
|
+
object; symbols and inherited properties are omitted. DOM and SSR spread helpers
|
|
40
|
+
intentionally have different contracts. The native SFC gate verifies bounded component spread lowering, including
|
|
41
|
+
call-expression caching, live rest props, explicit children and component
|
|
42
|
+
references. Implicit body/legacy slots, element spreads and hydration remain
|
|
43
|
+
unverified.
|
|
44
|
+
|
|
45
|
+
Lifecycle helpers require a live native owner. `onMount` runs untracked in a
|
|
46
|
+
microtask after synchronous DOM insertion; a synchronous returned function is
|
|
47
|
+
owned cleanup. Async callbacks do not register returned promise values as
|
|
48
|
+
cleanup. Disposed roots skip pending mounts, including synchronous SSR roots.
|
|
49
|
+
`onDestroy` runs on owner disposal, including SSR. `tick()` yields, then commits
|
|
50
|
+
pending native rune work across the default effect and opt-in render queues.
|
|
51
|
+
Future promise/timer updates and hydration remain separate contracts.
|
|
52
|
+
|
|
53
|
+
Mount hooks share the post-DOM rune-effect queue, preserving registration order
|
|
54
|
+
across sibling components. By default, pending native component references publish before
|
|
55
|
+
mount/effect callbacks. Opaque exported instances preserve live getters and methods;
|
|
56
|
+
source fixtures now verify named/empty exports and scalar/member `bind:this` in Chromium and SSR. Other export forms and loop-dependent references remain separate acceptance work.
|
|
57
|
+
|
|
58
|
+
Stores implement the subscription protocol, but reads are not automatically
|
|
59
|
+
tracked by native DOM bindings. Subscribe into a native signal and unsubscribe
|
|
60
|
+
on destruction:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { createSignal } from "@tschk/moonshine-dom/reactive";
|
|
64
|
+
import { writable } from "@tschk/moonshine-svelte/store";
|
|
65
|
+
import { onDestroy } from "@tschk/moonshine-svelte/svelte";
|
|
66
|
+
|
|
67
|
+
// During native component initialization:
|
|
68
|
+
const count = writable(0);
|
|
69
|
+
const [value, setValue] = createSignal(0);
|
|
70
|
+
onDestroy(count.subscribe((next) => setValue(next)));
|
|
71
|
+
// Use value as a native DOM child accessor.
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Context and dispatch APIs require native function-component initialization through
|
|
75
|
+
`h(Component, props)` or `createComponent` from `@tschk/moonshine-dom/component`.
|
|
76
|
+
Each instance inherits its own context map; `getAllContexts()` returns that live
|
|
77
|
+
map. Mutating it changes subsequent context reads and child inheritance without
|
|
78
|
+
changing sibling or ancestor maps.
|
|
79
|
+
`createContext<T>()` returns typed get/set/has functions.
|
|
80
|
+
|
|
81
|
+
`createEventDispatcher` delivers non-bubbling `CustomEvent`s to the explicit
|
|
82
|
+
listener map on the `componentEvents` symbol from the component module. It
|
|
83
|
+
supports detail, listener arrays and cancellation. Callback props remain separate.
|
|
84
|
+
Disposed instances stop dispatching; listeners have no legacy exported-instance
|
|
85
|
+
`this`. This is a native compiler contract, not Svelte's internal component ABI.
|
|
86
|
+
|
|
87
|
+
This package does not execute existing Svelte compiler instructions or alias
|
|
88
|
+
`svelte`; existing output must be recompiled through Crepus. Authored top-level/imported `$store` reads, replacement and writable expressions have bounded DOM/SSR proof. The tested local compiler supports a bounded authored
|
|
89
|
+
SFC slice: scalar, shallow raw and ordinary deep `$state`, `$state.snapshot`, `$derived`,
|
|
90
|
+
post-DOM `$effect`, and live `$props()` (whole-object or destructured with rename,
|
|
91
|
+
defaults and read-only rest), click handlers, static/dynamic child components,
|
|
92
|
+
keyed blocks, `$effect.tracking()`, manual `$effect.root`, checkbox/value/range
|
|
93
|
+
bindings, named/empty component exports, scalar/member `bind:this` and named context helpers. Reachable plain
|
|
94
|
+
TypeScript context helpers use the native bridge too. Fixtures are verified in
|
|
95
|
+
Chromium and SSR, including skipped effect factories on the server.
|
|
96
|
+
Full source-compatibility remains unfinished; the acceptance surface is
|
|
97
|
+
tracked in [the SFC coverage ledger](../../docs/native-sfc-coverage.md).
|
|
98
|
+
|
|
99
|
+
The `/runes` helpers are compiler targets. `createState` isolates proxy writes
|
|
100
|
+
from ordinary object/array inputs and tracks deep mutations. Unusual property
|
|
101
|
+
descriptors reject explicitly. Reads capture raw properties lazily; proxy writes
|
|
102
|
+
remain isolated from the input. Own properties shadowing inherited names enumerate
|
|
103
|
+
consistently, intentionally differing from Svelte's edge behavior. Complete Svelte
|
|
104
|
+
proxy parity remains unfinished. `stateSnapshot` clones plain data and supported
|
|
105
|
+
platform values, handles `toJSON` and cycles, and retains uncloneable values.
|
|
106
|
+
Map/Set snapshots are shallow, matching Svelte production.
|
|
107
|
+
`createRawState` preserves object/function identity and tracks only replacement,
|
|
108
|
+
with Svelte's `===` equality and the same teardown-history policy as deep cells.
|
|
109
|
+
Nested mutation remains ordinary JavaScript; this helper does not create proxies.
|
|
110
|
+
`createRuneEffect` deduplicates post-DOM microtask effects, owns
|
|
111
|
+
teardown and nested work, and skips synchronous SSR, including bare renders and
|
|
112
|
+
retained foreign owners. It does not implement
|
|
113
|
+
the pre scheduler or asynchronous Svelte boundaries; `/pre` stays separate. `runeTracking()` reports whether
|
|
114
|
+
synchronous dependency collection is active: true inside native render accessors,
|
|
115
|
+
derived computations and rune effects; false during component setup, ordinary
|
|
116
|
+
event callbacks and `untrack`. The tracking source fixture verifies these contexts
|
|
117
|
+
in Chromium and SSR.
|
|
118
|
+
|
|
119
|
+
`createDerived` evaluates on demand and allows optimistic `.value` overrides.
|
|
120
|
+
Dependency invalidation resets an override even when the computed value is equal;
|
|
121
|
+
dynamic dependencies, errors and diamond updates preserve the kernel's pull graph.
|
|
122
|
+
Runtime helper tests do not establish compiler coverage for every rune form.
|
|
123
|
+
|
|
124
|
+
The source gate also admits imported `.svelte.ts` and `.svelte.js` factories with
|
|
125
|
+
function-local state, raw state and derived declarations. Typed and JavaScript
|
|
126
|
+
factories preserve independent instances, cached reads, live getters and SSR
|
|
127
|
+
request output. Top-level exported rune cells remain explicitly unsupported,
|
|
128
|
+
including declarations supplying types without a runtime implementation. Two
|
|
129
|
+
synchronous writes can still cause an extra native derived callback; the current
|
|
130
|
+
source fixture tests writes separately and does not establish full batching
|
|
131
|
+
parity.
|
|
132
|
+
|
|
133
|
+
`createProp` supplies writable compiler prop cells: ordinary writes stay local;
|
|
134
|
+
declared bindable props forward an actual parent setter. Defaults use a lazy,
|
|
135
|
+
reactive derived value, matching the Svelte 5.57.1 reference. A bound undefined prop
|
|
136
|
+
with a fallback rejects in the browser; SSR forwards a defined fallback instead.
|
|
137
|
+
This helper does not validate source binding declarations or implement `bind:this`.
|
|
138
|
+
Its SSR forwarding is not Svelte `$bindable` parity. Source SSR lowering must use
|
|
139
|
+
the optional `/ssr` local prop cells and buffered retries with one-time setup and
|
|
140
|
+
lifecycle callbacks retained only from the accepted render pass.
|
|
141
|
+
|
|
142
|
+
The root `crepusRenderer` is a lazy native SSR compatibility renderer. Pages
|
|
143
|
+
must explicitly use `@tschk/moonshine-dom/ssr`. Source-path loading is supported
|
|
144
|
+
under Bun; bundled deployments use
|
|
145
|
+
`@tschk/moonshine-dom/framework-native` with their bundled module table.
|
|
146
|
+
|
|
147
|
+
The optional `/binding` entry wraps native single-select and radio bindings with
|
|
148
|
+
state-origin comparison: a native state proxy and its original object compare
|
|
149
|
+
equal. Selected values are forwarded unchanged, preserving proxy overlays.
|
|
150
|
+
Multiple selects and checkbox groups retain array `includes` identity semantics.
|
|
151
|
+
Single selects match a typed `undefined` option before adopting browser defaults;
|
|
152
|
+
choosing a duplicate option retains that node when its model value is unchanged.
|
|
153
|
+
Checkbox binding and typed-value metadata helpers forward to native bindings. This entry does not add source binding
|
|
154
|
+
syntax support or recognize proxies created by an installed Svelte runtime.
|
|
155
|
+
|
|
156
|
+
`createRuneRoot` on `/runes` runs untracked setup synchronously and returns an
|
|
157
|
+
idempotent disposer. Its effects have a manual lifetime: enclosing component
|
|
158
|
+
unmounts and effect reruns do not dispose the root. Returned setup cleanup runs
|
|
159
|
+
when explicitly stopped, after owned effects. SSR component frames skip setup
|
|
160
|
+
entirely; server lowering must also erase source `$effect.root` callbacks.
|
|
161
|
+
Svelte `/binding` also maps empty numeric `bindValue` writes to `null`;
|
|
162
|
+
invalid nonempty numeric defaults reset to `NaN`, while text fields still write
|
|
163
|
+
empty strings. Generic native DOM bindings keep their
|
|
164
|
+
existing `undefined` numeric contract. Input type must remain fixed.
|
|
165
|
+
|
|
166
|
+
Teardown history is confined to this adapter. Within a pending flush, cleanup
|
|
167
|
+
reads the first previous values of compiler-created state and derived cells;
|
|
168
|
+
cleanup writes remain immediately readable. Derived historical reads keep a
|
|
169
|
+
separate cache, and synchronous native DOM consumers continue to read live values.
|
|
170
|
+
Proxy property history follows Svelte's lazy sources: previously unread properties
|
|
171
|
+
can read as undefined during cleanup, while key enumeration follows current structure.
|
|
172
|
+
External kernel getters without instrumented writers cannot reconstruct history.
|
|
173
|
+
SSR destruction reads live values. The source acceptance gate checks pending
|
|
174
|
+
teardown, derived values, manual stop, disposal and isolated SSR requests.
|
|
175
|
+
|
|
176
|
+
The optional `/ssr` entry exposes compiler-facing `createServerProp`, `afterRender`
|
|
177
|
+
and `settle` helpers for synchronous server bindings. Server prop assignments stay
|
|
178
|
+
local until successful serialization ends; only a currently undefined parent with
|
|
179
|
+
an own setter receives the final defined child value. `settle` buffers retryable
|
|
180
|
+
template output while setup stays outside the callback. Discarded attempts run
|
|
181
|
+
internal cleanup but suppress Svelte destroy callbacks, including nested accepted
|
|
182
|
+
attempts inside a discarded parent. Only accepted output updates select bookkeeping.
|
|
183
|
+
Runaway invalidation throws after 1,000 attempts. The native source gate now
|
|
184
|
+
verifies scalar component `bind:value` with a child `$bindable(3)` default:
|
|
185
|
+
two-way browser writes, retained nodes, independent instances, all four SSR
|
|
186
|
+
initial/assignment cases and accepted cleanup. Failed undefined browser setup
|
|
187
|
+
cleans the partial native parent owner, differing from the pinned reference.
|
|
188
|
+
The composite source fixtures also verify typed member targets, renamed child
|
|
189
|
+
bindable props, reactive fallback values without premature parent publication,
|
|
190
|
+
and binding precedence over spreads in either attribute order. Named component
|
|
191
|
+
getter/setter pairs also have source acceptance: boolean/string transformations,
|
|
192
|
+
captured member functions called without a receiver, and strict literal-union
|
|
193
|
+
types. Dynamic replacements retain their captured functions, set up the new
|
|
194
|
+
component before old cleanup, and keep both write directions live. Unlike ordinary bindings, SSR function bindings publish an undefined
|
|
195
|
+
getter's child fallback once without retrying setup. Browser undefined values
|
|
196
|
+
still reject when the child has a fallback. Other binding forms, asynchronous
|
|
197
|
+
rendering and hydration remain unverified.
|
|
198
|
+
|
|
199
|
+
The separate `/pre` entry exports `withRuneScheduler(callback)` and
|
|
200
|
+
`createRunePreEffect(callback)`. Enter scheduling under a live native owner:
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
import { h, render } from "@tschk/moonshine-dom/dom";
|
|
204
|
+
import { withRuneScheduler } from "@tschk/moonshine-svelte/pre";
|
|
205
|
+
|
|
206
|
+
const dispose = render(() => withRuneScheduler(() => h(App, {})), target);
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Within this tree, pre-effects run initially during setup. Later writes coalesce
|
|
210
|
+
into a microtask: parent pre-effects see current state and old DOM, structural
|
|
211
|
+
work removes hidden descendants before their queued callbacks, retained keyed
|
|
212
|
+
rows follow current index order, and DOM updates precede post-effects. References
|
|
213
|
+
publish before mounts/effects by default; opt-in `publication: "post"` on the
|
|
214
|
+
component reference can register after child mounts/effects and before ancestor
|
|
215
|
+
posts. Scheduled control branches dispose children before parent hooks while
|
|
216
|
+
retained rows survive updates. Child post callbacks precede ancestor callbacks.
|
|
217
|
+
Reference publication can invalidate pre-effects and text bindings; that work
|
|
218
|
+
waits until pending posts finish. Ordinary post-effect cleanup writes still
|
|
219
|
+
render before nested posts. Structural branches follow owner order, so an earlier
|
|
220
|
+
branch invalidated by a later branch waits for the next pass. Render/structural
|
|
221
|
+
job descendants dispose before their component's hooks; pre-effect shutdown
|
|
222
|
+
retains subscription-first error precedence.
|
|
223
|
+
Pure derived values remain pullable. Cleanup history spans the complete cycle.
|
|
224
|
+
Errors drain queued peers, ownership cancels disposed work, and repeated feedback
|
|
225
|
+
is limited per callback rather than per distinct job. Ownerless entry rejects;
|
|
226
|
+
Synchronous SSR bypasses new scheduling and suppresses pre callbacks, including
|
|
227
|
+
bare callbacks and foreign scopes.
|
|
228
|
+
|
|
229
|
+
The ordinary native renderer stays synchronous. `/runes` does not import this
|
|
230
|
+
scheduler. The authored pre-effect pair has bounded DOM/SSR source acceptance,
|
|
231
|
+
including skipped SSR factories and child-first destruction. Other pre-effect
|
|
232
|
+
variants, actions, attachments, async boundaries and hydration need separate
|
|
233
|
+
source acceptance. `tick()` also drains synchronous writes between these native
|
|
234
|
+
queues; this does not establish Svelte's asynchronous compiler semantics.
|
|
235
|
+
|
|
236
|
+
The separate `/directives` entry provides manual `installAction(element, invoke,
|
|
237
|
+
readParameter?)` and `installAttachment(element, readAttachment)` under a live
|
|
238
|
+
`withRuneScheduler` tree. Install them before the element's attribute/text jobs
|
|
239
|
+
(for native `h`, use its first `ref` prop). Factories run after insertion; action
|
|
240
|
+
updates keep their source position before later DOM jobs. Attachment identity
|
|
241
|
+
changes clean up before replacement, while retained callback dependencies rerun
|
|
242
|
+
after DOM updates. Final cleanup sees the element removed.
|
|
243
|
+
|
|
244
|
+
Action parameters observe deep native state, including lazy cyclic aliases;
|
|
245
|
+
merely reading another alias does not trigger an update. Traversal interests
|
|
246
|
+
are removed on rerun/disposal. SSR skips all directive thunks. Native cleanup
|
|
247
|
+
drains peers and reports the first failure, differing from Svelte's early abort.
|
|
248
|
+
These helpers do not establish `use:` or `{@attach}` source lowering, attachment
|
|
249
|
+
spread support, hydration or complete Svelte directive compatibility.
|
|
250
|
+
|
|
251
|
+
The optional `/await` entry exports `awaitBlock(read, { pending, then, catch })`.
|
|
252
|
+
Use it under a live `withRuneScheduler` owner; branch values and errors are
|
|
253
|
+
accessors. Promise completion commits within one microtask, ignores stale or
|
|
254
|
+
disposed generations, and preserves a retained branch's nodes and local state.
|
|
255
|
+
Replacement branches initialize before old cleanup. Plain values, including
|
|
256
|
+
`false` and `null`, select `then`; already resolved promises skip pending output.
|
|
257
|
+
|
|
258
|
+
Synchronous SSR reads once and emits pending output for promises, or `then` for
|
|
259
|
+
plain values. It does not await. Missing rejection branches use native owner
|
|
260
|
+
error handling; unhandled callback failures retain the original error in a
|
|
261
|
+
microtask. This helper does not establish authored `{#await}` lowering,
|
|
262
|
+
asynchronous SSR, hydration, transitions or complete Svelte compatibility.
|
|
263
|
+
|
|
264
|
+
The optional `/store-binding` entry exports `createStoreReference<T>()` for
|
|
265
|
+
compiler-owned component subscriptions. Create one per store variable under a
|
|
266
|
+
live native owner. Its `get(store)` subscribes lazily, caches repeated reads and
|
|
267
|
+
tracks later notifications, including identical object/function values.
|
|
268
|
+
`releaseIfChanged(next)` releases the old subscription immediately and returns
|
|
269
|
+
`next`, without eagerly subscribing to it. Call `finish()` at the end of instance
|
|
270
|
+
setup, after registering authored destruction hooks; this preserves cached store
|
|
271
|
+
reads during teardown and releases child subscriptions before parent hooks. A
|
|
272
|
+
failed-setup fallback still releases acquired subscriptions.
|
|
273
|
+
|
|
274
|
+
Both function and observable-object unsubscriptions retain their protocol and
|
|
275
|
+
receiver. Effect cleanup uses the existing previous-flush history. SSR caches
|
|
276
|
+
subscriptions within the request owner and releases them on disposal. Retained
|
|
277
|
+
reads after disposal use temporary subscriptions; callbacks emitted after
|
|
278
|
+
unsubscription are ignored. Compiled SSR instead selects the separate server
|
|
279
|
+
group described below. Native failures drain cleanup peers. The local Crepuscularity `dafde4ab` compiler verifies authored top-level and
|
|
280
|
+
imported `$store` reads plus simple `$state` store-variable replacement. It
|
|
281
|
+
rejects `$store` assignments and mutations. This compiler patch is applied locally in
|
|
282
|
+
Crepuscularity but is not released; legacy reactive statements, async SSR and hydration
|
|
283
|
+
remain unsupported.
|
|
284
|
+
|
|
285
|
+
`/ssr` also provides `createServerState(initial)` and
|
|
286
|
+
`createServerDerived(compute, cache?)` for server lowering. State is a plain box.
|
|
287
|
+
Derived caching defaults to the render context at creation; component lowering
|
|
288
|
+
passes `true` explicitly. Nullish overrides resume computation, while falsy and
|
|
289
|
+
function values remain literal overrides. This centralizes server runtime policy
|
|
290
|
+
in Moonshine. The verified compiler source gate checks these imports for
|
|
291
|
+
components and both JS and TS companion factories, including a companion sharing
|
|
292
|
+
its importing SFC's basename.
|
|
293
|
+
|
|
294
|
+
The verified local `dafde4ab` compiler installs the rune scheduler in generated DOM
|
|
295
|
+
components. Native handwritten rendering remains synchronous by default. The
|
|
296
|
+
same authored counter built by this compiler is 10,766 gzip bytes versus pinned
|
|
297
|
+
Svelte 5.57.1's 17,389; this is one workload, not a universal size or speed claim.
|
|
298
|
+
The optional adapter-event hook in the shared DOM factory adds 32 gzip bytes to
|
|
299
|
+
this workload; the signal kernel is unchanged.
|
|
300
|
+
|
|
301
|
+
The 340-module source gate includes a self-imported recursive component tree.
|
|
302
|
+
Conditional updates preserve local state, focus and context; removed descendants
|
|
303
|
+
stop before their stale props can be evaluated. Disposal/remounting and fresh
|
|
304
|
+
escaped SSR match the pinned reference. Broader component compatibility remains
|
|
305
|
+
tracked in the coverage ledger.
|
|
306
|
+
|
|
307
|
+
Named component snippets preserve lexical values, consumer arguments, inherited
|
|
308
|
+
context, retained focus/local state and child-first cleanup. Type-only `Snippet`
|
|
309
|
+
imports use Moonshine's host-free contract; both generated graphs pass strict
|
|
310
|
+
checking, with fresh escaped SSR verified.
|
|
311
|
+
|
|
312
|
+
Bare intrinsic attributes preserve empty strings, expression attributes retain
|
|
313
|
+
boolean values, and bare component props remain `true`. The source gate also
|
|
314
|
+
checks boolean controls, empty file values and quoted event expressions in DOM
|
|
315
|
+
and SSR. A bounded form-reset fixture also checks literal versus expression
|
|
316
|
+
defaults, bound text/number/checkbox models, user/programmatic writes, reset after
|
|
317
|
+
tick, independent instances, disposal and fresh SSR. Broader reset variants remain open.
|
|
318
|
+
|
|
319
|
+
Function-local TS and JS companion factories can contain `$effect`. Two factories in
|
|
320
|
+
each of two component instances match the reference post-DOM and cleanup traces;
|
|
321
|
+
SSR erases registration and callback evaluation. Same-basename SFC/JS companions
|
|
322
|
+
are supported through explicit compiler inventory mapping and collision-safe
|
|
323
|
+
component output names. The source gate checks strict TS graphs, emitted JS
|
|
324
|
+
syntax, host-free imports and fresh escaped requests.
|
|
325
|
+
|
|
326
|
+
Textarea source checks cover literal versus expression defaults, unchanged-output
|
|
327
|
+
dirty-edit retention, changed values and independent instances. Raw closing tags,
|
|
328
|
+
component-looking markup and script strings remain text. Literal CR/CRLF, named
|
|
329
|
+
entities, semicolonless ampersands and static/dynamic decoding differences match
|
|
330
|
+
pinned Svelte DOM and parsed SSR; fixtures declare UTF-8 explicitly. Ordinary
|
|
331
|
+
text fragment whitespace and programmatic raw-snippet ownership remain open.
|
|
332
|
+
|
|
333
|
+
The source gate also checks bounded ordinary whitespace, snippet boundaries,
|
|
334
|
+
NBSP edges and pre/textarea indentation against exact DOM and parsed SSR
|
|
335
|
+
references. Leading pre newlines before conditionals, keys, snippets, raw HTML and imported
|
|
336
|
+
components now have exact DOM/parsed SSR checks; SVG and nested snippet
|
|
337
|
+
whitespace remain separate work.
|
|
338
|
+
|
|
339
|
+
Private raw-snippet bridges are available on `/raw-snippet`, `/raw-snippet-dom`
|
|
340
|
+
and `/raw-snippet-ssr`. They separate source callable metadata, DOM getter cells
|
|
341
|
+
and SSR values, with explicit consumer ownership and cleanup. These entries
|
|
342
|
+
require compiler lowering; authored `createRawSnippet` remains outside the source
|
|
343
|
+
acceptance gate. Only one trimmed HTML element root is supported, without
|
|
344
|
+
hydration. See the coverage ledger for the exact runtime and reference checks.
|
|
345
|
+
|
|
346
|
+
The private DOM entry also exports `createRawSnippetSlot`. Lowering creates one
|
|
347
|
+
slot per `@render`, passes `slot.deferRemoved` to `mapArray`, wraps invocation in
|
|
348
|
+
`slot.render`, and returns `slot.commit(mapped())` for every output, including
|
|
349
|
+
ordinary and empty snippets. This commits replacement setup before disposing
|
|
350
|
+
the previous branch and drains canceled work when its owner is disposed.
|
|
351
|
+
|
|
352
|
+
The private raw insertion hook adds 30 gzip bytes to the authored counter;
|
|
353
|
+
the signal-only default bundle remains unchanged.
|
|
354
|
+
|
|
355
|
+
The source gate verifies textarea body/reset-default independence, exact reader
|
|
356
|
+
order and complete SSR defaultValue expression erasure. Coercion/error-order
|
|
357
|
+
variants remain separate reference work.
|
|
358
|
+
|
|
359
|
+
Textarea expression bodies also preserve dirty edits when a forced rerun returns
|
|
360
|
+
the same object, without repeating its conversion. The source gate checks
|
|
361
|
+
conversion failures, DOM Symbol assignment errors, reset/disposal, literal versus
|
|
362
|
+
expression newlines, independent instances and fresh escaped SSR. Mixed body
|
|
363
|
+
expressions and default-value conversion ordering remain separate work.
|
|
364
|
+
|
|
365
|
+
One-level object and fixed-array `$derived` patterns now have authored DOM/SSR
|
|
366
|
+
proof, including renames, lazy defaults, bounded iterator closing, typed tuples,
|
|
367
|
+
independent overrides and error recovery. Nested/rest/computed patterns still
|
|
368
|
+
reject. Imperatively read derived chains remain lazy after source writes.
|
|
369
|
+
|
|
370
|
+
`/ssr` exports `createServerStoreReferences()` for compiler-owned server groups.
|
|
371
|
+
Create per-variable refs through `group.create<T>()`; call `group.finish()` after
|
|
372
|
+
successful serialization and accepted settlement, before bindable prop commits.
|
|
373
|
+
Cleanup follows first-read order and preserves completed same-source caches for
|
|
374
|
+
destruction hooks. Failed/discarded attempts still release resources. Late/new
|
|
375
|
+
destruction reads are balanced one-shot subscriptions, and peer cleanup drains
|
|
376
|
+
after errors; these deliberately avoid reference leaks. The DOM `/store-binding`
|
|
377
|
+
helper retains its separate destruction-before-release contract.
|
|
378
|
+
|
|
379
|
+
Compiler-facing `/store-binding` exports `setStoreValue`, `mutateStoreValue`
|
|
380
|
+
and `updateStoreValue`; `/ssr` exports `setStoreValue`, `mutateServerStore`
|
|
381
|
+
and `updateStoreValue`. Lowering captures the writable source before evaluating
|
|
382
|
+
an assignment and preserves the expression result. DOM member writes and SSR
|
|
383
|
+
member writes deliberately have different read/notification ordering. These
|
|
384
|
+
helpers require compiler-owned evaluation order; they are not general mutation
|
|
385
|
+
utilities. The frozen writable-store compiler passes the 346-module source gate,
|
|
386
|
+
including direct/compound/logical/member writes and prefix/postfix updates.
|
|
387
|
+
Destructuring, delete and iteration write targets remain rejected.
|
|
388
|
+
|
|
389
|
+
Scheduled list cleanup follows current retained keyed row order, children before
|
|
390
|
+
parents, while deferred removed rows remain alive until commit or owner disposal.
|
|
391
|
+
Authored keyed and unkeyed `#each` blocks now pass real CLI source acceptance
|
|
392
|
+
with identifier and static nested object item bindings, live indices,
|
|
393
|
+
arrays/iterables/array-like/nullish inputs,
|
|
394
|
+
key visits interleaved with setup and physical movement, retained row state and
|
|
395
|
+
empty fallbacks. Repeated empty inputs retain the same fallback root and DOM.
|
|
396
|
+
SSR does not evaluate keys. Renamed aliases, nested member bindings and unbound
|
|
397
|
+
callback/tag receivers have minified browser and fresh SSR proof. Defaults,
|
|
398
|
+
rest/array patterns and direct item/index write targets still diagnose;
|
|
399
|
+
transitions and hydration remain open.
|
|
400
|
+
|
|
401
|
+
Plain modern/legacy module scripts preserve module-once execution and named
|
|
402
|
+
exports. Bounded module runes now support state/raw state, snapshots and private
|
|
403
|
+
derived values. Stable exported state is the actual value/proxy; reassigned
|
|
404
|
+
state exports and derived exports reject. Module state persists across SSR
|
|
405
|
+
requests; module derived reads recompute while component derived values cache.
|
|
406
|
+
Module async/generator factories preserve rune creation-time ownership and caching.
|
|
407
|
+
Instance scripts and companion factories keep their placement restrictions.
|
|
408
|
+
The permanent gate covers 784 modules using frozen combined compiler
|
|
409
|
+
`ae8c6f6f`,
|
|
410
|
+
including shared module-rune state, snapshots, export identity, lifetime and SSR
|
|
411
|
+
cache behavior, bounded action/attachment source behavior and eight SSR prop-order
|
|
412
|
+
cases plus finite Vue defaults, explicit Solid source and authored await blocks. All 778 emitted TypeScript modules pass both strict checkers,
|
|
413
|
+
with no type exceptions. Await alias `typeof` queries have bounded source proof,
|
|
414
|
+
including qualified queries, nested defaults and annotations; invalid value-as-type
|
|
415
|
+
uses still fail both strict checkers.
|
|
416
|
+
|
|
417
|
+
The optional `/each` compiler entry reuses native insertion ranges and owners.
|
|
418
|
+
Duplicate or unstable keys diagnose and provisional roots always clean up;
|
|
419
|
+
reference production failure leaks are deliberately not reproduced. Cleanup
|
|
420
|
+
continues through sibling errors. With frozen compiler `c2ae808e` and the
|
|
421
|
+
invalidation/demand runtime, the unchanged authored SFC counter measures
|
|
422
|
+
34,650 raw / 12,683 gzip9 / 11,482 Brotli11 bytes versus the production
|
|
423
|
+
reference's 46,617 / 17,344 / 15,628. Correct derived overrides and equal-value
|
|
424
|
+
effect suppression add 1,386 browser gzip bytes; SSR gzip stays at 4,361 bytes.
|
|
425
|
+
The signal-only core is 4,041 raw bytes, up 243 raw / 86 gzip bytes from the
|
|
426
|
+
previous runtime. Native full-row list updates are slower in the measured workload;
|
|
427
|
+
smaller output does not establish a speed advantage.
|
|
428
|
+
|
|
429
|
+
A derived value first read after its component stops computes once from current
|
|
430
|
+
inputs, then freezes without subscribing. Failed computations keep and rethrow
|
|
431
|
+
the original error; dependency writes and newly attached owned work still reject.
|
|
432
|
+
This preserves native error/override policies rather than claiming exact Svelte
|
|
433
|
+
behavior for those cases.
|
|
434
|
+
|
|
435
|
+
Authored styles containing only explicit `:global(selector)` rules or a
|
|
436
|
+
`:global { ... }` block now compile to inventoried external CSS assets. Browser
|
|
437
|
+
checks cover scriptless and stateful components, outside matching elements,
|
|
438
|
+
independent instances, retained input edits/focus and disposal. No runtime style
|
|
439
|
+
injector is used, and ordinary counter output stays byte-identical.
|
|
440
|
+
|
|
441
|
+
Bounded local rules now scope authored intrinsic selector participants, including
|
|
442
|
+
ordinary compounds/combinators and global prefixes/suffixes. Sixteen state
|
|
443
|
+
predicates and before/after pseudo elements retain scope across hover, focus,
|
|
444
|
+
checked and disabled changes; unrelated elements and opaque child/raw HTML
|
|
445
|
+
boundaries remain untouched. Generated CSS is external, with matching DOM/SSR
|
|
446
|
+
hashes. Prefix matching memoizes selector/node states to avoid exponential
|
|
447
|
+
ancestor-path enumeration.
|
|
448
|
+
|
|
449
|
+
Bounded first/last/only child and of-type selectors, `:empty`, and four nth
|
|
450
|
+
selectors are supported. Nth arguments remain unscoped, including foreign
|
|
451
|
+
siblings. Other functional selectors, at-rules/keyframes, complex global placement,
|
|
452
|
+
preprocessing, external references, escapes, spreads and uncertain relational
|
|
453
|
+
branch/slot cases remain unsupported. The optional compiler uses the MIT Raffia
|
|
454
|
+
parser; no browser dependency is added. Its safety guard rejects more than 256
|
|
455
|
+
opening delimiters, including those in literals/comments. Full scoped CSS and
|
|
456
|
+
unused-selector diagnostics remain open.
|
|
457
|
+
|
|
458
|
+
Public instance rune fields now compile through Crepus: state/raw/derived/by,
|
|
459
|
+
including typed and zero-argument state fields. Bounded source checks cover
|
|
460
|
+
inheritance, initialization order, nonenumerable DOM accessors, ordinary SSR
|
|
461
|
+
state fields, callable receivers, independent instances and disposal. Private
|
|
462
|
+
identifier-keyed instance state/raw/derived initializers also have browser/SSR
|
|
463
|
+
source proof. Full constructor coverage remains open, including JavaScript SFC computed members.
|
|
464
|
+
Static fields and cast-wrapped state assignments with top-level await/yield
|
|
465
|
+
remain unsupported; full class compatibility is unfinished.
|
|
466
|
+
|
|
467
|
+
The compiler-facing `/runes` entry also exports private-field state/raw cells.
|
|
468
|
+
They reuse native state/history, expose historical tracked `value` and live
|
|
469
|
+
untracked `peek()`, and deep `assign()` returns its resolved proxy before feedback
|
|
470
|
+
or disposal can replace the current value. These runtime contracts have focused
|
|
471
|
+
behavior/type tests and bounded private-class source acceptance in the shared
|
|
472
|
+
shared source gate.
|
|
473
|
+
|
|
474
|
+
The optional `/mount` entry freshly mounts compiler-marked native SFC factories and returns their synchronous typed exports. It supports owned teardown, target-local anchors and copied root contexts; `/server-mount` rejects client mounting on the server. Hydration, transitions/outros, legacy event options and error transforms remain unsupported and reject explicitly. Both boolean `intro` values are accepted for sources without transitions. The erased `ComponentView<View, Exports, Props>` return metadata and `ComponentFactory<Implementation>` callable type preserve required/optional props and export types. Generated public functions must also preserve ESM hoisting. Public mounting checks metadata props rather than an implementation parameter typed `any`. The standalone `scripts/check-native-svelte-mount.ts --compiler <path>` source check verifies 28 generated modules with frozen compiler `703ceb65`: unchanged authored launchers, typed synchronous exports, two independent instances, anchor/context/cleanup behavior, DOM-free server imports and real Bun/Node ESM cycles. The combined await/source gate currently uses a separate compiler snapshot; merging those compiler lineages remains open. Declaration emission for setup-local interfaces is also unfinished.
|
package/package.json
CHANGED
|
@@ -1,6 +1,59 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tschk/moonshine-svelte",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.4.4",
|
|
4
|
+
"description": "Independent Svelte store and native lifecycle APIs with Moonshine signal bridges",
|
|
5
|
+
"license": "ISC",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./src/index.ts",
|
|
8
|
+
"types": "./src/index.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./src/index.ts",
|
|
11
|
+
"./svelte": "./src/svelte.ts",
|
|
12
|
+
"./store": "./src/store.ts",
|
|
13
|
+
"./runes": "./src/runes.ts",
|
|
14
|
+
"./binding": "./src/binding.ts",
|
|
15
|
+
"./class": "./src/class.ts",
|
|
16
|
+
"./ssr": "./src/ssr.ts",
|
|
17
|
+
"./pre": "./src/pre.ts",
|
|
18
|
+
"./directives": "./src/directives.ts",
|
|
19
|
+
"./await": "./src/await.ts",
|
|
20
|
+
"./store-binding": "./src/store-binding.ts",
|
|
21
|
+
"./raw-snippet": "./src/raw-snippet.ts",
|
|
22
|
+
"./raw-snippet-dom": "./src/raw-snippet-dom.ts",
|
|
23
|
+
"./raw-snippet-ssr": "./src/raw-snippet-ssr.ts",
|
|
24
|
+
"./each": "./src/each.ts",
|
|
25
|
+
"./mount": "./src/mount.ts",
|
|
26
|
+
"./server-mount": "./src/server-mount.ts",
|
|
27
|
+
"./const": "./src/const-derived.ts"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@tschk/moonshine": "^0.4.4",
|
|
31
|
+
"@tschk/moonshine-dom": "^0.4.4"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"typescript": "7.0.2"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"@tschk/moonshine-framework": "^0.4.4"
|
|
38
|
+
},
|
|
39
|
+
"peerDependenciesMeta": {
|
|
40
|
+
"@tschk/moonshine-framework": {
|
|
41
|
+
"optional": true
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"src",
|
|
46
|
+
"README.md"
|
|
47
|
+
],
|
|
48
|
+
"keywords": [
|
|
49
|
+
"moonshine",
|
|
50
|
+
"svelte",
|
|
51
|
+
"adapter"
|
|
52
|
+
],
|
|
53
|
+
"scripts": {
|
|
54
|
+
"format": "bunx prettier --check .",
|
|
55
|
+
"lint": "bunx oxlint .",
|
|
56
|
+
"typecheck": "bunx tsc --noEmit",
|
|
57
|
+
"test": "bun test"
|
|
58
|
+
}
|
|
59
|
+
}
|
package/src/await.ts
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/** Independent await-block control; no async SSR, hydration, or transitions. */
|
|
2
|
+
import {
|
|
3
|
+
createComponentScope,
|
|
4
|
+
createRenderEffect,
|
|
5
|
+
createSignal,
|
|
6
|
+
getOwner,
|
|
7
|
+
getRenderScheduler,
|
|
8
|
+
onCleanup,
|
|
9
|
+
runWithOwner,
|
|
10
|
+
untrack,
|
|
11
|
+
type Accessor,
|
|
12
|
+
} from "@tschk/moonshine-dom/reactive";
|
|
13
|
+
import { mapArray } from "@tschk/moonshine-dom/control";
|
|
14
|
+
import { isSSRRendering } from "@tschk/moonshine-dom/ssr-owner";
|
|
15
|
+
import { peekComponentContext } from "@tschk/moonshine-dom/component";
|
|
16
|
+
import { flushRuneWork } from "./pre";
|
|
17
|
+
/** Compiler-facing awaited source type; erased from runtime output. */
|
|
18
|
+
export type AwaitValue<Read extends () => unknown> = Awaited<ReturnType<Read>>;
|
|
19
|
+
export type AwaitInput<T> = T | PromiseLike<T>;
|
|
20
|
+
export type AwaitBranches<T, R> = {
|
|
21
|
+
pending?: () => R;
|
|
22
|
+
then?: (value: Accessor<T>) => R;
|
|
23
|
+
catch?: (error: Accessor<unknown>) => R;
|
|
24
|
+
};
|
|
25
|
+
function promise(value: unknown): value is PromiseLike<unknown> {
|
|
26
|
+
return (
|
|
27
|
+
value != null &&
|
|
28
|
+
(typeof value === "object" || typeof value === "function") &&
|
|
29
|
+
typeof (value as PromiseLike<unknown>).then === "function"
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
export function awaitBlock<T, R>(
|
|
33
|
+
read: () => T | PromiseLike<T>,
|
|
34
|
+
branches: AwaitBranches<T, R>,
|
|
35
|
+
schedule?: (read: () => (R | undefined)[]) => Accessor<(R | undefined)[]>,
|
|
36
|
+
): Accessor<(R | undefined)[]> {
|
|
37
|
+
if (isSSRRendering() || peekComponentContext()?.mode === "ssr") {
|
|
38
|
+
const input = read();
|
|
39
|
+
let child: R | undefined;
|
|
40
|
+
if (promise(input)) {
|
|
41
|
+
input.then(null, () => {});
|
|
42
|
+
child = branches.pending?.();
|
|
43
|
+
} else child = branches.then?.(() => input);
|
|
44
|
+
const result = child === undefined ? [] : [child];
|
|
45
|
+
return () => result;
|
|
46
|
+
}
|
|
47
|
+
const outer = getOwner();
|
|
48
|
+
if (!outer || outer.disposed || !getRenderScheduler())
|
|
49
|
+
throw Error("Await blocks require a live owner and withRuneScheduler");
|
|
50
|
+
return createComponentScope(() => {
|
|
51
|
+
const owner = getOwner()!;
|
|
52
|
+
let starting = true;
|
|
53
|
+
let generation = 0,
|
|
54
|
+
disposed = false;
|
|
55
|
+
const [state, setState] = createSignal<
|
|
56
|
+
"pending" | "then" | "catch" | undefined
|
|
57
|
+
>(undefined);
|
|
58
|
+
const [value, setValue] = createSignal<T | undefined>(undefined),
|
|
59
|
+
[error, setError] = createSignal<unknown>(undefined);
|
|
60
|
+
onCleanup(() => {
|
|
61
|
+
disposed = true;
|
|
62
|
+
generation++;
|
|
63
|
+
});
|
|
64
|
+
function alive(token: number) {
|
|
65
|
+
return !disposed && !owner.disposed && generation === token;
|
|
66
|
+
}
|
|
67
|
+
createRenderEffect(() => {
|
|
68
|
+
const token = ++generation;
|
|
69
|
+
const input = read();
|
|
70
|
+
if (!promise(input)) {
|
|
71
|
+
setValue(() => input);
|
|
72
|
+
setState("then");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
let settled = false;
|
|
76
|
+
const publish = (kind: "then" | "catch", next: unknown) => {
|
|
77
|
+
if (!alive(token)) return;
|
|
78
|
+
settled = true;
|
|
79
|
+
try {
|
|
80
|
+
runWithOwner(owner, () =>
|
|
81
|
+
untrack(() => {
|
|
82
|
+
if (kind === "then") setValue(() => next as T);
|
|
83
|
+
else setError(() => next);
|
|
84
|
+
setState(kind);
|
|
85
|
+
if (!starting) flushRuneWork();
|
|
86
|
+
}),
|
|
87
|
+
);
|
|
88
|
+
} catch (failure) {
|
|
89
|
+
queueMicrotask(() => {
|
|
90
|
+
throw failure;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
input.then(
|
|
95
|
+
(next) => publish("then", next),
|
|
96
|
+
(reason) => publish("catch", reason),
|
|
97
|
+
);
|
|
98
|
+
queueMicrotask(() => {
|
|
99
|
+
if (!settled && alive(token))
|
|
100
|
+
runWithOwner(owner, () => {
|
|
101
|
+
setState("pending");
|
|
102
|
+
flushRuneWork();
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
const view = mapArray(
|
|
107
|
+
() => {
|
|
108
|
+
const current = state();
|
|
109
|
+
return current === undefined ? [] : [current];
|
|
110
|
+
},
|
|
111
|
+
(kind) => {
|
|
112
|
+
if (kind === "pending") return branches.pending?.();
|
|
113
|
+
if (kind === "then") return branches.then?.(value as Accessor<T>);
|
|
114
|
+
if (!branches.catch) throw untrack(error);
|
|
115
|
+
return branches.catch(error);
|
|
116
|
+
},
|
|
117
|
+
undefined,
|
|
118
|
+
schedule,
|
|
119
|
+
);
|
|
120
|
+
starting = false;
|
|
121
|
+
return view;
|
|
122
|
+
});
|
|
123
|
+
}
|