@lightspeed-js/core 0.2.0 → 0.3.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.
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Which file answers to each component name (ADR-058).
3
+ *
4
+ * Names are a flat namespace: a component is its filename, lowercased, from
5
+ * whichever root it was found in. The first root to claim a name keeps it, and
6
+ * `src` comes first, so an app can replace a package's component.
7
+ *
8
+ * Two files answering to one name is either that deliberate override or a
9
+ * collision nobody meant, and the two look identical from the files alone. So
10
+ * the app says which it means: an override across roots has to be declared,
11
+ * and two files under the same root are always a mistake.
12
+ */
13
+ export interface FoundComponent {
14
+ /** Absolute path to the `.lsp` file. */
15
+ file: string;
16
+ /** The component root it was found under. */
17
+ root: string;
18
+ }
19
+ export type Conflict = {
20
+ kind: "same-root";
21
+ id: string;
22
+ kept: string;
23
+ other: string;
24
+ } | {
25
+ kind: "undeclared-override";
26
+ id: string;
27
+ kept: string;
28
+ other: string;
29
+ };
30
+ export interface ComponentIds {
31
+ /** Component id to the file that answers to it. */
32
+ files: Map<string, string>;
33
+ /** Declared overrides that replaced something. */
34
+ overrides: Array<{
35
+ id: string;
36
+ kept: string;
37
+ replaced: string;
38
+ }>;
39
+ conflicts: Conflict[];
40
+ /** Names in `overrides` that replace nothing - left over after a rename or removal. */
41
+ unusedOverrides: string[];
42
+ }
43
+ export declare function componentIdOf(file: string): string;
44
+ /** `found` must be in precedence order: every file of the first root, then the next. */
45
+ export declare function resolveComponentIds(found: FoundComponent[], declaredOverrides?: readonly string[]): ComponentIds;
46
+ /** One message per conflict, each ending in the change that fixes it (ADR-055). */
47
+ export declare function describeConflict(conflict: Conflict, rel: (file: string) => string): string;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * A project's components: where they are found, which file answers to each
3
+ * name, and their compiled definitions for build-time rendering.
4
+ *
5
+ * Shared by the Vite plugin and by `renderFragment` (ADR-059), so a component
6
+ * rendered into someone else's page goes through exactly the pipeline a
7
+ * Lightspeed page does. Moved out of the plugin unchanged; a second pipeline
8
+ * would be a second renderer to drift.
9
+ */
10
+ import { parseLSP } from "../parser/index.js";
11
+ import type { ComponentDefinition } from "../runtime/components/index.js";
12
+ import { type Conflict } from "./component-ids.js";
13
+ export type Compiler = "esbuild" | "tsc" | "swc";
14
+ export interface ComponentProjectOptions {
15
+ /** The project root. A function when it is only known later (Vite's configResolved). */
16
+ root: string | (() => string);
17
+ /** Roots beyond the project's own `src`, relative to the root. */
18
+ componentDirs?: string[];
19
+ /** Names the app replaces on purpose (ADR-058). */
20
+ overrides?: string[];
21
+ /** For component logic at build time. */
22
+ compiler?: Compiler;
23
+ }
24
+ /** A parsed `.lsp` file, as the registry holds it. */
25
+ export interface RegistryEntry {
26
+ template: string;
27
+ skeleton: string;
28
+ style: string;
29
+ styleDeferred: string;
30
+ logic: string;
31
+ needs?: string[];
32
+ }
33
+ export interface ComponentProject {
34
+ /** Component id to its parsed source. Swapped in whole on each rebuild. */
35
+ readonly registry: Map<string, RegistryEntry>;
36
+ /** Component id to the file that answers to it. */
37
+ readonly files: Map<string, string>;
38
+ /** Clashes from the latest rebuild (ADR-058). */
39
+ readonly conflicts: Conflict[];
40
+ /** Every root components come from, in precedence order: `src` first. */
41
+ roots(): string[];
42
+ /** Rescan the roots. Concurrent callers share one rescan. */
43
+ rebuild(): Promise<void>;
44
+ /** Compile every component, as the client's modules do, for build-time rendering. */
45
+ definitions(): Promise<ComponentDefinition[]>;
46
+ /** Compile markup that uses components (a page, or a fragment of one) into island markers. */
47
+ compileMarkup(html: string, id?: string): string;
48
+ /** Compile component logic (TypeScript) to JavaScript. */
49
+ compileLogic(code: string): Promise<string>;
50
+ }
51
+ export declare function createComponentProject(options: ComponentProjectOptions): ComponentProject;
52
+ /**
53
+ * Fold in any `<style src="…">` the component asked for.
54
+ *
55
+ * A shared block is referenced the way an import is - relative to the `.lsp`
56
+ * file - and each component that asks for it gets its own scoped copy, so
57
+ * sharing a file never means sharing a selector.
58
+ */
59
+ export declare function withSharedStyles(parsed: ReturnType<typeof parseLSP>, watch?: (file: string) => void): import("../parser/lsp.ts").LSPComponent;
60
+ /** Compile TypeScript at build time using Node.js APIs. */
61
+ export declare function compileTypeScriptAtBuildTime(code: string, compiler: string): Promise<string>;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Render Lightspeed components into a page Lightspeed doesn't own (ADR-059).
3
+ *
4
+ * A host - a docs site, a CMS preview, another framework's page - hands over
5
+ * markup that uses components (`<Button variant="secondary">Save</Button>`)
6
+ * and gets back what a Lightspeed page build would have produced for it: the
7
+ * rendered islands, the CSS, and the `<head>` payload hydration reads. The
8
+ * same component project, compiler and renderer as a page, so a fragment
9
+ * cannot render differently from the page it stands in for.
10
+ *
11
+ * In the browser the host imports `virtual:lightspeed/components` from
12
+ * `lightspeedPlugin({ host: true, fragments })` and calls
13
+ * `app.hydrate(container)` on the element it put the fragment in.
14
+ */
15
+ import type { TemplateExpression } from "../compile/expressions.js";
16
+ import type { ComponentProject, Compiler } from "../project/components.js";
17
+ export interface FragmentRendererOptions {
18
+ /** The project root: components are found in its `src`, then `componentDirs`. */
19
+ root: string;
20
+ /** Further component roots, relative to the root - as `lightspeedPlugin`'s. */
21
+ componentDirs?: string[];
22
+ /** Names the app replaces on purpose (ADR-058). */
23
+ overrides?: string[];
24
+ compiler?: Compiler;
25
+ }
26
+ export interface RenderFragmentsOptions {
27
+ /** Ship islands that need nothing from the browser as plain HTML. On by default, as for pages. */
28
+ flattenStatic?: boolean;
29
+ }
30
+ export interface RenderedFragments {
31
+ /** Each fragment's markup, rendered, in the order given. */
32
+ html: string[];
33
+ /**
34
+ * Tags for the host page's `<head>`: the manifest hydration reads, and any
35
+ * build-time state. One payload for all the fragments, because the runtime
36
+ * reads them by id, once per page.
37
+ */
38
+ head: string;
39
+ /** Scoped CSS for the components the fragments use. */
40
+ css: string;
41
+ /** Their deferred CSS (`<style deferred>`). */
42
+ cssDeferred: string;
43
+ /** Component ids the fragments reach. */
44
+ used: string[];
45
+ /** Islands rendered, and shipped as plain HTML. */
46
+ rendered: number;
47
+ flattened: number;
48
+ /**
49
+ * The fragments' own expressions (`$disabled="true"` on a component tag).
50
+ * The browser needs the same table: pass the fragments to
51
+ * `lightspeedPlugin({ host: true, fragments })`, which compiles it into
52
+ * `virtual:lightspeed/components`.
53
+ */
54
+ expressions: TemplateExpression[];
55
+ }
56
+ export interface FragmentRenderer {
57
+ render(fragments: string[], options?: RenderFragmentsOptions): Promise<RenderedFragments>;
58
+ /** The component project it renders with. */
59
+ readonly project: ComponentProject;
60
+ }
61
+ export declare function createFragmentRenderer(options: FragmentRendererOptions): FragmentRenderer;
@@ -7,6 +7,8 @@
7
7
  */
8
8
  export { renderPage } from "./render.js";
9
9
  export type { RenderPageOptions, RenderPageResult } from "./render.js";
10
+ export { createFragmentRenderer } from "./fragment.js";
11
+ export type { FragmentRenderer, FragmentRendererOptions, RenderFragmentsOptions, RenderedFragments, } from "./fragment.js";
10
12
  export { rebuildPages } from "./rebuild.js";
11
13
  export type { RebuildOptions, RebuildResult } from "./rebuild.js";
12
14
  /**
@@ -73,6 +73,22 @@ interface LightspeedPluginOptions {
73
73
  * Two files under the same root are never an override.
74
74
  */
75
75
  overrides?: string[];
76
+ /**
77
+ * Run inside a site Lightspeed doesn't own (ADR-059): an Astro site, a
78
+ * Vite app, a docs site. The plugin compiles `.lsp` files and provides
79
+ * `virtual:lightspeed/components`, and leaves the host's HTML alone: no
80
+ * page discovery, no page compiling or rendering, no route or manifest
81
+ * files. The host renders islands at build with `createFragmentRenderer`
82
+ * from `@lightspeed-js/core/ssg` and calls `app.hydrate(container)`.
83
+ */
84
+ host?: boolean;
85
+ /**
86
+ * Files of markup the host renders with `createFragmentRenderer`, relative
87
+ * to the root (globs allowed). Their own expressions - `$disabled="true"` on
88
+ * a component tag - are compiled into `virtual:lightspeed/components`, as a
89
+ * page's are, so hydration finds them.
90
+ */
91
+ fragments?: string[];
76
92
  }
77
93
  export declare function lightspeedPlugin(options?: LightspeedPluginOptions): Plugin[];
78
94
  export {};
@@ -0,0 +1 @@
1
+ export {};