@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,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Island boundaries, as comment pairs.
|
|
3
|
+
*
|
|
4
|
+
* ```html
|
|
5
|
+
* <!--lsp {"c":"chart","props":{"title":"Q"},"hydrate":"visible"}-->
|
|
6
|
+
* …content…
|
|
7
|
+
* <!--/lsp-->
|
|
8
|
+
* ```
|
|
9
|
+
*
|
|
10
|
+
* Comments rather than a custom element because comments are legal in **every**
|
|
11
|
+
* parser insertion mode. `<lsp-mount>` inside `<tbody>` is foster-parented out
|
|
12
|
+
* of the table by the HTML parser - confirmed, not theoretical - which becomes
|
|
13
|
+
* a shipped-HTML bug the moment markers appear in static output.
|
|
14
|
+
*
|
|
15
|
+
* A pair also gives three things one element could not hold at once: rendered
|
|
16
|
+
* content, slot content, and a skeleton. Content simply lives *between* the
|
|
17
|
+
* comments as ordinary DOM.
|
|
18
|
+
*/
|
|
19
|
+
export interface MarkerPayload {
|
|
20
|
+
/** Component id. */
|
|
21
|
+
c: string;
|
|
22
|
+
/** Static attributes, which become props. */
|
|
23
|
+
props?: Record<string, string | true>;
|
|
24
|
+
/** Static presentational attributes, forwarded to the child's root. */
|
|
25
|
+
attrs?: Record<string, string>;
|
|
26
|
+
/** `@event="handler"`, evaluated in the parent's scope. */
|
|
27
|
+
on?: Record<string, string>;
|
|
28
|
+
/** `$name="expr"` - dynamic props, plus class/style for the root. */
|
|
29
|
+
bind?: Record<string, string>;
|
|
30
|
+
/**
|
|
31
|
+
* `$cell:name="key"` - a prop the **child** subscribes to, by cell key.
|
|
32
|
+
*
|
|
33
|
+
* Written at the usage site like a prop, delivered like a cell: nothing is
|
|
34
|
+
* evaluated in the parent's scope, so the parent is not in the propagation
|
|
35
|
+
* path and can be flattened, unmounted or never mounted at all. See ADR-053.
|
|
36
|
+
*/
|
|
37
|
+
cells?: Record<string, string>;
|
|
38
|
+
/**
|
|
39
|
+
* `<Dynamic $is="expr" />` - the component is named by an expression, not by
|
|
40
|
+
* the tag.
|
|
41
|
+
*
|
|
42
|
+
* `c` is empty in this case and filled in at mount, once the expression can
|
|
43
|
+
* be evaluated against the owner's scope. Everything downstream - props,
|
|
44
|
+
* slots, `$hydrate`, `$needs` - behaves exactly as it would for a tag.
|
|
45
|
+
*/
|
|
46
|
+
dyn?: string;
|
|
47
|
+
/** `$hydrate` keyword: when the logic runs. */
|
|
48
|
+
hydrate?: string;
|
|
49
|
+
/**
|
|
50
|
+
* `$needs` - the cells this usage waits on. See ADR-010.
|
|
51
|
+
*
|
|
52
|
+
* Independent of `hydrate`, which says *when* the logic runs. This says what
|
|
53
|
+
* has to be true before there is anything to render. The skeleton is the
|
|
54
|
+
* consequence, not the declaration.
|
|
55
|
+
*
|
|
56
|
+
* Present but empty is `$needs` with no value: "wait for me, I fetch
|
|
57
|
+
* internally". It names nothing resolvable, so it suppresses build rendering
|
|
58
|
+
* and imposes no runtime gate.
|
|
59
|
+
*/
|
|
60
|
+
needs?: string[];
|
|
61
|
+
/** Set by the build: the content between the markers is rendered output. */
|
|
62
|
+
ssr?: number;
|
|
63
|
+
}
|
|
64
|
+
export declare const MARKER_END = "<!--/lsp-->";
|
|
65
|
+
/**
|
|
66
|
+
* A comment may not contain `--`, so any run of dashes in the payload is
|
|
67
|
+
* escaped. `-` parses back to `-`, so the round trip is lossless.
|
|
68
|
+
*/
|
|
69
|
+
export declare function serializeMarker(payload: MarkerPayload): string;
|
|
70
|
+
export declare function isMarkerStart(node: Node): boolean;
|
|
71
|
+
export declare function isMarkerEnd(node: Node): boolean;
|
|
72
|
+
export declare function parseMarker(node: Node): MarkerPayload | null;
|
|
73
|
+
/**
|
|
74
|
+
* Index of the `<!--/lsp-->` closing `nodes[start]`, accounting for nesting.
|
|
75
|
+
* Returns -1 if unterminated.
|
|
76
|
+
*/
|
|
77
|
+
export declare function findMarkerEnd(nodes: Node[], start: number): number;
|
|
78
|
+
/**
|
|
79
|
+
* The slot content for a marker.
|
|
80
|
+
*
|
|
81
|
+
* For a server-rendered island the nodes between the comments are *output*,
|
|
82
|
+
* not slot content - the original is stashed in an inert
|
|
83
|
+
* `<template data-lsp-slot>` so the client can rebuild from it.
|
|
84
|
+
*/
|
|
85
|
+
export declare function slotNodesFor(payload: MarkerPayload, between: Node[]): Node[];
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layouts - shared document structure for multi-page projects.
|
|
3
|
+
*
|
|
4
|
+
* A multi-page project repeats its doctype, head, stylesheet links, chrome and
|
|
5
|
+
* script tags in every page. A layout holds that once; pages declare which one
|
|
6
|
+
* they use and supply only their content.
|
|
7
|
+
*
|
|
8
|
+
* This is deliberately the *same* vocabulary as component slots - `<slot>`,
|
|
9
|
+
* `<slot name>`, `<template slot>`, fallback content - rather than a second
|
|
10
|
+
* composition concept to learn. The difference is only when it happens:
|
|
11
|
+
* layouts are substituted at build, so the output is what you would have
|
|
12
|
+
* written by hand and nothing about layouts survives into the page.
|
|
13
|
+
*/
|
|
14
|
+
export declare const LAYOUT_OPT_OUT = "none";
|
|
15
|
+
/**
|
|
16
|
+
* The layout name a page asks for, or null if it does not ask.
|
|
17
|
+
*
|
|
18
|
+
* Null means "decide by convention" - not "no layout". Only an explicit
|
|
19
|
+
* `none` opts out, so a page can always escape the convention.
|
|
20
|
+
*/
|
|
21
|
+
export declare function readLayoutName(html: string): string | null;
|
|
22
|
+
export declare function stripLayoutDeclaration(html: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* Substitute a page into its layout.
|
|
25
|
+
*
|
|
26
|
+
* Unfilled slots keep their fallback content, which is what makes a layout
|
|
27
|
+
* able to carry a sensible default title or empty chrome.
|
|
28
|
+
*/
|
|
29
|
+
export declare function applyLayout(page: string, layout: string, context?: {
|
|
30
|
+
page?: string;
|
|
31
|
+
layout?: string;
|
|
32
|
+
}): string;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export interface LSPComponent {
|
|
2
|
+
view: string;
|
|
3
|
+
skeleton: string;
|
|
4
|
+
logic: string;
|
|
5
|
+
/** `<style>` - inlined with the page. Anything affecting size or layout. */
|
|
6
|
+
style: string;
|
|
7
|
+
/**
|
|
8
|
+
* `<style deferred>` - shipped with the component and applied when it
|
|
9
|
+
* hydrates. Hover, focus, transitions: nothing can be in those states before
|
|
10
|
+
* hydration, so they cost nothing up front.
|
|
11
|
+
*/
|
|
12
|
+
styleDeferred: string;
|
|
13
|
+
/**
|
|
14
|
+
* Files a `<style src="…">` asked for, in the order they were written.
|
|
15
|
+
*
|
|
16
|
+
* Unresolved here: parsing stays pure, and reading a file is the caller's
|
|
17
|
+
* business. `inlineStyleSources` does the joining, given something that can
|
|
18
|
+
* read.
|
|
19
|
+
*/
|
|
20
|
+
styleSources: string[];
|
|
21
|
+
styleDeferredSources: string[];
|
|
22
|
+
/**
|
|
23
|
+
* `<needs>` - cell keys this component cannot render without.
|
|
24
|
+
*
|
|
25
|
+
* Declared by the component rather than by every place that uses it. A
|
|
26
|
+
* caller should not have to know what its data dependencies are, and
|
|
27
|
+
* repeating the key at each usage is the same coupling a prop chain has.
|
|
28
|
+
*
|
|
29
|
+
* Read by the compiler, not at runtime: gating decides whether the logic
|
|
30
|
+
* runs at all, so it has to be knowable before the logic exists.
|
|
31
|
+
*/
|
|
32
|
+
needs: string[];
|
|
33
|
+
filename: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Parse a Lightspeed Single File Component (.lsp)
|
|
37
|
+
*/
|
|
38
|
+
export declare function parseLSP(content: string, filename: string): LSPComponent;
|
|
39
|
+
/**
|
|
40
|
+
* Fold every `<style src="…">` into the component's own styles.
|
|
41
|
+
*
|
|
42
|
+
* Shared rules are **inlined per component**, not emitted once: each copy is
|
|
43
|
+
* scoped to the component that asked for it, so sharing a file never means
|
|
44
|
+
* sharing a selector. Two components with the same rules get two scoped
|
|
45
|
+
* copies, which is what stops one of them ever styling the other's markup.
|
|
46
|
+
*
|
|
47
|
+
* Shared text comes first, so a component's own rules win. Everything is
|
|
48
|
+
* scoped with `:where()` and therefore weighs nothing, which means source
|
|
49
|
+
* order is the whole of the cascade here - "mine, after theirs" is the only
|
|
50
|
+
* override rule an author needs to know.
|
|
51
|
+
*/
|
|
52
|
+
export declare function inlineStyleSources(component: LSPComponent, read: (specifier: string, from: string) => string): LSPComponent;
|
|
53
|
+
/**
|
|
54
|
+
* Validate that required sections exist
|
|
55
|
+
*/
|
|
56
|
+
export declare function validateLSP(component: LSPComponent): string[];
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route patterns - `/blog/:slug`.
|
|
3
|
+
*
|
|
4
|
+
* A page declares the shape of its URL, and the build turns that into real
|
|
5
|
+
* files wherever it can enumerate the values. Nothing here does client-side
|
|
6
|
+
* navigation: links are ordinary links and every navigation is a full page
|
|
7
|
+
* load. The only thing a pattern buys is that data can live in the *path*
|
|
8
|
+
* rather than a query string.
|
|
9
|
+
*/
|
|
10
|
+
export interface RouteDeclaration {
|
|
11
|
+
/** Absent when the routes tree supplies it. */
|
|
12
|
+
pattern?: string;
|
|
13
|
+
/**
|
|
14
|
+
* The build-time cell holding the rows to enumerate, if any.
|
|
15
|
+
*
|
|
16
|
+
* With it, the build emits one real file per row. Without it the URLs are
|
|
17
|
+
* not knowable at build - an admin editing arbitrary entries - and the page
|
|
18
|
+
* ships as a single shell the host rewrites onto, with params read from the
|
|
19
|
+
* path at runtime.
|
|
20
|
+
*/
|
|
21
|
+
from?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface RouteSegment {
|
|
24
|
+
/** Literal text, or the parameter name when `param` is true. */
|
|
25
|
+
value: string;
|
|
26
|
+
param: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface ParsedRoute {
|
|
29
|
+
pattern: string;
|
|
30
|
+
segments: RouteSegment[];
|
|
31
|
+
params: string[];
|
|
32
|
+
/**
|
|
33
|
+
* Every URL matching this pattern has the same number of segments.
|
|
34
|
+
*
|
|
35
|
+
* False only for a catch-all. It matters because a rewritten page is one
|
|
36
|
+
* file answering many URLs: with a relative `base`, asset paths resolve
|
|
37
|
+
* against the URL's depth rather than the file's, so a varying depth cannot
|
|
38
|
+
* be made to work.
|
|
39
|
+
*/
|
|
40
|
+
fixedDepth: boolean;
|
|
41
|
+
}
|
|
42
|
+
export declare function readRouteDeclaration(html: string): RouteDeclaration | null;
|
|
43
|
+
export declare function stripRouteDeclaration(html: string): string;
|
|
44
|
+
export declare function parseRoute(pattern: string): ParsedRoute;
|
|
45
|
+
/** `/blog/:slug` + `{ slug: "post-1" }` → `/blog/post-1` */
|
|
46
|
+
export declare function buildPath(route: ParsedRoute, params: Record<string, unknown>): string;
|
|
47
|
+
/** `/blog/:slug` + `/blog/post-1` → `{ slug: "post-1" }`, or null. */
|
|
48
|
+
export declare function matchPath(route: ParsedRoute, pathname: string): Record<string, string> | null;
|
|
49
|
+
/**
|
|
50
|
+
* The file a URL is served from: `/blog/post-1` → `blog/post-1/index.html`.
|
|
51
|
+
*
|
|
52
|
+
* Directory output rather than `blog/post-1.html` so the URL carries no
|
|
53
|
+
* extension, which every static host serves without configuration.
|
|
54
|
+
*/
|
|
55
|
+
export declare function outputFileFor(path: string): string;
|
|
56
|
+
/**
|
|
57
|
+
* How many directories deep a URL sits, which is what a relative `base`
|
|
58
|
+
* resolves asset paths against.
|
|
59
|
+
*/
|
|
60
|
+
export declare function depthOf(path: string): number;
|
|
61
|
+
/** The directory whose tree defines a project's URLs. */
|
|
62
|
+
export declare const ROUTES_DIR = "routes";
|
|
63
|
+
/**
|
|
64
|
+
* The URL a page answers, from where it sits in the routes tree.
|
|
65
|
+
*
|
|
66
|
+
* `[param]` is a segment, `[...rest]` a catch-all, and a trailing `index` is
|
|
67
|
+
* the folder itself. The tree *is* the route map, so finding a page's URL is
|
|
68
|
+
* reading its path rather than opening it.
|
|
69
|
+
*
|
|
70
|
+
* index.html → /
|
|
71
|
+
* about.html → /about
|
|
72
|
+
* blog/index.html → /blog
|
|
73
|
+
* collections/[collection]/index.html → /collections/:collection
|
|
74
|
+
* collections/[collection]/[id].html → /collections/:collection/:id
|
|
75
|
+
* docs/[...rest].html → /docs/*
|
|
76
|
+
*/
|
|
77
|
+
export declare function routeFromFile(relative: string): string;
|
|
78
|
+
/** Does this path carry dynamic segments, or is it a fixed page? */
|
|
79
|
+
export declare function isDynamicRoute(relative: string): boolean;
|
|
80
|
+
/**
|
|
81
|
+
* A stable file for a runtime-resolved page.
|
|
82
|
+
*
|
|
83
|
+
* One shell answers many URLs, so it cannot live at any of them. It goes
|
|
84
|
+
* somewhere clearly internal instead, and the host rewrites onto it. Depth is
|
|
85
|
+
* irrelevant because a runtime route already requires an absolute `base`.
|
|
86
|
+
*/
|
|
87
|
+
export declare function shellFileFor(pattern: string): string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read a `<style src="…">` from disk, relative to the `.lsp` that asked.
|
|
3
|
+
*
|
|
4
|
+
* Kept out of `parser/index.ts` on purpose: that module is in the graph of
|
|
5
|
+
* `lightspeed/compile`, which a consumer may bundle for a browser, and `fs`
|
|
6
|
+
* has no business travelling there. Anything with a filesystem imports this
|
|
7
|
+
* one directly.
|
|
8
|
+
*
|
|
9
|
+
* A missing file throws, naming both ends. The failure this replaces is a
|
|
10
|
+
* component compiling with its styles silently absent - which is exactly the
|
|
11
|
+
* bug that made shared styles worth having.
|
|
12
|
+
*/
|
|
13
|
+
export declare function nodeStyleReader(): (specifier: string, from: string) => string;
|
|
14
|
+
/** Where a `<style src="…">` resolves to, for watching it. */
|
|
15
|
+
export declare function styleSourcePaths(component: {
|
|
16
|
+
filename: string;
|
|
17
|
+
styleSources: string[];
|
|
18
|
+
styleDeferredSources: string[];
|
|
19
|
+
}): string[];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build-time CSS scoping.
|
|
3
|
+
*
|
|
4
|
+
* Every component's styles are scoped by default: its selectors gain an
|
|
5
|
+
* attribute matching a per-component hash, and the runtime stamps that same
|
|
6
|
+
* attribute on the elements the component owns. `:global(...)` opts a part of a
|
|
7
|
+
* selector back out.
|
|
8
|
+
*
|
|
9
|
+
* No shadow DOM and no runtime cost - which is what let ADR-003 reject custom
|
|
10
|
+
* elements without giving up encapsulation.
|
|
11
|
+
*/
|
|
12
|
+
/** A short, stable id for a component. */
|
|
13
|
+
export declare function scopeIdFor(componentId: string): string;
|
|
14
|
+
export declare function scopeStyles(css: string, scopeId: string): string;
|
|
15
|
+
export interface Scopes {
|
|
16
|
+
/** Zero-specificity scope for ordinary rules. */
|
|
17
|
+
normal: string;
|
|
18
|
+
/** Weighted anchor for `:deep()` overrides. */
|
|
19
|
+
deep: string;
|
|
20
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
interface ComponentRegistry {
|
|
2
|
+
[key: string]: {
|
|
3
|
+
template: string;
|
|
4
|
+
style: string;
|
|
5
|
+
logic: string;
|
|
6
|
+
/** Cell keys the component itself declared - see `LSPComponent.needs`. */
|
|
7
|
+
needs?: string[];
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Transform Lightspeed template syntax to browser-ready HTML.
|
|
12
|
+
*
|
|
13
|
+
* Child components are NOT inlined here. They become <lsp-mount> placeholders
|
|
14
|
+
* that the runtime replaces with a real, independently-stated instance. Text
|
|
15
|
+
* spliced at build time has no identity, which is what made every usage of a
|
|
16
|
+
* component share one state object.
|
|
17
|
+
*/
|
|
18
|
+
export declare function transformTemplate(template: string, componentId: string, componentRegistry?: ComponentRegistry): string;
|
|
19
|
+
/**
|
|
20
|
+
* Extract reactive expressions from template for analysis
|
|
21
|
+
*/
|
|
22
|
+
export declare function extractReactiveExpressions(template: string): string[];
|
|
23
|
+
/**
|
|
24
|
+
* Extract import statements from component logic for dependency tracking
|
|
25
|
+
*/
|
|
26
|
+
export declare function extractImports(logic: string): Array<{
|
|
27
|
+
variable: string;
|
|
28
|
+
path: string;
|
|
29
|
+
type: "default" | "named" | "star";
|
|
30
|
+
}>;
|
|
31
|
+
export declare function validateTemplate(template: string): string[];
|
|
32
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import type { ComponentInstance } from "../components/ComponentInstance.js";
|
|
2
|
+
import type { CellManager } from "../state/index.js";
|
|
3
|
+
import type { BindingHolder, ForBlock, Row } from "../dom/ForBlock.js";
|
|
4
|
+
export type BindingKind = "text" | "class" | "attr" | "prop" | "model" | "for" | "show" | "html";
|
|
5
|
+
type Compiled = (state: any, props: any, shared: any, pending: (key: string) => boolean, ...locals: any[]) => any;
|
|
6
|
+
interface BaseBinding {
|
|
7
|
+
expr: string;
|
|
8
|
+
compiled: Compiled;
|
|
9
|
+
/** Cell keys this expression read last time it was evaluated. */
|
|
10
|
+
cellKeys?: Set<string>;
|
|
11
|
+
cellUnsubscribes?: Array<() => void>;
|
|
12
|
+
cleanupRegistered?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface ElementBinding extends BaseBinding {
|
|
15
|
+
kind: "text" | "class" | "attr" | "show" | "html";
|
|
16
|
+
el: Element;
|
|
17
|
+
/** Target attribute name, for kind "attr". */
|
|
18
|
+
attr?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A binding whose target is a child component's prop rather than an element.
|
|
22
|
+
* Owned by the parent and evaluated in the parent's scope.
|
|
23
|
+
*/
|
|
24
|
+
export interface PropBinding extends BaseBinding {
|
|
25
|
+
kind: "prop";
|
|
26
|
+
target: ComponentInstance;
|
|
27
|
+
name: string;
|
|
28
|
+
}
|
|
29
|
+
export interface ModelTarget {
|
|
30
|
+
/** DOM *property* - not an attribute. */
|
|
31
|
+
prop: "value" | "checked";
|
|
32
|
+
event: string;
|
|
33
|
+
coerce: (raw: any) => any;
|
|
34
|
+
radio?: boolean;
|
|
35
|
+
}
|
|
36
|
+
export interface ModelBinding extends BaseBinding {
|
|
37
|
+
kind: "model";
|
|
38
|
+
el: Element;
|
|
39
|
+
target: ModelTarget;
|
|
40
|
+
write: (state: any, props: any, shared: any, pending: (key: string) => boolean, value: any, ...locals: any[]) => void;
|
|
41
|
+
}
|
|
42
|
+
/** Re-evaluates a `$for` collection and hands it to the block to reconcile. */
|
|
43
|
+
export interface ForBinding extends BaseBinding {
|
|
44
|
+
kind: "for";
|
|
45
|
+
block: ForBlock;
|
|
46
|
+
}
|
|
47
|
+
export type Binding = ElementBinding | PropBinding | ModelBinding | ForBinding;
|
|
48
|
+
/**
|
|
49
|
+
* Where an expression is evaluated.
|
|
50
|
+
*
|
|
51
|
+
* Rows share their component's state and props but carry their own loop
|
|
52
|
+
* variables and their own cleanup, so removing a row detaches exactly its
|
|
53
|
+
* listeners and cell subscriptions.
|
|
54
|
+
*/
|
|
55
|
+
export declare class BindingScope {
|
|
56
|
+
instance: ComponentInstance;
|
|
57
|
+
locals: any[];
|
|
58
|
+
cleanup: BindingHolder;
|
|
59
|
+
constructor(instance: ComponentInstance, locals: any[], cleanup: BindingHolder);
|
|
60
|
+
get id(): string;
|
|
61
|
+
get state(): any;
|
|
62
|
+
get props(): any;
|
|
63
|
+
}
|
|
64
|
+
export declare function resolveModelTarget(el: Element): ModelTarget;
|
|
65
|
+
/**
|
|
66
|
+
* Compiles and applies template bindings.
|
|
67
|
+
*
|
|
68
|
+
* Bindings are collected once at mount and stored on whatever owns their scope
|
|
69
|
+
* - a component instance, or a single `$for` row. An update touches only that
|
|
70
|
+
* owner's elements. Nothing here queries the document.
|
|
71
|
+
*/
|
|
72
|
+
export declare class BindingEvaluator {
|
|
73
|
+
private cells;
|
|
74
|
+
private onPropChanged;
|
|
75
|
+
private compiledCache;
|
|
76
|
+
private writerCache;
|
|
77
|
+
/**
|
|
78
|
+
* @param onPropChanged Queues the child whose prop changed. Routed through
|
|
79
|
+
* the runtime's scheduler so prop cascades batch per frame like any other
|
|
80
|
+
* update, instead of recursing.
|
|
81
|
+
*/
|
|
82
|
+
constructor(cells: CellManager, onPropChanged?: (instance: ComponentInstance) => void);
|
|
83
|
+
/**
|
|
84
|
+
* Loop aliases become function parameters, so `{{ item.name }}` resolves
|
|
85
|
+
* without a scope object or `with`. The cache keys on the parameter list too
|
|
86
|
+
* - the same expression means different things inside different blocks.
|
|
87
|
+
*/
|
|
88
|
+
/** Compiled by the build, merged as components and pages register. */
|
|
89
|
+
private expressions;
|
|
90
|
+
private writers;
|
|
91
|
+
registerExpressions(expressions?: Record<string, any>, writers?: Record<string, any>): void;
|
|
92
|
+
/**
|
|
93
|
+
* The function the build compiled for this expression.
|
|
94
|
+
*
|
|
95
|
+
* Not compiled here: `new Function` is what forces a page to allow
|
|
96
|
+
* `unsafe-eval`. The build emits a table keyed exactly as this cache was
|
|
97
|
+
* keyed, so a lookup replaces a compile. See [[ADR-016]].
|
|
98
|
+
*/
|
|
99
|
+
private compile;
|
|
100
|
+
private compileWriter;
|
|
101
|
+
createBinding(el: Element, kind: "text" | "class" | "attr" | "show" | "html", expr: string, attr?: string, params?: string[]): ElementBinding;
|
|
102
|
+
createPropBinding(target: ComponentInstance, name: string, expr: string, params?: string[]): PropBinding;
|
|
103
|
+
/**
|
|
104
|
+
* `$cell:count="ui:clicks"` - hand a cell to a child as one of its props.
|
|
105
|
+
*
|
|
106
|
+
* Not a binding, deliberately. A binding is owned by whoever wrote the
|
|
107
|
+
* expression, and for a prop that is the *parent* - so the value only
|
|
108
|
+
* arrives while the parent is mounted and still evaluating. Here the
|
|
109
|
+
* subscription belongs to the child, which is the whole point: it survives
|
|
110
|
+
* the parent being flattened to HTML, never mounting, or mounting long
|
|
111
|
+
* after this one did.
|
|
112
|
+
*
|
|
113
|
+
* A pending cell writes nothing, so a declared default still stands. That is
|
|
114
|
+
* the ADR-005 distinction doing its job - "nobody has said yet" is not the
|
|
115
|
+
* same as "the answer is undefined".
|
|
116
|
+
*/
|
|
117
|
+
bindCellProp(target: ComponentInstance, name: string, key: string): void;
|
|
118
|
+
createModelBinding(el: Element, expr: string, params?: string[]): ModelBinding;
|
|
119
|
+
createForBinding(block: ForBlock, expr: string, params?: string[]): ForBinding;
|
|
120
|
+
/** A plain read view of the cell namespace, with no dependency recording. */
|
|
121
|
+
readProxy(): any;
|
|
122
|
+
/**
|
|
123
|
+
* `pending('key')` in a template expression.
|
|
124
|
+
*
|
|
125
|
+
* Must record a read exactly like the cell proxy does. Without that a
|
|
126
|
+
* `$if="pending('x')"` would register no dependency, never re-evaluate when
|
|
127
|
+
* the cell fills, and leave a permanent placeholder with no error - which is
|
|
128
|
+
* why this shares `onRead` with the proxy rather than reaching for the cell
|
|
129
|
+
* directly.
|
|
130
|
+
*/
|
|
131
|
+
pendingProbe(onRead?: (key: string) => void): (key: string) => boolean;
|
|
132
|
+
/** For `$needs`, which asks about cell readiness rather than values. */
|
|
133
|
+
get cellManager(): CellManager;
|
|
134
|
+
/** Compile a standalone expression, e.g. a `$key`. */
|
|
135
|
+
compileExpression(expr: string, params?: string[]): Compiled;
|
|
136
|
+
evaluate(expr: string, instance: ComponentInstance, locals?: any[]): any;
|
|
137
|
+
applyBinding(binding: Binding, scope: BindingScope): void;
|
|
138
|
+
/** Re-evaluate everything owned by this instance, including its blocks. */
|
|
139
|
+
updateInstance(instance: ComponentInstance): void;
|
|
140
|
+
updateRow(instance: ComponentInstance, row: Row): void;
|
|
141
|
+
private updateBlock;
|
|
142
|
+
/** Re-evaluate an instance and everything below it. */
|
|
143
|
+
updateTree(instance: ComponentInstance): void;
|
|
144
|
+
/** Listen for edits and push them back into state. */
|
|
145
|
+
attachModel(binding: ModelBinding, scope: BindingScope): void;
|
|
146
|
+
private readFromDom;
|
|
147
|
+
private apply;
|
|
148
|
+
private applyModel;
|
|
149
|
+
/**
|
|
150
|
+
* Keep a binding subscribed to precisely the cells it read.
|
|
151
|
+
*
|
|
152
|
+
* Re-derived on every evaluation so conditional reads stay correct - an
|
|
153
|
+
* expression like `shared.a ? shared.b : shared.c` touches different cells
|
|
154
|
+
* depending on the value of the first.
|
|
155
|
+
*/
|
|
156
|
+
private syncCellSubscriptions;
|
|
157
|
+
/** What a string `$class` last added, so it can take it back. */
|
|
158
|
+
private appliedClasses;
|
|
159
|
+
private updateClasses;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* `$show` - hide without destroying.
|
|
163
|
+
*
|
|
164
|
+
* The counterpart to `$if`, which rebuilds its subtree and takes any DOM state
|
|
165
|
+
* with it (ADR-007). A half-typed input, a scroll position or a focused field
|
|
166
|
+
* inside a toggled region survives this and does not survive `$if`.
|
|
167
|
+
*
|
|
168
|
+
* The element's own display value is remembered the first time it is hidden,
|
|
169
|
+
* so restoring never has to guess - clearing the property outright would
|
|
170
|
+
* discard an inline `display` the author set.
|
|
171
|
+
*/
|
|
172
|
+
/** Marks an element hidden by `$show`. Paired with HIDDEN_RULE in the CSS. */
|
|
173
|
+
export declare const HIDDEN_ATTR = "data-lsp-hidden";
|
|
174
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface ImportInfo {
|
|
2
|
+
variable: string;
|
|
3
|
+
path: string;
|
|
4
|
+
type: "default" | "named" | "star";
|
|
5
|
+
}
|
|
6
|
+
export declare class ImportTransformer {
|
|
7
|
+
transformImports(logic: string): string;
|
|
8
|
+
private pathToComponentId;
|
|
9
|
+
extractImports(logic: string): ImportInfo[];
|
|
10
|
+
}
|
|
11
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ImportTransformer } from "./ImportTransformer.js";
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { SignalManager } from "../signals/index.js";
|
|
2
|
+
import type { Binding } from "../bindings/index.js";
|
|
3
|
+
import type { BindingHolder, ForBlock } from "../dom/ForBlock.js";
|
|
4
|
+
export declare class ComponentInstance implements BindingHolder {
|
|
5
|
+
/** Unique per mount, e.g. "button#2". Keys the runtime's instance map. */
|
|
6
|
+
id: string;
|
|
7
|
+
/** The component definition this was mounted from, e.g. "button". */
|
|
8
|
+
componentId: string;
|
|
9
|
+
parent: ComponentInstance | null;
|
|
10
|
+
children: ComponentInstance[];
|
|
11
|
+
/** Top-level nodes this instance owns in the DOM. */
|
|
12
|
+
rootNodes: Node[];
|
|
13
|
+
/**
|
|
14
|
+
* Elements this component named with `$ref`, by name.
|
|
15
|
+
*
|
|
16
|
+
* Filled while the DOM is collected - which is *after* `setup` runs - so
|
|
17
|
+
* logic reads them in `onMounted` and not before. The object identity is
|
|
18
|
+
* stable, so a component can hold the reference it was handed at setup and
|
|
19
|
+
* find it populated later.
|
|
20
|
+
*/
|
|
21
|
+
refs: Record<string, Element>;
|
|
22
|
+
/**
|
|
23
|
+
* `onMounted` callbacks, waiting for this instance's nodes to be in the
|
|
24
|
+
* document.
|
|
25
|
+
*
|
|
26
|
+
* Held rather than run, because logic runs *before* the DOM exists: `setup`
|
|
27
|
+
* happens first, then the template is built or adopted, then the nodes are
|
|
28
|
+
* inserted. A callback that measures something, focuses something or
|
|
29
|
+
* observes a node needs the last of those to have happened.
|
|
30
|
+
*/
|
|
31
|
+
mountedCallbacks: Function[];
|
|
32
|
+
/**
|
|
33
|
+
* Cleanups registered by the component's logic - signal listeners, cell
|
|
34
|
+
* subscriptions, `onUnmounted`. See {@link addLogicCleanup}.
|
|
35
|
+
*/
|
|
36
|
+
logicCleanup: Function[];
|
|
37
|
+
/** Bindings whose expressions evaluate against THIS instance's scope. */
|
|
38
|
+
bindings: Binding[];
|
|
39
|
+
/** `$for` blocks declared directly in this component's template. */
|
|
40
|
+
blocks: ForBlock[];
|
|
41
|
+
/** Style scope hash for the component this was mounted from. */
|
|
42
|
+
scopeId?: string;
|
|
43
|
+
props: any;
|
|
44
|
+
state: any;
|
|
45
|
+
handlers: Map<string, Function>;
|
|
46
|
+
/**
|
|
47
|
+
* `onUpdate` callbacks, run *before* this instance's bindings re-evaluate.
|
|
48
|
+
*
|
|
49
|
+
* Before rather than after, deliberately: a callback exists to recompute
|
|
50
|
+
* state a binding is about to read, so running it first means the same pass
|
|
51
|
+
* picks it up - and a write from inside it cannot schedule a second pass to
|
|
52
|
+
* do what this one was already going to do.
|
|
53
|
+
*/
|
|
54
|
+
updateCallbacks: Function[];
|
|
55
|
+
/**
|
|
56
|
+
* Handler names an event expression is compiled with, fixed at build.
|
|
57
|
+
*
|
|
58
|
+
* Not `handlers.keys()`: that is whatever the instance registered, which the
|
|
59
|
+
* build cannot know for a handler created at runtime. See [[ADR-016]].
|
|
60
|
+
*/
|
|
61
|
+
handlerNames: string[];
|
|
62
|
+
signals: SignalManager;
|
|
63
|
+
cleanup: Function[];
|
|
64
|
+
/**
|
|
65
|
+
* Evidence that this instance needs the browser.
|
|
66
|
+
*
|
|
67
|
+
* Recorded while it renders rather than inferred from its source: component
|
|
68
|
+
* logic is arbitrary JavaScript, and what it *did* is knowable where what it
|
|
69
|
+
* *might do* is not. A build that watched an instance mount and saw it
|
|
70
|
+
* register nothing has better grounds than any parser.
|
|
71
|
+
*
|
|
72
|
+
* Not a guarantee - a listener attached from a promise is invisible here -
|
|
73
|
+
* which is why this decides a warning today and not what ships.
|
|
74
|
+
*/
|
|
75
|
+
listeners: number;
|
|
76
|
+
models: number;
|
|
77
|
+
/** `onMounted` was asked for. It never runs at build, but wanting it counts. */
|
|
78
|
+
wantsMount: boolean;
|
|
79
|
+
/** Cells it declared that are not fetched at build. */
|
|
80
|
+
runtimeCells: Set<string>;
|
|
81
|
+
constructor(id: string, componentId: string, signals: SignalManager, parent?: ComponentInstance | null);
|
|
82
|
+
addHandler(name: string, fn: Function): void;
|
|
83
|
+
getHandler(name: string): Function | undefined;
|
|
84
|
+
hasHandler(name: string): boolean;
|
|
85
|
+
addCleanup(cleanup: Function): void;
|
|
86
|
+
/**
|
|
87
|
+
* A cleanup that undoes something the component's *logic* set up.
|
|
88
|
+
*
|
|
89
|
+
* Kept apart from the DOM-owned ones because the two have opposite fates on
|
|
90
|
+
* a rebuild. When adoption bails, the rendered nodes are thrown away and
|
|
91
|
+
* everything attached to them has to go with them - but **the logic has
|
|
92
|
+
* already run and will not run again**, so anything it registered is the
|
|
93
|
+
* only copy there will ever be. Running these would silently unsubscribe a
|
|
94
|
+
* component from its own signals and leave it looking alive.
|
|
95
|
+
*
|
|
96
|
+
* Handlers were already protected from exactly this. Subscriptions were not.
|
|
97
|
+
*/
|
|
98
|
+
addLogicCleanup(cleanup: Function): void;
|
|
99
|
+
/** Drop everything tied to the DOM being replaced, and only that. */
|
|
100
|
+
releaseDom(): void;
|
|
101
|
+
addBinding(binding: Binding): void;
|
|
102
|
+
addBlock(block: ForBlock): void;
|
|
103
|
+
/**
|
|
104
|
+
* Tear down this instance and everything below it.
|
|
105
|
+
*
|
|
106
|
+
* Children go first so their cleanup runs while their parent is still
|
|
107
|
+
* intact. Does not touch the DOM - the mounter owns node removal.
|
|
108
|
+
*/
|
|
109
|
+
destroy(): void;
|
|
110
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ComponentDefinition } from "./types/index.js";
|
|
2
|
+
export declare class ComponentRegistry {
|
|
3
|
+
private components;
|
|
4
|
+
register(component: ComponentDefinition): void;
|
|
5
|
+
update(id: string, component: ComponentDefinition): void;
|
|
6
|
+
get(id: string): ComponentDefinition | undefined;
|
|
7
|
+
has(id: string): boolean;
|
|
8
|
+
getAllIds(): string[];
|
|
9
|
+
getAll(): ComponentDefinition[];
|
|
10
|
+
clear(): void;
|
|
11
|
+
}
|