@lightspeed-js/core 0.1.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/LICENSE +21 -0
- package/README.md +75 -0
- package/dist/compile.js +1494 -0
- package/dist/index.js +3060 -0
- package/dist/ssg.js +3620 -0
- package/dist/types/build.d.ts +1 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/src/agent/index.d.ts +175 -0
- package/dist/types/src/cli/commands/agent.d.ts +9 -0
- package/dist/types/src/cli/commands/build.d.ts +2 -0
- package/dist/types/src/cli/commands/create.d.ts +2 -0
- package/dist/types/src/cli/commands/dev.d.ts +2 -0
- package/dist/types/src/cli/commands/index.d.ts +3 -0
- package/dist/types/src/cli/index.d.ts +2 -0
- package/dist/types/src/cli/types/commands.d.ts +11 -0
- package/dist/types/src/cli/types/index.d.ts +1 -0
- package/dist/types/src/cli/utils/commands.d.ts +5 -0
- package/dist/types/src/cli/utils/index.d.ts +1 -0
- package/dist/types/src/compile/expressions.d.ts +76 -0
- package/dist/types/src/compile/index.d.ts +44 -0
- package/dist/types/src/compile/logic.d.ts +101 -0
- package/dist/types/src/manifest.d.ts +148 -0
- package/dist/types/src/marker.d.ts +85 -0
- package/dist/types/src/parser/index.d.ts +2 -0
- package/dist/types/src/parser/layout.d.ts +32 -0
- package/dist/types/src/parser/lsp.d.ts +56 -0
- package/dist/types/src/parser/route.d.ts +87 -0
- package/dist/types/src/parser/style-source-node.d.ts +19 -0
- package/dist/types/src/parser/style.d.ts +20 -0
- package/dist/types/src/parser/template.d.ts +32 -0
- package/dist/types/src/parser/test.d.ts +1 -0
- package/dist/types/src/runtime/bindings/BindingEvaluator.d.ts +174 -0
- package/dist/types/src/runtime/bindings/index.d.ts +2 -0
- package/dist/types/src/runtime/compiler/ImportTransformer.d.ts +11 -0
- package/dist/types/src/runtime/compiler/index.d.ts +1 -0
- package/dist/types/src/runtime/components/ComponentInstance.d.ts +110 -0
- package/dist/types/src/runtime/components/ComponentRegistry.d.ts +11 -0
- package/dist/types/src/runtime/components/index.d.ts +3 -0
- package/dist/types/src/runtime/components/types/index.d.ts +34 -0
- package/dist/types/src/runtime/core.d.ts +251 -0
- package/dist/types/src/runtime/diagnostics.d.ts +33 -0
- package/dist/types/src/runtime/dom/ForBlock.d.ts +127 -0
- package/dist/types/src/runtime/dom/Mounter.d.ts +223 -0
- package/dist/types/src/runtime/dom/StyleManager.d.ts +57 -0
- package/dist/types/src/runtime/dom/hydration.d.ts +35 -0
- package/dist/types/src/runtime/dom/index.d.ts +7 -0
- package/dist/types/src/runtime/events/EventBinder.d.ts +18 -0
- package/dist/types/src/runtime/events/index.d.ts +1 -0
- package/dist/types/src/runtime/globals/GlobalFunctions.d.ts +24 -0
- package/dist/types/src/runtime/globals/index.d.ts +1 -0
- package/dist/types/src/runtime/helpers.d.ts +141 -0
- package/dist/types/src/runtime/index.d.ts +16 -0
- package/dist/types/src/runtime/reactive/ReactiveProxy.d.ts +63 -0
- package/dist/types/src/runtime/reactive/UpdateScheduler.d.ts +9 -0
- package/dist/types/src/runtime/reactive/index.d.ts +2 -0
- package/dist/types/src/runtime/signals/SignalManager.d.ts +10 -0
- package/dist/types/src/runtime/signals/index.d.ts +3 -0
- package/dist/types/src/runtime/signals/signals.d.ts +16 -0
- package/dist/types/src/runtime/signals/types/index.d.ts +1 -0
- package/dist/types/src/runtime/signals/types/signals.d.ts +7 -0
- package/dist/types/src/runtime/state/Cell.d.ts +68 -0
- package/dist/types/src/runtime/state/CellManager.d.ts +101 -0
- package/dist/types/src/runtime/state/index.d.ts +4 -0
- package/dist/types/src/specifiers.d.ts +16 -0
- package/dist/types/src/ssg/index.d.ts +21 -0
- package/dist/types/src/ssg/rebuild.d.ts +49 -0
- package/dist/types/src/ssg/render.d.ts +104 -0
- package/dist/types/src/types/lsp.d.ts +22 -0
- package/dist/types/src/types/optional.d.ts +14 -0
- package/dist/types/src/types/virtual.d.ts +4 -0
- package/dist/types/src/vite/csp.d.ts +12 -0
- package/dist/types/src/vite/index.d.ts +2 -0
- package/dist/types/src/vite/layout.d.ts +17 -0
- package/dist/types/src/vite/plugin.d.ts +69 -0
- package/dist/types/src/vite/routes.d.ts +139 -0
- package/dist/types/test/adopted-styles.test.d.ts +1 -0
- package/dist/types/test/adoption.test.d.ts +1 -0
- package/dist/types/test/agent.test.d.ts +1 -0
- package/dist/types/test/audit.test.d.ts +1 -0
- package/dist/types/test/bindings.test.d.ts +1 -0
- package/dist/types/test/branches-in-loops.test.d.ts +1 -0
- package/dist/types/test/build.test.d.ts +1 -0
- package/dist/types/test/buildtime.test.d.ts +1 -0
- package/dist/types/test/built.test.d.ts +1 -0
- package/dist/types/test/cell-props.test.d.ts +1 -0
- package/dist/types/test/comments.test.d.ts +1 -0
- package/dist/types/test/compile-expressions.test.d.ts +1 -0
- package/dist/types/test/compile-logic.test.d.ts +1 -0
- package/dist/types/test/components.test.d.ts +1 -0
- package/dist/types/test/composition.test.d.ts +1 -0
- package/dist/types/test/declared-needs.test.d.ts +1 -0
- package/dist/types/test/diagnostics.test.d.ts +1 -0
- package/dist/types/test/dynamic.test.d.ts +1 -0
- package/dist/types/test/example.test.d.ts +1 -0
- package/dist/types/test/faq.test.d.ts +1 -0
- package/dist/types/test/flatten-nested.test.d.ts +1 -0
- package/dist/types/test/html-binding.test.d.ts +1 -0
- package/dist/types/test/hydration-report.test.d.ts +1 -0
- package/dist/types/test/hydration.test.d.ts +1 -0
- package/dist/types/test/layout-resolution.test.d.ts +1 -0
- package/dist/types/test/layout.test.d.ts +1 -0
- package/dist/types/test/lazy.test.d.ts +1 -0
- package/dist/types/test/manifest.test.d.ts +1 -0
- package/dist/types/test/model-props.test.d.ts +1 -0
- package/dist/types/test/model.test.d.ts +1 -0
- package/dist/types/test/mounting.test.d.ts +1 -0
- package/dist/types/test/needs.test.d.ts +1 -0
- package/dist/types/test/nested-rows.test.d.ts +1 -0
- package/dist/types/test/on-mounted.test.d.ts +1 -0
- package/dist/types/test/on-update.test.d.ts +1 -0
- package/dist/types/test/page-island.test.d.ts +1 -0
- package/dist/types/test/pages.test.d.ts +1 -0
- package/dist/types/test/reactivity.test.d.ts +1 -0
- package/dist/types/test/rebuild-keeps-logic.test.d.ts +1 -0
- package/dist/types/test/rebuild.test.d.ts +1 -0
- package/dist/types/test/reenumerate.test.d.ts +1 -0
- package/dist/types/test/refresh.test.d.ts +1 -0
- package/dist/types/test/refs.test.d.ts +1 -0
- package/dist/types/test/reorder.test.d.ts +1 -0
- package/dist/types/test/route.test.d.ts +1 -0
- package/dist/types/test/routes-build.test.d.ts +1 -0
- package/dist/types/test/routes-dev.test.d.ts +1 -0
- package/dist/types/test/routes-emit.test.d.ts +1 -0
- package/dist/types/test/routes-runtime.test.d.ts +1 -0
- package/dist/types/test/setup.d.ts +59 -0
- package/dist/types/test/show.test.d.ts +1 -0
- package/dist/types/test/skeleton-adoption.test.d.ts +1 -0
- package/dist/types/test/slots.test.d.ts +1 -0
- package/dist/types/test/specifiers.test.d.ts +1 -0
- package/dist/types/test/ssg.test.d.ts +1 -0
- package/dist/types/test/state.test.d.ts +1 -0
- package/dist/types/test/static-islands.test.d.ts +1 -0
- package/dist/types/test/stats.test.d.ts +1 -0
- package/dist/types/test/structural.test.d.ts +1 -0
- package/dist/types/test/style-sources.test.d.ts +1 -0
- package/dist/types/test/styles.test.d.ts +1 -0
- package/dist/types/test/surgical-rebuild.test.d.ts +1 -0
- package/dist/types/test/tables.test.d.ts +1 -0
- package/dist/types/test/unmount.test.d.ts +1 -0
- package/dist/types/test/verbatim.test.d.ts +1 -0
- package/dist/vite.js +5655 -0
- package/package.json +64 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface ComponentDefinition {
|
|
2
|
+
id: string;
|
|
3
|
+
template: string;
|
|
4
|
+
/** Static placeholder shown until this component hydrates. */
|
|
5
|
+
skeleton?: string;
|
|
6
|
+
/**
|
|
7
|
+
* Cell keys the component declared it cannot render without.
|
|
8
|
+
*
|
|
9
|
+
* Travels on the definition so a *usage* of it inherits the wait: a caller
|
|
10
|
+
* should not have to know the child's data dependencies, which is the
|
|
11
|
+
* coupling a prop chain has. Merged with any `$needs` the placement adds.
|
|
12
|
+
*/
|
|
13
|
+
needs?: string[];
|
|
14
|
+
style: string;
|
|
15
|
+
/**
|
|
16
|
+
* The component's compiled logic.
|
|
17
|
+
*
|
|
18
|
+
* A real function built by the compiler, not a string evaluated at mount:
|
|
19
|
+
* that is what lets logic `import` project modules and what keeps the
|
|
20
|
+
* browser free of `new Function`. See [[ADR-016]].
|
|
21
|
+
*/
|
|
22
|
+
setup?: (context: Record<string, unknown>) => void;
|
|
23
|
+
/**
|
|
24
|
+
* Handler names an event expression is compiled with, in order.
|
|
25
|
+
*
|
|
26
|
+
* Fixed at build so the compiled signature cannot depend on what the
|
|
27
|
+
* instance happened to register. See [[ADR-016]].
|
|
28
|
+
*/
|
|
29
|
+
handlers?: string[];
|
|
30
|
+
/** Per-component hash stamped on owned elements for style scoping. */
|
|
31
|
+
/** Deferred CSS, applied when the component hydrates. */
|
|
32
|
+
styleDeferred?: string;
|
|
33
|
+
scopeId?: string;
|
|
34
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { ComponentInstance, type ComponentDefinition } from "./components/index.js";
|
|
2
|
+
import type { MarkerRange } from "./dom/Mounter.js";
|
|
3
|
+
/**
|
|
4
|
+
* A mounted root. Hold onto it to unmount that island later.
|
|
5
|
+
*
|
|
6
|
+
* `instance` is replaced when the root is remounted (hot reload), so read it
|
|
7
|
+
* through the handle rather than caching it.
|
|
8
|
+
*/
|
|
9
|
+
export interface MountHandle {
|
|
10
|
+
id: string;
|
|
11
|
+
componentId: string;
|
|
12
|
+
instance: ComponentInstance;
|
|
13
|
+
props: any;
|
|
14
|
+
/** Set when mounted imperatively into a container element. */
|
|
15
|
+
container?: Element;
|
|
16
|
+
/** Set when discovered as an island marker in markup. */
|
|
17
|
+
range?: MarkerRange;
|
|
18
|
+
/**
|
|
19
|
+
* Original slot markup. Kept as a string so a remount can rebuild the
|
|
20
|
+
* content it consumed.
|
|
21
|
+
*/
|
|
22
|
+
slotHtml?: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function registerPageExpressions(expressions?: Record<string, any>, writers?: Record<string, any>): void;
|
|
25
|
+
export declare class LightspeedRuntime {
|
|
26
|
+
private componentRegistry;
|
|
27
|
+
/** Keyed by instance id ("button#2"), not component id. */
|
|
28
|
+
private instances;
|
|
29
|
+
private globalSignals;
|
|
30
|
+
private cells;
|
|
31
|
+
private initialised;
|
|
32
|
+
private instanceCounter;
|
|
33
|
+
/**
|
|
34
|
+
* Independently mounted islands. Lightspeed does not own the page - a HUD, a
|
|
35
|
+
* form and an overlay are three roots that happen to share a runtime.
|
|
36
|
+
*/
|
|
37
|
+
/**
|
|
38
|
+
* Component id to a dynamic import.
|
|
39
|
+
*
|
|
40
|
+
* Supplied by the build's virtual module. Each import is a chunk boundary,
|
|
41
|
+
* so a component's code arrives only when something mounts it - which is
|
|
42
|
+
* what makes `$hydrate` defer bytes rather than only execution.
|
|
43
|
+
*/
|
|
44
|
+
private loaders;
|
|
45
|
+
private loading;
|
|
46
|
+
/** Skeleton markup and scope for components that have not loaded yet. */
|
|
47
|
+
private pageMetadata;
|
|
48
|
+
private roots;
|
|
49
|
+
private rootCounter;
|
|
50
|
+
private ssg;
|
|
51
|
+
/** Cancels for islands still waiting on a hydration trigger. */
|
|
52
|
+
private pendingIslands;
|
|
53
|
+
private reactiveProxy;
|
|
54
|
+
private updateScheduler;
|
|
55
|
+
private bindingEvaluator;
|
|
56
|
+
private eventBinder;
|
|
57
|
+
private globalFunctions;
|
|
58
|
+
/**
|
|
59
|
+
* Instances with `onMounted` callbacks that have not run yet.
|
|
60
|
+
*
|
|
61
|
+
* Order is mount order, which puts children before parents: a child mounts
|
|
62
|
+
* during its parent's collection, so it queues first. A parent that measures
|
|
63
|
+
* something a child rendered therefore sees it.
|
|
64
|
+
*/
|
|
65
|
+
private pendingMounts;
|
|
66
|
+
/** Guards against a callback that mounts something re-entering the flush. */
|
|
67
|
+
private flushing;
|
|
68
|
+
private mounter;
|
|
69
|
+
private styleManager;
|
|
70
|
+
constructor(options?: {
|
|
71
|
+
ssg?: boolean;
|
|
72
|
+
});
|
|
73
|
+
/**
|
|
74
|
+
* Instances whose `onUpdate` is running, so their writes do not queue again.
|
|
75
|
+
*
|
|
76
|
+
* The write is not lost - the bindings evaluate immediately after, in this
|
|
77
|
+
* same pass. See the guard in the `ReactiveProxy` callback above.
|
|
78
|
+
*/
|
|
79
|
+
private updating;
|
|
80
|
+
private runUpdateCallbacks;
|
|
81
|
+
/**
|
|
82
|
+
* Apply a batch of state changes.
|
|
83
|
+
*
|
|
84
|
+
* Each owner is the instance whose state changed, so only its bindings are
|
|
85
|
+
* re-evaluated. A null owner comes from the window-global `reactive()`,
|
|
86
|
+
* which has no instance - that falls back to updating the whole tree.
|
|
87
|
+
*/
|
|
88
|
+
private handleUpdate;
|
|
89
|
+
registerComponent(component: ComponentDefinition): void;
|
|
90
|
+
/**
|
|
91
|
+
* Metadata the page inlined for components that may not have loaded.
|
|
92
|
+
*
|
|
93
|
+
* Read from the document rather than passed in, so an app never has to wire
|
|
94
|
+
* up something the build already knows.
|
|
95
|
+
*/
|
|
96
|
+
private readPageMetadata;
|
|
97
|
+
/**
|
|
98
|
+
* Content the build resolved and baked into the page.
|
|
99
|
+
*
|
|
100
|
+
* Seeded in the constructor, so cells hold their values before anything
|
|
101
|
+
* mounts - `$needs` gates are already open, and what the client renders
|
|
102
|
+
* matches the HTML, so adoption does not bail out.
|
|
103
|
+
*/
|
|
104
|
+
/**
|
|
105
|
+
* Resolve `route` from the URL, for a page whose paths were not knowable at
|
|
106
|
+
* build.
|
|
107
|
+
*
|
|
108
|
+
* A baked route ships its params in the state payload; a rewritten one ships
|
|
109
|
+
* only the pattern, because one file answers many URLs. Seeding the same
|
|
110
|
+
* cell either way is what lets a component read `shared.route.params.id`
|
|
111
|
+
* without knowing or caring which kind of page it is on.
|
|
112
|
+
*
|
|
113
|
+
* Runs before `setup()`, so the value is present for the first render rather
|
|
114
|
+
* than arriving as an update.
|
|
115
|
+
*/
|
|
116
|
+
private readPageRoute;
|
|
117
|
+
private readPageState;
|
|
118
|
+
/** Hand the runtime a lazy import per component. */
|
|
119
|
+
useComponents(loaders: Record<string, () => Promise<any>>,
|
|
120
|
+
/**
|
|
121
|
+
* Expressions a page contributes, from its island markers.
|
|
122
|
+
*
|
|
123
|
+
* They belong to no component, so they arrive here - with the entry chunk
|
|
124
|
+
* that hydration itself comes from, which is the only place guaranteed to
|
|
125
|
+
* be present when the first island mounts.
|
|
126
|
+
*/
|
|
127
|
+
page?: {
|
|
128
|
+
expressions?: Record<string, any>;
|
|
129
|
+
writers?: Record<string, any>;
|
|
130
|
+
}): void;
|
|
131
|
+
/**
|
|
132
|
+
* A component's definition, fetching it if it has not arrived yet.
|
|
133
|
+
*
|
|
134
|
+
* Concurrent requests share one import - two islands using the same
|
|
135
|
+
* component must not trigger two fetches.
|
|
136
|
+
*/
|
|
137
|
+
private resolveComponent;
|
|
138
|
+
updateComponent(id: string, component: ComponentDefinition): void;
|
|
139
|
+
/**
|
|
140
|
+
* Create an instance and run its logic. Called by the mounter for every
|
|
141
|
+
* <lsp-mount> it encounters, so one definition yields many instances.
|
|
142
|
+
*/
|
|
143
|
+
/**
|
|
144
|
+
* Let an instance hear about the objects it was handed.
|
|
145
|
+
*
|
|
146
|
+
* A form shares one mutable object - the parent owns it, children write into
|
|
147
|
+
* it with `$model`. Writing notifies whoever *wrapped* the object, which is
|
|
148
|
+
* the parent alone, so a sibling reading the same object kept rendering the
|
|
149
|
+
* previous value until something else happened to update it. Registering
|
|
150
|
+
* every holder is what makes a shared object behave like shared state.
|
|
151
|
+
*/
|
|
152
|
+
private shareObjectProps;
|
|
153
|
+
private createInstance;
|
|
154
|
+
private createExecutionContext;
|
|
155
|
+
/** Whether this render declared any build-time content. */
|
|
156
|
+
hasBuildData(): boolean;
|
|
157
|
+
/** Wait for build-time content, and let a failure fail the build. */
|
|
158
|
+
settleBuildData(): Promise<void>;
|
|
159
|
+
/** Resolved build-time content, for the page to carry to the client. */
|
|
160
|
+
buildPayload(): Record<string, unknown>;
|
|
161
|
+
/** Seed content the build resolved, before anything mounts. */
|
|
162
|
+
/**
|
|
163
|
+
* What each mounted island would need the browser for, if anything.
|
|
164
|
+
*
|
|
165
|
+
* Per instance, and per instance only. What one island needs from another is
|
|
166
|
+
* visible in the markers rather than here - see `flattenStatic`.
|
|
167
|
+
*/
|
|
168
|
+
hydrationReport(options?: {
|
|
169
|
+
routeIsLive?: boolean;
|
|
170
|
+
}): {
|
|
171
|
+
id: string;
|
|
172
|
+
componentId: string;
|
|
173
|
+
live: boolean;
|
|
174
|
+
reasons: string[];
|
|
175
|
+
}[];
|
|
176
|
+
seedState(values: Record<string, unknown>): void;
|
|
177
|
+
/**
|
|
178
|
+
* A shared cell, from outside any component.
|
|
179
|
+
*
|
|
180
|
+
* Needed by `$needs`, which is a promise that *someone* fills the cell - and
|
|
181
|
+
* that someone cannot be the gated component itself, whose logic has not run.
|
|
182
|
+
* A page-level script, an earlier island, or a cell initialiser fills it; this
|
|
183
|
+
* is how the first of those reaches it.
|
|
184
|
+
*
|
|
185
|
+
* Unscoped, so a subscription here lives until its returned unsubscribe is
|
|
186
|
+
* called. Component logic gets the instance-bound facade instead.
|
|
187
|
+
*/
|
|
188
|
+
shared(key: string, init?: any, options?: any): {
|
|
189
|
+
key: string;
|
|
190
|
+
value: any;
|
|
191
|
+
readonly pending: boolean;
|
|
192
|
+
set: (next: any) => void;
|
|
193
|
+
subscribe: (listener: (value: any) => void) => () => void;
|
|
194
|
+
};
|
|
195
|
+
/**
|
|
196
|
+
* A cell facade bound to one instance, so manual subscriptions are dropped
|
|
197
|
+
* when that instance is destroyed - same contract as scoped signals.
|
|
198
|
+
*/
|
|
199
|
+
private scopedCell;
|
|
200
|
+
private defaultProps;
|
|
201
|
+
/**
|
|
202
|
+
* Mount a component into a container as an independent root.
|
|
203
|
+
*
|
|
204
|
+
* Call it as many times as there are islands on the page. Roots share this
|
|
205
|
+
* runtime - and therefore signals and styles - but nothing else.
|
|
206
|
+
*
|
|
207
|
+
* Replaces the container's contents. Attaching to server-rendered markup
|
|
208
|
+
* instead is deferred until there is markup to attach to.
|
|
209
|
+
*/
|
|
210
|
+
mount(componentId: string, target: Element | string, props?: any, slotHtml?: string): Promise<MountHandle | null>;
|
|
211
|
+
/** Tear down one root, leaving the others alone. */
|
|
212
|
+
unmount(handle: MountHandle): void;
|
|
213
|
+
private remountAll;
|
|
214
|
+
/**
|
|
215
|
+
* Find and mount every island in the markup.
|
|
216
|
+
*
|
|
217
|
+
* This is the primary path: a page - hand-written or server-rendered - marks
|
|
218
|
+
* its islands with <lsp-mount>, and nobody hand-maintains a list of mount()
|
|
219
|
+
* calls beside it. The same marker the compiler emits for a nested child in a
|
|
220
|
+
* template marks a top-level island in a page; one concept, two contexts.
|
|
221
|
+
*
|
|
222
|
+
* Only outermost markers are scanned. A marker nested inside another belongs
|
|
223
|
+
* to that island's tree and is mounted with it.
|
|
224
|
+
*
|
|
225
|
+
* The <lsp-mount> element is kept in the DOM as a wrapper rather than being
|
|
226
|
+
* replaced, giving a stable anchor for remounts and, later, for deferred
|
|
227
|
+
* hydration. It is styled `display: contents` so it does not affect layout.
|
|
228
|
+
*/
|
|
229
|
+
hydrate(scope?: ParentNode): Promise<MountHandle[]>;
|
|
230
|
+
/**
|
|
231
|
+
* Run the `onMounted` callbacks whose nodes are now in the document.
|
|
232
|
+
*
|
|
233
|
+
* Drains rather than iterates: a callback is allowed to mount something,
|
|
234
|
+
* and whatever that queues belongs to the next pass rather than to a list
|
|
235
|
+
* being walked. Each callback is isolated - one that throws is the
|
|
236
|
+
* component's fault and must not stop the others, which is a real failure
|
|
237
|
+
* mode when a page has a dozen islands and one has a typo.
|
|
238
|
+
*/
|
|
239
|
+
private flushMounted;
|
|
240
|
+
/** Bring a discovered island to life and register it as a root. */
|
|
241
|
+
private mountIsland;
|
|
242
|
+
private parseSlotHtml;
|
|
243
|
+
private resolveTarget;
|
|
244
|
+
private unregisterSubtree;
|
|
245
|
+
/** Convenience for the single-root case. */
|
|
246
|
+
init(rootComponentId?: string, target?: Element | string): Promise<void>;
|
|
247
|
+
destroy(): void;
|
|
248
|
+
}
|
|
249
|
+
export declare function createLightspeedApp(options?: {
|
|
250
|
+
ssg?: boolean;
|
|
251
|
+
}): LightspeedRuntime;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One place that decides what the runtime says, and when.
|
|
3
|
+
*
|
|
4
|
+
* The distinction that matters is **fault versus chatter**, not dev versus
|
|
5
|
+
* production:
|
|
6
|
+
*
|
|
7
|
+
* - A *fault* means the page will not behave as written - a binding that threw,
|
|
8
|
+
* a component that could not be found, an island that had to be rebuilt.
|
|
9
|
+
* These are always reported. Hiding them behind a DEV flag is how a page ends
|
|
10
|
+
* up quietly broken in the one environment users see: adoption bail-outs were
|
|
11
|
+
* DEV-gated, so a bug that left *nothing on a built page interactive* reported
|
|
12
|
+
* zero problems while six components failed. Twice this was chased down the
|
|
13
|
+
* wrong path because the evidence was suppressed.
|
|
14
|
+
*
|
|
15
|
+
* - *Chatter* is useful while building and noise otherwise - "mounted x",
|
|
16
|
+
* "styles updated". That stays behind DEV.
|
|
17
|
+
*
|
|
18
|
+
* Everything carries a prefix so it can be filtered, and so a warning from
|
|
19
|
+
* Lightspeed is distinguishable from one the application produced.
|
|
20
|
+
*/
|
|
21
|
+
/** Something is wrong and the page will not behave as written. */
|
|
22
|
+
export declare function warn(message: string, ...detail: unknown[]): void;
|
|
23
|
+
/** Something failed outright. */
|
|
24
|
+
export declare function fail(message: string, ...detail: unknown[]): void;
|
|
25
|
+
/**
|
|
26
|
+
* Extra context for a fault - a compiled source dump, a full stack.
|
|
27
|
+
*
|
|
28
|
+
* Reported only in dev, because it is unreadable in a production console and
|
|
29
|
+
* can be large. The fault itself is always reported first.
|
|
30
|
+
*/
|
|
31
|
+
export declare function detail(message: string, ...args: unknown[]): void;
|
|
32
|
+
/** Useful while developing, noise in production. */
|
|
33
|
+
export declare function debug(message: string, ...args: unknown[]): void;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { ComponentInstance } from "../components/ComponentInstance.js";
|
|
2
|
+
import type { Binding } from "../bindings/index.js";
|
|
3
|
+
/**
|
|
4
|
+
* Anything that can own bindings and be torn down: a component instance, or a
|
|
5
|
+
* single row of a `$for`.
|
|
6
|
+
*/
|
|
7
|
+
export interface BindingHolder {
|
|
8
|
+
addBinding(binding: Binding): void;
|
|
9
|
+
addCleanup(cleanup: Function): void;
|
|
10
|
+
addBlock(block: ForBlock): void;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* One iteration of a `$for`.
|
|
14
|
+
*
|
|
15
|
+
* Owns everything created for it, so removing an item tears down exactly that
|
|
16
|
+
* item's bindings, listeners, child components and nested blocks.
|
|
17
|
+
*/
|
|
18
|
+
export declare class Row implements BindingHolder {
|
|
19
|
+
locals: any[];
|
|
20
|
+
key: string;
|
|
21
|
+
nodes: Node[];
|
|
22
|
+
bindings: Binding[];
|
|
23
|
+
blocks: ForBlock[];
|
|
24
|
+
cleanup: Function[];
|
|
25
|
+
instances: ComponentInstance[];
|
|
26
|
+
/** Loop variables in scope here: outer aliases first, then this row's item. */
|
|
27
|
+
constructor(locals: any[]);
|
|
28
|
+
addBinding(binding: Binding): void;
|
|
29
|
+
addCleanup(cleanup: Function): void;
|
|
30
|
+
addBlock(block: ForBlock): void;
|
|
31
|
+
get item(): any;
|
|
32
|
+
set item(value: any);
|
|
33
|
+
}
|
|
34
|
+
export interface RowRenderer {
|
|
35
|
+
createRow(block: ForBlock, item: any): Promise<Row>;
|
|
36
|
+
refreshRow(block: ForBlock, row: Row): void;
|
|
37
|
+
destroyRow(block: ForBlock, row: Row): void;
|
|
38
|
+
/**
|
|
39
|
+
* Rows have landed in the document.
|
|
40
|
+
*
|
|
41
|
+
* A block places its own nodes rather than going through `fillRange`, so
|
|
42
|
+
* without this nothing told the runtime that anything inside them was
|
|
43
|
+
* connected - and `onMounted` for a component in a row never ran.
|
|
44
|
+
*/
|
|
45
|
+
connected?(): void;
|
|
46
|
+
}
|
|
47
|
+
/** `$for="n in 5"` and iterables, not just arrays. */
|
|
48
|
+
export declare function toIterable(value: any): any[];
|
|
49
|
+
/**
|
|
50
|
+
* A `$for` block: an anchor, a row template, and a keyed set of rows.
|
|
51
|
+
*
|
|
52
|
+
* Rows are always keyed. Unkeyed reuse looks fine for text but silently hands
|
|
53
|
+
* one row's carried state - a child component, an input's value, focus - to a
|
|
54
|
+
* different item. See ADR-007.
|
|
55
|
+
*/
|
|
56
|
+
export declare class ForBlock {
|
|
57
|
+
owner: ComponentInstance;
|
|
58
|
+
anchor: Comment;
|
|
59
|
+
/**
|
|
60
|
+
* One template for `$for`; one per branch for an `$if` chain, indexed by
|
|
61
|
+
* the branch the condition selects.
|
|
62
|
+
*/
|
|
63
|
+
templates: Element[];
|
|
64
|
+
/** null for `$if`, which has no loop variable. */
|
|
65
|
+
alias: string | null;
|
|
66
|
+
/** Alias names in scope from enclosing blocks, outermost first. */
|
|
67
|
+
params: string[];
|
|
68
|
+
/** Live reference to the enclosing row's locals, if nested. */
|
|
69
|
+
parentLocals: any[];
|
|
70
|
+
keyFor: ((locals: any[]) => any) | null;
|
|
71
|
+
private renderer;
|
|
72
|
+
/** `$if`: zero or one row, driven by a condition rather than a list. */
|
|
73
|
+
conditional: boolean;
|
|
74
|
+
/** The collection expression, kept so adoption can re-evaluate it. */
|
|
75
|
+
itemsExpr: string;
|
|
76
|
+
rows: Map<string, Row>;
|
|
77
|
+
order: string[];
|
|
78
|
+
private pendingItems;
|
|
79
|
+
private running;
|
|
80
|
+
private warnedDuplicates;
|
|
81
|
+
constructor(owner: ComponentInstance, anchor: Comment,
|
|
82
|
+
/**
|
|
83
|
+
* One template for `$for`; one per branch for an `$if` chain, indexed by
|
|
84
|
+
* the branch the condition selects.
|
|
85
|
+
*/
|
|
86
|
+
templates: Element[],
|
|
87
|
+
/** null for `$if`, which has no loop variable. */
|
|
88
|
+
alias: string | null,
|
|
89
|
+
/** Alias names in scope from enclosing blocks, outermost first. */
|
|
90
|
+
params: string[],
|
|
91
|
+
/** Live reference to the enclosing row's locals, if nested. */
|
|
92
|
+
parentLocals: any[], keyFor: ((locals: any[]) => any) | null, renderer: RowRenderer,
|
|
93
|
+
/** `$if`: zero or one row, driven by a condition rather than a list. */
|
|
94
|
+
conditional?: boolean,
|
|
95
|
+
/** The collection expression, kept so adoption can re-evaluate it. */
|
|
96
|
+
itemsExpr?: string);
|
|
97
|
+
/**
|
|
98
|
+
* Turn a bound expression into the rows it implies.
|
|
99
|
+
*
|
|
100
|
+
* `$if` is the degenerate case - a block whose collection is empty or a
|
|
101
|
+
* single placeholder - so it needs no reconciliation logic of its own.
|
|
102
|
+
*/
|
|
103
|
+
toItems(value: any): any[];
|
|
104
|
+
/** The markup a row should clone. */
|
|
105
|
+
templateFor(item: any): Element;
|
|
106
|
+
/**
|
|
107
|
+
* Queue a reconcile.
|
|
108
|
+
*
|
|
109
|
+
* Rows containing components mount asynchronously, so a reconcile can still
|
|
110
|
+
* be in flight when the collection changes again. Only the latest snapshot
|
|
111
|
+
* matters, so it replaces any queued one and the loop runs until settled.
|
|
112
|
+
*/
|
|
113
|
+
schedule(items: any[]): void;
|
|
114
|
+
private drain;
|
|
115
|
+
/** Public so adoption can match rendered rows to the current data. */
|
|
116
|
+
keysFor(items: any[]): string[];
|
|
117
|
+
private reconcile;
|
|
118
|
+
/**
|
|
119
|
+
* Put rows in order before the anchor.
|
|
120
|
+
*
|
|
121
|
+
* Walks backwards so each row is inserted before the one after it, and skips
|
|
122
|
+
* nodes already in place. No longest-increasing-subsequence pass - simple
|
|
123
|
+
* moves are proportionate for the list sizes this targets.
|
|
124
|
+
*/
|
|
125
|
+
private position;
|
|
126
|
+
destroy(): void;
|
|
127
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import type { ComponentRegistry } from "../components/ComponentRegistry.js";
|
|
2
|
+
import type { ComponentInstance } from "../components/ComponentInstance.js";
|
|
3
|
+
import type { BindingEvaluator } from "../bindings/index.js";
|
|
4
|
+
import type { EventBinder } from "../events/index.js";
|
|
5
|
+
import { ForBlock, Row, type RowRenderer } from "./ForBlock.js";
|
|
6
|
+
import { type MarkerPayload } from "../../marker.js";
|
|
7
|
+
/** An island boundary found while walking, plus what sits between it. */
|
|
8
|
+
export interface MarkerRange {
|
|
9
|
+
start: Comment;
|
|
10
|
+
end: Comment;
|
|
11
|
+
payload: MarkerPayload;
|
|
12
|
+
/** Nodes between the comments - slot content, owned by the parent. */
|
|
13
|
+
slotNodes: Node[];
|
|
14
|
+
/**
|
|
15
|
+
* This range was found by adopting build-rendered DOM, so whatever sits
|
|
16
|
+
* between its comments is rendered output.
|
|
17
|
+
*
|
|
18
|
+
* Only *top-level* markers carry the `ssr` flag, so a nested island had no
|
|
19
|
+
* way to know its own content came from the build. A deferred one would
|
|
20
|
+
* rebuild itself when its trigger fired - throwing away correct DOM, losing
|
|
21
|
+
* focus and scroll, in the exact case ADR-011 exists to prevent.
|
|
22
|
+
*/
|
|
23
|
+
adopted?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export type InitInstance = (componentId: string, parent: ComponentInstance | null, props: any) => Promise<ComponentInstance | null>;
|
|
26
|
+
/** Drop an instance and everything below it from the runtime's registry. */
|
|
27
|
+
export type ReleaseInstance = (instance: ComponentInstance) => void;
|
|
28
|
+
/**
|
|
29
|
+
* Builds the instance tree and the DOM for it.
|
|
30
|
+
*
|
|
31
|
+
* A component's template is cloned per usage, so every <lsp-mount> becomes its
|
|
32
|
+
* own instance with its own state. The whole tree is built detached and
|
|
33
|
+
* inserted once, so nothing intermediate is ever painted.
|
|
34
|
+
*/
|
|
35
|
+
export declare class Mounter implements RowRenderer {
|
|
36
|
+
private registry;
|
|
37
|
+
private bindings;
|
|
38
|
+
private events;
|
|
39
|
+
private initInstance;
|
|
40
|
+
private releaseInstance;
|
|
41
|
+
/** Fetches a component's definition if it has not arrived yet. */
|
|
42
|
+
private resolve;
|
|
43
|
+
/** Skeleton and scope for a component that has not been fetched. */
|
|
44
|
+
private metadata;
|
|
45
|
+
/** Build-time rendering: mount everything that is not waiting on data. */
|
|
46
|
+
private ssg;
|
|
47
|
+
/** Emits the `$shimmer` placeholder styles on first use. */
|
|
48
|
+
private styles;
|
|
49
|
+
constructor(registry: ComponentRegistry, bindings: BindingEvaluator, events: EventBinder, initInstance: InitInstance, releaseInstance?: ReleaseInstance,
|
|
50
|
+
/** Fetches a component's definition if it has not arrived yet. */
|
|
51
|
+
resolve?: (componentId: string) => Promise<any>,
|
|
52
|
+
/** Skeleton and scope for a component that has not been fetched. */
|
|
53
|
+
metadata?: (componentId: string) => {
|
|
54
|
+
scope?: string;
|
|
55
|
+
skeleton?: string;
|
|
56
|
+
} | null,
|
|
57
|
+
/** Build-time rendering: mount everything that is not waiting on data. */
|
|
58
|
+
ssg?: boolean,
|
|
59
|
+
/** Emits the `$shimmer` placeholder styles on first use. */
|
|
60
|
+
styles?: {
|
|
61
|
+
ensurePendingStyles(): void;
|
|
62
|
+
} | null);
|
|
63
|
+
mountRoot(componentId: string, container: Element, props?: any, slotNodes?: Node[]): Promise<ComponentInstance | null>;
|
|
64
|
+
mount(componentId: string, parent: ComponentInstance | null, props: any, slotNodes: Node[], preserved?: Map<string, Node[]>,
|
|
65
|
+
/** Server-rendered nodes to bind to instead of building fresh. */
|
|
66
|
+
adopt?: Node[],
|
|
67
|
+
/**
|
|
68
|
+
* Detaches nested build-rendered content, for the rebuild path only.
|
|
69
|
+
*
|
|
70
|
+
* Deliberately lazy. Doing it eagerly guts the very DOM adoption is about
|
|
71
|
+
* to walk: every nested marker comes back empty, every nested component
|
|
72
|
+
* bails out, and the page silently rebuilds everything. Only called if
|
|
73
|
+
* adoption is not attempted or does not succeed.
|
|
74
|
+
*/
|
|
75
|
+
rescue?: () => Map<string, Node[]>): Promise<ComponentInstance | null>;
|
|
76
|
+
/** Remove an instance's DOM and tear down its subtree. */
|
|
77
|
+
unmount(instance: ComponentInstance): void;
|
|
78
|
+
private parseTemplate;
|
|
79
|
+
private fillSlots;
|
|
80
|
+
/**
|
|
81
|
+
* Walk a subtree, attaching bindings and listeners to the owning instance
|
|
82
|
+
* and queueing any nested mount points.
|
|
83
|
+
*/
|
|
84
|
+
private collect;
|
|
85
|
+
/**
|
|
86
|
+
* Walk a node's children, treating an island boundary as a unit.
|
|
87
|
+
*
|
|
88
|
+
* Index-based rather than per-node, because a marker claims a *range* of
|
|
89
|
+
* siblings rather than a single element.
|
|
90
|
+
*/
|
|
91
|
+
/**
|
|
92
|
+
* `$needs` on an ordinary element: hold its children until the cells resolve.
|
|
93
|
+
*
|
|
94
|
+
* The lightweight tier of ADR-010. A component with a `<skeleton>` is the
|
|
95
|
+
* answer when the placeholder deserves real markup; this is for the case
|
|
96
|
+
* where one value is outstanding and a component would be ceremony.
|
|
97
|
+
*/
|
|
98
|
+
private collectNeeds;
|
|
99
|
+
private collectChildren;
|
|
100
|
+
/**
|
|
101
|
+
* Detach the content of every nested marker inside a range.
|
|
102
|
+
*
|
|
103
|
+
* Called before the range is rebuilt, while the rendered DOM is still there.
|
|
104
|
+
* Nodes are moved into a fragment rather than referenced, because filling the
|
|
105
|
+
* range removes anything still attached.
|
|
106
|
+
*/
|
|
107
|
+
private rescueRendered;
|
|
108
|
+
private takeRescued;
|
|
109
|
+
/**
|
|
110
|
+
* Bind to server-rendered DOM instead of rebuilding it.
|
|
111
|
+
*
|
|
112
|
+
* Walks the template and the rendered nodes in lockstep. Every divergence -
|
|
113
|
+
* a different tag, a missing block anchor, the wrong number of rows - returns
|
|
114
|
+
* false, and the caller rebuilds that component the way it always has. So
|
|
115
|
+
* adoption can be a performance win but never a correctness risk: a mismatch
|
|
116
|
+
* costs a rebuild and a dev warning rather than leaving bindings pointed at
|
|
117
|
+
* DOM that does not match the data.
|
|
118
|
+
*
|
|
119
|
+
* That guard matters more since ADR-010, because most components now render
|
|
120
|
+
* real content at build. Anything time- or environment-dependent (a
|
|
121
|
+
* timestamp, a persisted cell) legitimately differs between build and client.
|
|
122
|
+
*/
|
|
123
|
+
private adoptNodes;
|
|
124
|
+
/**
|
|
125
|
+
* Adopt a `$for` / `$if` block against its rendered anchor and rows.
|
|
126
|
+
*
|
|
127
|
+
* Returns how many rendered nodes were consumed, or -1 to bail out.
|
|
128
|
+
*
|
|
129
|
+
* Rather than rebuilding rows, this pre-populates the block so the first
|
|
130
|
+
* reconcile matches them by key and calls refreshRow - the existing keyed
|
|
131
|
+
* path does the rest.
|
|
132
|
+
*/
|
|
133
|
+
private adoptBlock;
|
|
134
|
+
/**
|
|
135
|
+
* Called once nodes have been inserted somewhere connected to the document.
|
|
136
|
+
*
|
|
137
|
+
* The runtime uses it to run `onMounted`. Here rather than at the end of a
|
|
138
|
+
* mount, because a component built fresh lands in a *fragment* first - its
|
|
139
|
+
* nodes are not in the document until an ancestor is inserted, and a
|
|
140
|
+
* callback that measures anything before then measures nothing.
|
|
141
|
+
*/
|
|
142
|
+
onConnected?: () => void;
|
|
143
|
+
/** Replace whatever sits between a marker pair. */
|
|
144
|
+
private fillRange;
|
|
145
|
+
private collectElement;
|
|
146
|
+
/**
|
|
147
|
+
* Turn `$for="item in expr"` into an anchored block.
|
|
148
|
+
*
|
|
149
|
+
* The element is replaced by a comment anchor and kept as the row template.
|
|
150
|
+
* A comment, not an element: a `$for` inside <tbody> or <select> cannot
|
|
151
|
+
* carry a wrapper, and unlike an empty text node a comment survives
|
|
152
|
+
* serialisation, which matters once markup is server-rendered.
|
|
153
|
+
*/
|
|
154
|
+
private createBlock;
|
|
155
|
+
/**
|
|
156
|
+
* Hand a block the rows already in the document.
|
|
157
|
+
*
|
|
158
|
+
* Keys are recomputed from the current data rather than shipped, so the
|
|
159
|
+
* first reconcile matches these by key and refreshes them in place instead
|
|
160
|
+
* of building replacements.
|
|
161
|
+
*/
|
|
162
|
+
private adoptRows;
|
|
163
|
+
/**
|
|
164
|
+
* Collect an `$if` / `$else-if` / `$else` chain from adjacent siblings.
|
|
165
|
+
*
|
|
166
|
+
* `nextElementSibling` skips the whitespace and comments between them. Each
|
|
167
|
+
* branch is detached as it is claimed, so a later walk cannot mistake a
|
|
168
|
+
* consumed `$else` for a stray one.
|
|
169
|
+
*/
|
|
170
|
+
private gatherBranches;
|
|
171
|
+
createRow(block: ForBlock, item: any): Promise<Row>;
|
|
172
|
+
/** A block placed its rows in the document. See `RowRenderer.connected`. */
|
|
173
|
+
connected(): void;
|
|
174
|
+
refreshRow(block: ForBlock, row: Row): void;
|
|
175
|
+
destroyRow(block: ForBlock, row: Row): void;
|
|
176
|
+
private cellsProxy;
|
|
177
|
+
private applyDirective;
|
|
178
|
+
/**
|
|
179
|
+
* A bare name resolves to a handler; anything else is an expression.
|
|
180
|
+
*
|
|
181
|
+
* Expressions are what make per-row actions possible - `@click="remove(item)"`
|
|
182
|
+
* inside a `$for` needs the loop variable, which a handler name cannot carry.
|
|
183
|
+
* Handler names and loop aliases are passed as parameters so free identifiers
|
|
184
|
+
* in the expression resolve without a scope object or `with`.
|
|
185
|
+
*/
|
|
186
|
+
private bindEvent;
|
|
187
|
+
private mountMarker;
|
|
188
|
+
/**
|
|
189
|
+
* Leave a skeleton between the markers and mount when the trigger fires.
|
|
190
|
+
*
|
|
191
|
+
* Slot content is detached first: rendering the skeleton clears the range,
|
|
192
|
+
* and those nodes already carry bindings the parent collected, so they must
|
|
193
|
+
* be preserved rather than rebuilt.
|
|
194
|
+
*/
|
|
195
|
+
private deferMarker;
|
|
196
|
+
/** Evaluate a `$is` expression to a component id. */
|
|
197
|
+
private resolveDynamicId;
|
|
198
|
+
/** Show a component's static placeholder, scoped so its own styles apply. */
|
|
199
|
+
renderSkeleton(range: MarkerRange): void;
|
|
200
|
+
private completeMount;
|
|
201
|
+
/**
|
|
202
|
+
* Top-level island markers in a scope.
|
|
203
|
+
*
|
|
204
|
+
* Depth tracking rather than containment: comments have no children, so a
|
|
205
|
+
* nested marker is one that appears between another's boundaries.
|
|
206
|
+
*/
|
|
207
|
+
findIslands(scope: ParentNode): MarkerRange[];
|
|
208
|
+
/** Mount a discovered island into its own boundary. */
|
|
209
|
+
mountRange(range: MarkerRange, props: any, slotNodes: Node[]): Promise<ComponentInstance | null>;
|
|
210
|
+
/** Attributes the parent wrote, applied to the child's root. */
|
|
211
|
+
private applyForwarded;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Collapse an if/else-if/else chain into one expression yielding the index of
|
|
215
|
+
* the winning branch, or -1 when none matches.
|
|
216
|
+
*
|
|
217
|
+
* Doing it in a single expression keeps "first truthy wins" in one place, and
|
|
218
|
+
* means the chain needs no evaluation machinery beyond an ordinary binding.
|
|
219
|
+
*/
|
|
220
|
+
export declare function composeBranchExpression(branches: Array<{
|
|
221
|
+
condition: string | null;
|
|
222
|
+
el: Element;
|
|
223
|
+
}>): string;
|