@takazudo/zfb 0.1.0-next.2

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.
Files changed (45) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/LICENSE +21 -0
  3. package/README.md +207 -0
  4. package/bin/zfb.mjs +61 -0
  5. package/dist/config.d.ts +542 -0
  6. package/dist/config.d.ts.map +1 -0
  7. package/dist/config.js +24 -0
  8. package/dist/config.js.map +1 -0
  9. package/dist/content.d.ts +231 -0
  10. package/dist/content.d.ts.map +1 -0
  11. package/dist/content.js +449 -0
  12. package/dist/content.js.map +1 -0
  13. package/dist/frontmatter.d.ts +23 -0
  14. package/dist/frontmatter.d.ts.map +1 -0
  15. package/dist/frontmatter.js +142 -0
  16. package/dist/frontmatter.js.map +1 -0
  17. package/dist/index.d.ts +7 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +21 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/island.d.ts +121 -0
  22. package/dist/island.d.ts.map +1 -0
  23. package/dist/island.js +273 -0
  24. package/dist/island.js.map +1 -0
  25. package/dist/jsx-types.d.ts +37 -0
  26. package/dist/jsx-types.d.ts.map +1 -0
  27. package/dist/jsx-types.js +12 -0
  28. package/dist/jsx-types.js.map +1 -0
  29. package/dist/paginate.d.ts +43 -0
  30. package/dist/paginate.d.ts.map +1 -0
  31. package/dist/paginate.js +44 -0
  32. package/dist/paginate.js.map +1 -0
  33. package/dist/plugins.d.ts +259 -0
  34. package/dist/plugins.d.ts.map +1 -0
  35. package/dist/plugins.js +42 -0
  36. package/dist/plugins.js.map +1 -0
  37. package/dist/runtime.d.ts +101 -0
  38. package/dist/runtime.d.ts.map +1 -0
  39. package/dist/runtime.js +454 -0
  40. package/dist/runtime.js.map +1 -0
  41. package/dist/types.d.ts +27 -0
  42. package/dist/types.d.ts.map +1 -0
  43. package/dist/types.js +33 -0
  44. package/dist/types.js.map +1 -0
  45. package/package.json +98 -0
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Structural JSX element (VNode). Framework-agnostic: both Preact and React
3
+ * produce objects that satisfy this shape when a JSX element is rendered.
4
+ *
5
+ * The `props` bag may itself contain `children` (nested VNodes) as well as
6
+ * HTML attribute values (strings, numbers, booleans). All fields are widened
7
+ * to `unknown` so the type stays opaque to callers that don't know which
8
+ * framework produced the value.
9
+ *
10
+ * `type` accepts BOTH function components (call signature) AND class
11
+ * components (construct signature). Preact and React both expose
12
+ * `ComponentType = FunctionComponent | ComponentClass`, and the JSX runtime
13
+ * stores either on `.type`. Without the construct signature, valid Preact
14
+ * `<MyComponent />` expressions fail to assign to `VNode` at the framework
15
+ * boundary (e.g. inside `<Island>` children) — even though class components
16
+ * are rare in modern Preact, the `ComponentType` union includes them.
17
+ */
18
+ export type VNodeObject = {
19
+ readonly type: string | ((...args: unknown[]) => unknown) | (new (...args: unknown[]) => unknown);
20
+ readonly props: Readonly<Record<string, unknown>>;
21
+ readonly key: unknown;
22
+ };
23
+ /**
24
+ * A renderable JSX node: a primitive, `null`, `undefined`, an array, or a
25
+ * structured VNode object.
26
+ *
27
+ * This union covers every value both Preact and React treat as valid
28
+ * `children`. Importantly it does **not** reference any framework-specific
29
+ * type, so packages that depend on `zfb` need not install a React / Preact
30
+ * `@types` package to satisfy this type.
31
+ */
32
+ export type VNode = string | number | boolean | null | undefined | bigint | VNodeArray | VNodeObject;
33
+ export interface VNodeArray extends ReadonlyArray<VNode> {
34
+ }
35
+ /** @deprecated Use `VNode` instead. */
36
+ export type ReactNode = VNode;
37
+ //# sourceMappingURL=jsx-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsx-types.d.ts","sourceRoot":"","sources":["../src/jsx-types.ts"],"names":[],"mappings":"AAWA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,GAAG,CAAC,KAAK,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,CAAC;IAClG,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAClD,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,MAAM,KAAK,GACb,MAAM,GACN,MAAM,GACN,OAAO,GACP,IAAI,GACJ,SAAS,GACT,MAAM,GACN,UAAU,GACV,WAAW,CAAC;AAEhB,MAAM,WAAW,UAAW,SAAQ,aAAa,CAAC,KAAK,CAAC;CAAG;AAK3D,uCAAuC;AACvC,MAAM,MAAM,SAAS,GAAG,KAAK,CAAC"}
@@ -0,0 +1,12 @@
1
+ // Lightweight ambient JSX types so the public Island wrapper does not
2
+ // hard-depend on either preact or react as a runtime/types dependency.
3
+ //
4
+ // BCI-4: `IslandProps.children` is now typed as `VNode` (a structural union)
5
+ // rather than the old `ReactNode` alias, so non-React frameworks can
6
+ // implement `IslandProps` without any React type dependency at the type level.
7
+ //
8
+ // Both Preact and React's `children` accept this structural union; the build
9
+ // is type-erased so the actual rendering happens in the user's app under
10
+ // whichever framework adapter they chose.
11
+ export {};
12
+ //# sourceMappingURL=jsx-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsx-types.js","sourceRoot":"","sources":["../src/jsx-types.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,uEAAuE;AACvE,EAAE;AACF,6EAA6E;AAC7E,qEAAqE;AACrE,+EAA+E;AAC/E,EAAE;AACF,6EAA6E;AAC7E,yEAAyE;AACzE,0CAA0C"}
@@ -0,0 +1,43 @@
1
+ /** Options accepted by [`paginate`]. */
2
+ export type PaginateOptions<K extends string = string> = {
3
+ /** Items per page. Must be a positive integer (>= 1). */
4
+ pageSize: number;
5
+ /** Name of the dynamic param to fill (e.g. `"page"` for `[page].tsx`). */
6
+ param: K;
7
+ };
8
+ /** One page of paginated results. The shape consumed by the page component. */
9
+ export type PaginatedPage<T> = {
10
+ /** Items belonging to this page. */
11
+ data: T[];
12
+ /** 1-based page number. */
13
+ page: number;
14
+ /** Total number of pages (>= 1). */
15
+ lastPage: number;
16
+ /** Items per page (echoed for convenience). */
17
+ pageSize: number;
18
+ /** Total item count across all pages. */
19
+ total: number;
20
+ };
21
+ /**
22
+ * Route entry returned by [`paginate`], one per page.
23
+ *
24
+ * The `K` generic carries the literal param name through, so consumers
25
+ * accessing e.g. `route.params.page` get `string` rather than
26
+ * `string | undefined` under `noUncheckedIndexedAccess`.
27
+ */
28
+ export type PaginateRoute<T, K extends string = string> = {
29
+ params: Record<K, string>;
30
+ props: {
31
+ page: PaginatedPage<T>;
32
+ };
33
+ };
34
+ /**
35
+ * Slice `items` into pages of `opts.pageSize` and produce one route entry
36
+ * per page. The `param` option names the dynamic segment to fill — for
37
+ * `pages/blog/page/[page].tsx` pass `param: "page"`.
38
+ *
39
+ * Always emits at least one route. An empty input list yields a single
40
+ * empty page so the index route still renders rather than 404ing.
41
+ */
42
+ export declare function paginate<T, K extends string = string>(items: readonly T[], opts: PaginateOptions<K>): PaginateRoute<T, K>[];
43
+ //# sourceMappingURL=paginate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paginate.d.ts","sourceRoot":"","sources":["../src/paginate.ts"],"names":[],"mappings":"AAOA,wCAAwC;AACxC,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,IAAI;IACvD,yDAAyD;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,0EAA0E;IAC1E,KAAK,EAAE,CAAC,CAAC;CACV,CAAC;AAEF,+EAA+E;AAC/E,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI;IAC7B,oCAAoC;IACpC,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,oCAAoC;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,yCAAyC;IACzC,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,GAAG,MAAM,IAAI;IACxD,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAC1B,KAAK,EAAE;QAAE,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,CAAA;KAAE,CAAC;CACnC,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,GAAG,MAAM,EACnD,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,IAAI,EAAE,eAAe,CAAC,CAAC,CAAC,GACvB,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAgCvB"}
@@ -0,0 +1,44 @@
1
+ // `zfb/paginate` — paginate an array of items into per-page route entries.
2
+ //
3
+ // Mirrors the shape of Astro's `paginate()` so the bundled basic-blog template can
4
+ // emit one route per page from a `paths()` export. The renderer treats
5
+ // each returned object as `{ params, props }`, the same convention used
6
+ // elsewhere by the routing engine.
7
+ /**
8
+ * Slice `items` into pages of `opts.pageSize` and produce one route entry
9
+ * per page. The `param` option names the dynamic segment to fill — for
10
+ * `pages/blog/page/[page].tsx` pass `param: "page"`.
11
+ *
12
+ * Always emits at least one route. An empty input list yields a single
13
+ * empty page so the index route still renders rather than 404ing.
14
+ */
15
+ export function paginate(items, opts) {
16
+ if (!Number.isInteger(opts.pageSize) || opts.pageSize < 1) {
17
+ throw new RangeError(`paginate: pageSize must be an integer >= 1, got ${String(opts.pageSize)}`);
18
+ }
19
+ if (typeof opts.param !== "string" || opts.param.length === 0) {
20
+ throw new TypeError(`paginate: param must be a non-empty string, got ${JSON.stringify(opts.param)}`);
21
+ }
22
+ const pageSize = opts.pageSize;
23
+ const total = items.length;
24
+ const lastPage = Math.max(1, Math.ceil(total / pageSize));
25
+ const routes = [];
26
+ for (let page = 1; page <= lastPage; page++) {
27
+ const start = (page - 1) * pageSize;
28
+ const slice = items.slice(start, start + pageSize);
29
+ routes.push({
30
+ params: { [opts.param]: String(page) },
31
+ props: {
32
+ page: {
33
+ data: slice,
34
+ page,
35
+ lastPage,
36
+ pageSize,
37
+ total,
38
+ },
39
+ },
40
+ });
41
+ }
42
+ return routes;
43
+ }
44
+ //# sourceMappingURL=paginate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paginate.js","sourceRoot":"","sources":["../src/paginate.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,EAAE;AACF,mFAAmF;AACnF,uEAAuE;AACvE,wEAAwE;AACxE,mCAAmC;AAoCnC;;;;;;;GAOG;AACH,MAAM,UAAU,QAAQ,CACtB,KAAmB,EACnB,IAAwB;IAExB,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,GAAG,CAAC,EAAE,CAAC;QAC1D,MAAM,IAAI,UAAU,CAClB,mDAAmD,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAC3E,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9D,MAAM,IAAI,SAAS,CACjB,mDAAmD,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAChF,CAAC;IACJ,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;IAC/B,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC;IAC3B,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC;IAC1D,MAAM,MAAM,GAA0B,EAAE,CAAC;IACzC,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,IAAI,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC;QAC5C,MAAM,KAAK,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC;QACpC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,QAAQ,CAAC,CAAC;QACnD,MAAM,CAAC,IAAI,CAAC;YACV,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,EAAuB;YAC3D,KAAK,EAAE;gBACL,IAAI,EAAE;oBACJ,IAAI,EAAE,KAAK;oBACX,IAAI;oBACJ,QAAQ;oBACR,QAAQ;oBACR,KAAK;iBACN;aACF;SACF,CAAC,CAAC;IACL,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,259 @@
1
+ /**
2
+ * Logger handed to every plugin hook. The Rust side wraps `tracing` so
3
+ * the same lines show up alongside the rest of the build's structured
4
+ * logs. Hooks should prefer this over `console.log`.
5
+ */
6
+ export type ZfbPluginLogger = {
7
+ info(msg: string): void;
8
+ warn(msg: string): void;
9
+ error(msg: string): void;
10
+ };
11
+ /**
12
+ * One emitted route in the `postBuild` route manifest (#262).
13
+ * Present on `ctx.routes.routes` so a `postBuild` plugin can iterate
14
+ * every URL the build produced (e.g. to write a `sitemap.xml`).
15
+ */
16
+ export type ZfbRouteEntry = {
17
+ /** Emitted URL path, e.g. `/`, `/blog/hello/`, `/sitemap.xml`. */
18
+ url: string;
19
+ /** Path under `outDir`, e.g. `index.html`, `blog/hello/index.html`, `sitemap.xml`. */
20
+ output: string;
21
+ /** File extension: `html`, `xml`, `rss`, `txt`, `json`, … */
22
+ extension: string;
23
+ /** Source page module relative to the project root, e.g. `pages/blog/[slug].tsx`. */
24
+ source: string;
25
+ /**
26
+ * `true` when the page is prerendered to disk (default / SSG); `false`
27
+ * when the page exports `prerender = false` and is served by the
28
+ * runtime adapter (SSR — no on-disk artifact under `outDir`).
29
+ *
30
+ * Indexes that enumerate on-disk URLs (sitemap.xml, search-index.json,
31
+ * etc.) should filter `r.prerender !== false` to avoid surfacing SSR
32
+ * routes that have no static output.
33
+ */
34
+ prerender: boolean;
35
+ /**
36
+ * Bound route parameters. Absent for static routes.
37
+ * Dynamic (`[slug]`) params are string scalars; catchall (`[...rest]`)
38
+ * params are string arrays.
39
+ */
40
+ params?: Record<string, string | string[]>;
41
+ };
42
+ /**
43
+ * The route manifest exposed on `ctx.routes` during a `postBuild` callback
44
+ * (#262). Sorted by `url` for byte-stable output across runs.
45
+ */
46
+ export type ZfbRouteManifest = {
47
+ routes: ZfbRouteEntry[];
48
+ };
49
+ /**
50
+ * Context passed to `preBuild` and `postBuild`. `outDir` is the
51
+ * resolved absolute path of the configured `outDir` (default
52
+ * `<projectRoot>/dist`). `projectRoot` is the directory containing
53
+ * `zfb.config.ts`.
54
+ *
55
+ * `routes` is **only present on `postBuild`** calls; it is `undefined`
56
+ * on `preBuild`. This is intentional: the route manifest is not
57
+ * available until the build finishes writing `dist/` (#262).
58
+ */
59
+ export type ZfbBuildHookContext = {
60
+ /** Project root — the directory containing `zfb.config.ts`. */
61
+ projectRoot: string;
62
+ /** Resolved absolute path of the build output directory. */
63
+ outDir: string;
64
+ /** The full loaded `ZfbConfig` (data-only view). */
65
+ config: import("./config.js").ZfbConfig;
66
+ /** Plugin-specific options block, copied verbatim from the matching `PluginConfig.options`. */
67
+ options: Record<string, unknown>;
68
+ /** Logger that wraps the Rust-side `tracing` subscriber. */
69
+ logger: ZfbPluginLogger;
70
+ /**
71
+ * All routes emitted by this build, sorted by URL (#262).
72
+ * Present only on `postBuild` calls; `undefined` on `preBuild`.
73
+ */
74
+ routes?: ZfbRouteManifest;
75
+ };
76
+ /**
77
+ * A request handed to a `devMiddleware` handler. Subset of the Node
78
+ * `http.IncomingMessage` surface intentionally — the dev server is
79
+ * Rust-side `axum`, not Node, so we expose only what survives a JSON
80
+ * envelope hop.
81
+ */
82
+ export type ZfbDevMiddlewareRequest = {
83
+ method: string;
84
+ url: string;
85
+ /** Lower-cased header names → first value. */
86
+ headers: Record<string, string>;
87
+ /** Raw request body; absent for GET/HEAD. UTF-8 only — binary is out of scope for v1 dev plugins. */
88
+ body?: string;
89
+ };
90
+ /**
91
+ * Response returned by a `devMiddleware` handler. All fields optional
92
+ * except `status`. `body` may be a string (UTF-8) or a base64-encoded
93
+ * binary payload (set `bodyEncoding` to `"base64"` in that case).
94
+ */
95
+ export type ZfbDevMiddlewareResponse = {
96
+ status: number;
97
+ headers?: Record<string, string>;
98
+ body?: string;
99
+ bodyEncoding?: "utf8" | "base64";
100
+ };
101
+ /**
102
+ * Handler signature for a `devMiddleware` registration. The `next` callback
103
+ * is reserved for future composition; v1 plugins should produce a response
104
+ * directly. Returning `undefined` from the handler signals "I did not handle
105
+ * this request" — the dev server then falls through to its built-in routes
106
+ * (the page cache, /__zfb/livereload.js, etc.).
107
+ */
108
+ export type ZfbDevMiddlewareHandler = (req: ZfbDevMiddlewareRequest) => Promise<ZfbDevMiddlewareResponse | undefined> | ZfbDevMiddlewareResponse | undefined;
109
+ /**
110
+ * Context passed to `devMiddleware`. The `register` callback installs
111
+ * one handler per URL path prefix. `path` is matched as an exact prefix
112
+ * — a registration on `/doc-history` matches `/doc-history` and
113
+ * `/doc-history/foo`, but NOT `/doc-historyx`.
114
+ */
115
+ export type ZfbDevMiddlewareContext = {
116
+ projectRoot: string;
117
+ config: import("./config.js").ZfbConfig;
118
+ options: Record<string, unknown>;
119
+ logger: ZfbPluginLogger;
120
+ /** Register an HTTP handler at `path`. Calling twice on the same path overwrites. */
121
+ register(path: string, handler: ZfbDevMiddlewareHandler): void;
122
+ };
123
+ /**
124
+ * Loader signature for a virtual-module registration. Must return the
125
+ * **complete ESM module source text** as a string — the bundler /
126
+ * embedded V8 host feeds the returned string in as the module's
127
+ * source verbatim. The loader is invoked **exactly once per build**
128
+ * (and once per `zfb dev` host boot) the first time any consumer
129
+ * imports the registered specifier; subsequent imports of the same
130
+ * specifier re-use the memoised result.
131
+ *
132
+ * Example:
133
+ *
134
+ * ```ts
135
+ * addVirtualModule("virtual:my-data", () =>
136
+ * `export default ${JSON.stringify(myJson)}`,
137
+ * );
138
+ * ```
139
+ */
140
+ export type ZfbVirtualModuleLoader = () => string | Promise<string>;
141
+ /**
142
+ * Context passed to the new `setup` hook (#255). Runs once per host
143
+ * boot, in `Config.plugins` declaration order, **before** `preBuild`.
144
+ *
145
+ * `ctx.command` tells the plugin which lifecycle is active so it can
146
+ * gate dev-only registrations:
147
+ *
148
+ * ```ts
149
+ * setup({ command, injectRoute }) {
150
+ * if (command === "dev") {
151
+ * injectRoute("/api/dev/x", "./scripts/dev-x.ts");
152
+ * }
153
+ * }
154
+ * ```
155
+ *
156
+ * The hook's surface is intentionally **closed**: only
157
+ * `injectRoute`, `addVirtualModule`, `addAlias`. There is no
158
+ * `addRemarkPlugin` / `addRehypePlugin` / `addMarkdownVisitor` — by
159
+ * design (see the concept doc for the rationale).
160
+ */
161
+ export type ZfbSetupContext = {
162
+ /**
163
+ * Active zfb command. `"build"` during `zfb build`; `"dev"` during
164
+ * `zfb dev`. Gates `injectRoute` (calling it during `"build"` is an
165
+ * error — see [`injectRoute`](#injectRoute)).
166
+ */
167
+ command: "build" | "dev";
168
+ /** Project root — the directory containing `zfb.config.ts`. */
169
+ projectRoot: string;
170
+ /** The full loaded `ZfbConfig` (data-only view). */
171
+ config: import("./config.js").ZfbConfig;
172
+ /** Plugin-specific options block, copied verbatim from `PluginConfig.options`. */
173
+ options: Record<string, unknown>;
174
+ /** Logger that wraps the Rust-side `tracing` subscriber. */
175
+ logger: ZfbPluginLogger;
176
+ /**
177
+ * Register an import alias. **Exact-match-only in v1**:
178
+ * `addAlias("@/foo", "./src/foo.tsx")` rewrites `import "@/foo"`
179
+ * but does NOT match `import "@/foo/bar"`. Prefix-matching is
180
+ * explicitly deferred to v2 — switch to one bare alias per file
181
+ * until then.
182
+ *
183
+ * `to` is resolved relative to the project root. Two plugins
184
+ * registering the same `from` with different `to` raises
185
+ * `AliasConflict` and aborts the build.
186
+ */
187
+ addAlias(from: string, to: string): void;
188
+ /**
189
+ * Register a virtual module. `specifier` is a bare import
190
+ * specifier (recommended `virtual:` prefix, not enforced).
191
+ * `loader` returns the complete ESM source text as a string and
192
+ * runs **exactly once per build** at first import.
193
+ *
194
+ * Two plugins registering the same `specifier` raises
195
+ * `VirtualModuleConflict` and aborts the build.
196
+ */
197
+ addVirtualModule(specifier: string, loader: ZfbVirtualModuleLoader): void;
198
+ /**
199
+ * Register a synthetic page route. The dev server routes the
200
+ * matched URL into the page rendering pipeline as if `entrypoint`
201
+ * were a `pages/<...>.tsx` file. `pattern` uses the same grammar
202
+ * as `pages/` filenames (`/blog/[slug]`, `/api/dev/x`,
203
+ * `/docs/[...rest]`).
204
+ *
205
+ * **Dev-only.** Calling `injectRoute` when `ctx.command === "build"`
206
+ * raises `InjectRouteInBuildMode` and aborts the build — synthetic
207
+ * routes during a static build would invite SSR-shaped pages that
208
+ * conflict with `output: 'static'`.
209
+ *
210
+ * Two plugins registering the same `pattern` raises
211
+ * `InjectRouteConflict`.
212
+ */
213
+ injectRoute(pattern: string, entrypoint: string): void;
214
+ };
215
+ /**
216
+ * The plugin-module shape. `name` is informational (the resolved module
217
+ * specifier wins for identification on the Rust side) and helps the
218
+ * plugin self-identify in logs.
219
+ *
220
+ * Four optional hooks; declaration-order matters when multiple plugins
221
+ * touch the same surface. Each hook is independent — a plugin may
222
+ * declare any subset:
223
+ *
224
+ * - `setup` (#255) — register virtual modules, aliases, injected
225
+ * routes. Runs once at host boot, before `preBuild`.
226
+ * - `preBuild` — file-generation work that downstream stages will
227
+ * see. Runs once per `zfb build` and once per `zfb dev` boot.
228
+ * - `postBuild` — finalisation work that runs after `dist/` has been
229
+ * written.
230
+ * - `devMiddleware` — register HTTP handlers for ad-hoc dev-only
231
+ * URLs. Per-request dispatch, distinct from `injectRoute` (which
232
+ * goes through the page renderer).
233
+ */
234
+ export type ZfbPlugin = {
235
+ /** Plugin display name; surfaces in error / log lines. */
236
+ name: string;
237
+ setup?(ctx: ZfbSetupContext): Promise<void> | void;
238
+ preBuild?(ctx: ZfbBuildHookContext): Promise<void> | void;
239
+ postBuild?(ctx: ZfbBuildHookContext): Promise<void> | void;
240
+ devMiddleware?(ctx: ZfbDevMiddlewareContext): Promise<void> | void;
241
+ };
242
+ /**
243
+ * Identity helper that types the supplied object as a [`ZfbPlugin`].
244
+ * Use as the default export of a plugin module so editors surface
245
+ * field-level types and typos surface at compile time.
246
+ *
247
+ * ```ts
248
+ * import { definePlugin } from "@takazudo/zfb/plugins";
249
+ *
250
+ * export default definePlugin({
251
+ * name: "my-plugin",
252
+ * async preBuild({ outDir, logger }) {
253
+ * logger.info(`generating index into ${outDir}`);
254
+ * },
255
+ * });
256
+ * ```
257
+ */
258
+ export declare function definePlugin(plugin: ZfbPlugin): ZfbPlugin;
259
+ //# sourceMappingURL=plugins.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugins.d.ts","sourceRoot":"","sources":["../src/plugins.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ // `zfb/plugins` — TypeScript helper for the zfb plugin lifecycle.
2
+ //
3
+ // A plugin is a JS module whose default export is a [`ZfbPlugin`] object.
4
+ // `zfb.config.ts` references plugins by `name` (npm bare specifier or a
5
+ // `./`-relative path); the zfb config loader resolves each `name` to an
6
+ // absolute module specifier and the Rust-side plugin host loads the
7
+ // module via dynamic `import()` and dispatches the lifecycle hooks.
8
+ //
9
+ // Sub 3 / issue #108 — initial drop. Three optional hooks: `preBuild`,
10
+ // `postBuild`, `devMiddleware`. Astro-migration epic #253 / sub-issue
11
+ // #255 adds a fourth: `setup`, which runs once before `preBuild` and
12
+ // lets plugins register virtual modules, import aliases, and dev-only
13
+ // injected routes. None of the hooks see real Node IPC objects across
14
+ // the boundary; everything is JSON-friendly.
15
+ //
16
+ // ## Inline functions are NOT supported
17
+ //
18
+ // `PluginConfig` (in `./config.ts`) carries only data. A user cannot
19
+ // inline a function in `zfb.config.ts` — the config goes through a
20
+ // JSON round-trip and any function value would be silently dropped.
21
+ // Plugins must live in their own module (npm package or local file)
22
+ // and be referenced by `name`.
23
+ /**
24
+ * Identity helper that types the supplied object as a [`ZfbPlugin`].
25
+ * Use as the default export of a plugin module so editors surface
26
+ * field-level types and typos surface at compile time.
27
+ *
28
+ * ```ts
29
+ * import { definePlugin } from "@takazudo/zfb/plugins";
30
+ *
31
+ * export default definePlugin({
32
+ * name: "my-plugin",
33
+ * async preBuild({ outDir, logger }) {
34
+ * logger.info(`generating index into ${outDir}`);
35
+ * },
36
+ * });
37
+ * ```
38
+ */
39
+ export function definePlugin(plugin) {
40
+ return plugin;
41
+ }
42
+ //# sourceMappingURL=plugins.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugins.js","sourceRoot":"","sources":["../src/plugins.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,EAAE;AACF,0EAA0E;AAC1E,wEAAwE;AACxE,wEAAwE;AACxE,oEAAoE;AACpE,oEAAoE;AACpE,EAAE;AACF,uEAAuE;AACvE,sEAAsE;AACtE,qEAAqE;AACrE,sEAAsE;AACtE,sEAAsE;AACtE,6CAA6C;AAC7C,EAAE;AACF,wCAAwC;AACxC,EAAE;AACF,qEAAqE;AACrE,mEAAmE;AACnE,oEAAoE;AACpE,oEAAoE;AACpE,+BAA+B;AAmQ/B;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,YAAY,CAAC,MAAiB;IAC5C,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,101 @@
1
+ import { type When } from "./types.js";
2
+ /**
3
+ * Schedule a hydration `fire` callback for `target` according to `when`.
4
+ *
5
+ * Returns a `cancel` function that aborts the scheduling if it has not
6
+ * fired yet. After firing, calling `cancel` is a no-op. If the helper
7
+ * cannot find the relevant browser API (e.g. running in pure Node with no
8
+ * polyfill), it falls back to firing synchronously so server-side smoke
9
+ * tests still observe the call.
10
+ */
11
+ export declare function scheduleHydrate(target: Element, when: When | string | undefined, fire: () => void): () => void;
12
+ /**
13
+ * The shape of the default export each per-island bundle ships.
14
+ *
15
+ * `mode === "hydrate"` is used for SSR'd islands, `"render"` for
16
+ * SSR-skip islands.
17
+ */
18
+ type IslandMount = (props: Record<string, unknown>, element: Element, mode: "hydrate" | "render") => void;
19
+ type IslandUnmount = (element: Element) => void;
20
+ interface IslandModule {
21
+ mount?: IslandMount;
22
+ default?: IslandMount;
23
+ unmount?: IslandUnmount;
24
+ }
25
+ /**
26
+ * Map of `componentName → island descriptor` baked into the runtime entry.
27
+ *
28
+ * Two descriptor shapes are accepted so the same `mountIslands` runtime
29
+ * handles both bundling strategies the build emits:
30
+ *
31
+ * 1. `string` — a per-island bundle URL. The runtime fetches it via
32
+ * dynamic `import()` and reads `mount` / `default` off the loaded
33
+ * module. Used by the per-island bundling path
34
+ * (`bundle_per_island` / `render_runtime_entry_source`).
35
+ *
36
+ * 2. `IslandModule` — an inline module-shaped object whose `mount` (or
37
+ * `default`) is called directly. Used by the shared-bundle path
38
+ * (`render_shared_bundle_entry_source`): every island's source code
39
+ * is already in the same bundle, so the synthesised entry can hand
40
+ * the runtime the constructed mount functions inline without a
41
+ * second HTTP fetch. This preserves the one-request shared-bundle
42
+ * contract while giving up nothing on hydration semantics
43
+ * (zudolab/zudo-doc#1355 wave 6).
44
+ */
45
+ export type IslandManifestValue = string | IslandModule;
46
+ export type IslandManifest = Readonly<Record<string, IslandManifestValue>>;
47
+ /**
48
+ * Walk the DOM and mount every `[data-zfb-island]` / `[data-zfb-island-skip-ssr]`
49
+ * element using `manifest`.
50
+ *
51
+ * No-op when `document` is undefined (SSR, edge runtime). Safe to call
52
+ * multiple times: each element is mounted at most once thanks to the
53
+ * `mounted` WeakSet guard.
54
+ *
55
+ * The manifest is captured at module level so `mountNewIslands()` can re-use
56
+ * it after an SPA body swap without needing the caller to re-supply it.
57
+ */
58
+ export declare function mountIslands(manifest: IslandManifest): void;
59
+ /**
60
+ * Re-walk the current document body and mount any new island markers introduced
61
+ * by an SPA body swap. Uses the manifest captured by the previous `mountIslands`
62
+ * call — no manifest arg required.
63
+ *
64
+ * The caller (client-router `router.ts`) invokes this after `swap()` + `runScripts()`
65
+ * and before dispatching `zfb:page-load`, per W1B §12.2 contract.
66
+ *
67
+ * No-op when called before `mountIslands` (capturedManifest is null) or when
68
+ * `document` is undefined.
69
+ */
70
+ export declare function mountNewIslands(): void;
71
+ /**
72
+ * Cancel deferred-hydration callbacks for all islands in the old body before a
73
+ * swap. Prevents idle / visibility callbacks from running against orphan elements
74
+ * after `swapBodyElement` removes them from the live document. (W1B §12.5)
75
+ *
76
+ * Call this on `zfb:before-swap` (or equivalently, in the router's swap sequence
77
+ * before `swap()` mutates the DOM). Fire-and-forget; safe to call if nothing is
78
+ * pending.
79
+ */
80
+ export declare function cancelPendingIslands(): void;
81
+ /**
82
+ * Unmount all currently-mounted islands within `root` (default: `document.body`).
83
+ *
84
+ * Walks `root` for `[data-zfb-island]` and `[data-zfb-island-skip-ssr]` elements,
85
+ * looks up each element's unmount thunk in the `mounted` WeakMap, calls it (which
86
+ * triggers `render(null, element)` for Preact or `root.unmount()` for React), and
87
+ * removes the entry from the map so `mountNewIslands()` can re-mount later.
88
+ *
89
+ * Call this before `swapBodyElement(...)` so the OLD body's islands receive proper
90
+ * framework lifecycle cleanup (useEffect teardowns, etc.) before being discarded.
91
+ *
92
+ * No-op for elements not in the `mounted` map (e.g. never-mounted or already cleaned up).
93
+ */
94
+ export declare function unmountIslands(root?: ParentNode): void;
95
+ /**
96
+ * Test-only seam. Replace the module dynamic-import with a fake.
97
+ * Returns the previous implementation so tests can restore it.
98
+ */
99
+ export declare function __setIslandImporterForTests(impl: (url: string) => Promise<IslandModule>): (url: string) => Promise<IslandModule>;
100
+ export {};
101
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAe,KAAK,IAAI,EAAE,MAAM,YAAY,CAAC;AAkBpD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,OAAO,EACf,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,SAAS,EAC/B,IAAI,EAAE,MAAM,IAAI,GACf,MAAM,IAAI,CAcZ;AAoHD;;;;;GAKG;AACH,KAAK,WAAW,GAAG,CACjB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,SAAS,GAAG,QAAQ,KACvB,IAAI,CAAC;AAEV,KAAK,aAAa,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;AAEhD,UAAU,YAAY;IACpB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,YAAY,CAAC;AACxD,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC,CAAC;AA8B3E;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAwB3D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAmBtC;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAK3C;AAiMD;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,IAAI,GAAE,UAA0B,GAAG,IAAI,CAUrE;AAqCD;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,GAC3C,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,CAIxC"}