@moku-labs/web 0.6.0 → 1.0.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/README.md +227 -125
- package/dist/browser.d.mts +524 -111
- package/dist/browser.mjs +1565 -798
- package/dist/{convention-X3zLTlJ8.mjs → convention-CepUwWmT.mjs} +18 -1
- package/dist/{convention-Dr8jxG70.cjs → convention-krwh7Y6Q.cjs} +23 -0
- package/dist/index.cjs +2906 -1908
- package/dist/index.d.cts +365 -176
- package/dist/index.d.mts +366 -177
- package/dist/index.mjs +2903 -1907
- package/dist/{writer-DAF0pM25.cjs → writer-DV5hWB2i.cjs} +25 -27
- package/dist/{writer-BcWqa_7I.mjs → writer-Dc_lx22j.mjs} +25 -27
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -155,8 +155,9 @@ declare namespace types_d_exports$5 {
|
|
|
155
155
|
*
|
|
156
156
|
* Providers are walked in array order during resolution; the first provider to
|
|
157
157
|
* return a non-`undefined` (and non-empty-string) value for a key wins. `load()`
|
|
158
|
-
* is called once per resolution at `onInit` time
|
|
159
|
-
*
|
|
158
|
+
* is called exactly once per resolution at `onInit` time, after which both env
|
|
159
|
+
* maps are frozen. A provider like {@link cloudflareBindings} reads `globalThis`
|
|
160
|
+
* at that single `onInit` call (not per request).
|
|
160
161
|
*
|
|
161
162
|
* @example
|
|
162
163
|
* ```ts
|
|
@@ -227,7 +228,8 @@ type EnvConfig = {
|
|
|
227
228
|
/**
|
|
228
229
|
* Ordered list of value sources. The first provider yielding a non-`undefined`
|
|
229
230
|
* (and non-empty-string) value for a key wins. The plugin's own spec default is
|
|
230
|
-
* `[]`; the
|
|
231
|
+
* `[]`; the consumer supplies the providers per target (`[dotenv(), processEnv()]`
|
|
232
|
+
* on Node) — only the `/browser` entry pre-wires `browserEnv()` out of the box.
|
|
231
233
|
*/
|
|
232
234
|
providers: EnvProvider[];
|
|
233
235
|
/**
|
|
@@ -336,12 +338,25 @@ declare function browserEnv(options?: {
|
|
|
336
338
|
declare const envPlugin: import("@moku-labs/core").CorePluginInstance<"env", EnvConfig, EnvState, EnvApi>;
|
|
337
339
|
//#endregion
|
|
338
340
|
//#region src/config.d.ts
|
|
341
|
+
/**
|
|
342
|
+
* Deployment stage. Drives content draft visibility — drafts are suppressed
|
|
343
|
+
* only in `"production"`; `"development"` and `"test"` both surface them.
|
|
344
|
+
*/
|
|
345
|
+
type Stage = "production" | "development" | "test";
|
|
339
346
|
/**
|
|
340
347
|
* Global framework configuration. Minimal by design — per-plugin config is
|
|
341
348
|
* resolved via `pluginConfigs`, not merged here.
|
|
342
349
|
*/
|
|
343
350
|
type Config$8 = {
|
|
344
|
-
/**
|
|
351
|
+
/** Deployment stage. Drives content draft visibility (drafts hidden only in `"production"`). */stage: Stage;
|
|
352
|
+
/**
|
|
353
|
+
* Render mode — the single SSG/DATA/SPA switch, read by the router (`ctx.global`)
|
|
354
|
+
* and consumed by `build`/`spa` via `router.mode()`.
|
|
355
|
+
* - `"ssg"` static generation only (no client router emitted).
|
|
356
|
+
* - `"spa"` client-side routing only.
|
|
357
|
+
* - `"hybrid"` static HTML + client navigation overlay (default).
|
|
358
|
+
*/
|
|
359
|
+
mode: "ssg" | "spa" | "hybrid";
|
|
345
360
|
};
|
|
346
361
|
/**
|
|
347
362
|
* Framework event contract. Empty base — each plugin declares its own events
|
|
@@ -446,7 +461,7 @@ type Api$7 = {
|
|
|
446
461
|
* @file i18n plugin — public type definitions (Config + Api).
|
|
447
462
|
*/
|
|
448
463
|
/**
|
|
449
|
-
* i18n plugin configuration.
|
|
464
|
+
* i18n plugin configuration.
|
|
450
465
|
*
|
|
451
466
|
* `locales` and `defaultLocale` are required and validated in `onInit`. The
|
|
452
467
|
* optional maps default to empty objects so every lookup method is total —
|
|
@@ -543,7 +558,7 @@ type Api$6 = {
|
|
|
543
558
|
t(locale: string, key: string): string;
|
|
544
559
|
};
|
|
545
560
|
declare namespace types_d_exports$8 {
|
|
546
|
-
export { Api$5 as Api, ClientRoute, CompileInput, CompiledRoute, Config$5 as Config, ExtractRouteParams, ExtractSegmentParameter, HeadConfig$1 as HeadConfig, LayoutContext, MatcherTable, Prettify, RouteBuilder, RouteContext, RouteDefinition, RouteHandlers, RouteMap, RouteState, RouterApi, RouterConfig, RouterState, State$5 as State, TypedRoute };
|
|
561
|
+
export { Api$5 as Api, ClientRoute, CompileInput, CompiledRoute, Config$5 as Config, ExtractApi$2 as ExtractApi, ExtractRouteParams, ExtractSegmentParameter, GenerateContext, HeadConfig$1 as HeadConfig, LayoutContext, LoadContext, MatcherTable, Prettify, RouteBuilder, RouteContext, RouteDefinition, RouteHandlers, RouteMap, RouteRequire, RouteState, RouterApi, RouterConfig, RouterState, State$5 as State, TypedRoute, Urls };
|
|
547
562
|
}
|
|
548
563
|
/**
|
|
549
564
|
* Param contribution of a single path segment. `{name:?}` / `:name?` → optional;
|
|
@@ -582,6 +597,84 @@ interface RouteContext<S extends RouteState> {
|
|
|
582
597
|
readonly data: S["data"];
|
|
583
598
|
/** Active locale for this render. */
|
|
584
599
|
readonly locale: string;
|
|
600
|
+
/**
|
|
601
|
+
* Build a link to a named route by pattern substitution — the framework delivers
|
|
602
|
+
* this on the context (same output as `app.router.toUrl`), so render/head build
|
|
603
|
+
* links with no `app`/`createUrls` reference. Works identically at build and on
|
|
604
|
+
* client navigation.
|
|
605
|
+
*/
|
|
606
|
+
readonly url: (name: string, params?: Record<string, string>) => string;
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Structural extraction of a plugin instance's public API from its `_phantom`
|
|
610
|
+
* carrier (mirrors the kernel's `ExtractApi` / spec/09 §3). Lets the loader/generator
|
|
611
|
+
* `require` resolve a plugin instance to its typed public API.
|
|
612
|
+
*
|
|
613
|
+
* @example
|
|
614
|
+
* type ContentApi = ExtractApi<typeof contentPlugin>;
|
|
615
|
+
*/
|
|
616
|
+
type ExtractApi$2<PluginCandidate> = PluginCandidate extends {
|
|
617
|
+
readonly _phantom: {
|
|
618
|
+
readonly api: infer PluginApi;
|
|
619
|
+
};
|
|
620
|
+
} ? PluginApi : never;
|
|
621
|
+
/**
|
|
622
|
+
* Generic, instance-only `require` handed to a route's `.load()` / `.generate()` —
|
|
623
|
+
* the SAME shape as the kernel's `RequireFunction` (spec/08 §7) and the build's
|
|
624
|
+
* `PhaseRequire`, so the build forwards its own `ctx.require` straight through.
|
|
625
|
+
* Resolves a plugin INSTANCE to its public API; the consumer supplies the instance
|
|
626
|
+
* (e.g. `ctx.require(contentPlugin)`), so the router never names a sibling plugin.
|
|
627
|
+
*
|
|
628
|
+
* @example
|
|
629
|
+
* const content = ctx.require(contentPlugin); // ContentApi
|
|
630
|
+
*/
|
|
631
|
+
type RouteRequire = <PluginCandidate extends {
|
|
632
|
+
readonly name: string;
|
|
633
|
+
readonly spec: unknown;
|
|
634
|
+
readonly _phantom: {
|
|
635
|
+
readonly config: unknown;
|
|
636
|
+
readonly state: unknown;
|
|
637
|
+
readonly api: unknown;
|
|
638
|
+
readonly events: Record<string, unknown>;
|
|
639
|
+
};
|
|
640
|
+
}>(plugin: PluginCandidate) => ExtractApi$2<PluginCandidate>;
|
|
641
|
+
/**
|
|
642
|
+
* Build-time context handed to a route's `.load()`. Carries the resolved path
|
|
643
|
+
* `params` and active `locale`, plus the spec's `require`/`has` so a loader pulls
|
|
644
|
+
* sibling plugin APIs the canonical way — `ctx.require(contentPlugin)` — with no
|
|
645
|
+
* module global and no router→content coupling. Loaders run ONLY at build time
|
|
646
|
+
* (never on the client), inside the build plugin's context, so `require`/`has` are
|
|
647
|
+
* always live here.
|
|
648
|
+
*
|
|
649
|
+
* @example
|
|
650
|
+
* route("/{slug}/").load((ctx) => ctx.require(contentPlugin).load(ctx.params.slug, ctx.locale));
|
|
651
|
+
*/
|
|
652
|
+
interface LoadContext<S extends RouteState> {
|
|
653
|
+
/** Resolved path params for this page instance. */
|
|
654
|
+
readonly params: S["params"];
|
|
655
|
+
/** Active locale this page instance is built for. */
|
|
656
|
+
readonly locale: string;
|
|
657
|
+
/** Resolve a sibling plugin instance to its public API (spec/08 §7). */
|
|
658
|
+
readonly require: RouteRequire;
|
|
659
|
+
/** Whether a plugin is registered (by name) — branch on OPTIONAL plugins. */
|
|
660
|
+
readonly has: (name: string) => boolean;
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Build-time context handed to a route's `.generate()` — the static-param producer.
|
|
664
|
+
* Carries the active `locale` plus `require`/`has` (no `params` yet — `.generate()`
|
|
665
|
+
* PRODUCES the param sets). Same build-only guarantee as {@link LoadContext}.
|
|
666
|
+
*
|
|
667
|
+
* @example
|
|
668
|
+
* route("/{slug}/").generate(async (ctx) =>
|
|
669
|
+
* [...(await ctx.require(contentPlugin).loadAll()).get(ctx.locale) ?? []].map((a) => ({ slug: a.computed.slug })));
|
|
670
|
+
*/
|
|
671
|
+
interface GenerateContext {
|
|
672
|
+
/** Active locale to enumerate param sets for. */
|
|
673
|
+
readonly locale: string;
|
|
674
|
+
/** Resolve a sibling plugin instance to its public API (spec/08 §7). */
|
|
675
|
+
readonly require: RouteRequire;
|
|
676
|
+
/** Whether a plugin is registered (by name). */
|
|
677
|
+
readonly has: (name: string) => boolean;
|
|
585
678
|
}
|
|
586
679
|
/**
|
|
587
680
|
* Context handed to a route's `.layout()` wrapper: the render-time
|
|
@@ -617,7 +710,7 @@ interface RouteBuilder<S extends RouteState> extends RouteDefinition {
|
|
|
617
710
|
* Attach a data loader; widens the data generic (and ONLY the data generic) so
|
|
618
711
|
* `.render()`/`.head()` see its return. Path params are preserved unchanged.
|
|
619
712
|
*/
|
|
620
|
-
load<D>(loader: (
|
|
713
|
+
load<D>(loader: (ctx: LoadContext<S>) => D | Promise<D>): RouteBuilder<{
|
|
621
714
|
readonly params: S["params"];
|
|
622
715
|
readonly data: Awaited<D>;
|
|
623
716
|
}>;
|
|
@@ -631,19 +724,10 @@ interface RouteBuilder<S extends RouteState> extends RouteDefinition {
|
|
|
631
724
|
layout(component: (ctx: LayoutContext<S>, children: ComponentChildren) => VNode): RouteBuilder<S>;
|
|
632
725
|
/** Attach the page render handler. */
|
|
633
726
|
render(handler: (ctx: RouteContext<S>) => VNode): RouteBuilder<S>;
|
|
634
|
-
/**
|
|
635
|
-
* Attach the client-side validation gate: parse the raw `unknown` fetched from
|
|
636
|
-
* the persisted data file back into this route's data type `S["data"]`. Runs at
|
|
637
|
-
* the trust boundary before `render` on the client (and MUST return `S["data"]`,
|
|
638
|
-
* so a mismatched schema is a compile error). Throw inside it to reject malformed
|
|
639
|
-
* data — `spa` then falls back to HTML-over-fetch. Use a hand guard or any
|
|
640
|
-
* Standard-Schema validator (zod/valibot/arktype).
|
|
641
|
-
*/
|
|
642
|
-
parse(handler: (raw: unknown) => S["data"]): RouteBuilder<S>;
|
|
643
727
|
/** Attach the head/SEO handler. */
|
|
644
728
|
head(handler: (ctx: RouteContext<S>) => HeadConfig$1): RouteBuilder<S>;
|
|
645
|
-
/** Attach a static-generation param producer. */
|
|
646
|
-
generate(handler: (
|
|
729
|
+
/** Attach a static-generation param producer (receives a {@link GenerateContext}). */
|
|
730
|
+
generate(handler: (ctx: GenerateContext) => S["params"][] | Promise<S["params"][]>): RouteBuilder<S>;
|
|
647
731
|
/**
|
|
648
732
|
* Attach an arbitrary metadata bag. The bag MUST be JSON-serializable: it is
|
|
649
733
|
* projected verbatim into `clientManifest()` and shipped to the browser.
|
|
@@ -656,18 +740,16 @@ interface RouteBuilder<S extends RouteState> extends RouteDefinition {
|
|
|
656
740
|
}
|
|
657
741
|
/** Build-only handler bag captured by a `RouteBuilder` (consumed by `build` via `manifest()`). */
|
|
658
742
|
interface RouteHandlers {
|
|
659
|
-
/** Data loader. */
|
|
660
|
-
readonly load?: (
|
|
743
|
+
/** Data loader (receives a {@link LoadContext}: params + locale + require/has). */
|
|
744
|
+
readonly load?: (ctx: LoadContext<RouteState>) => unknown;
|
|
661
745
|
/** Layout wrapper (ctx-aware): frames the page in persistent chrome. SSG-only. */
|
|
662
746
|
readonly layout?: (ctx: LayoutContext<RouteState>, children: ComponentChildren) => VNode;
|
|
663
747
|
/** Page renderer. */
|
|
664
748
|
readonly render?: (ctx: RouteContext<RouteState>) => VNode;
|
|
665
|
-
/** Client-side validation gate: `unknown` (fetched JSON) → the route's data type, or throw. */
|
|
666
|
-
readonly parse?: (raw: unknown) => unknown;
|
|
667
749
|
/** Head/SEO producer. */
|
|
668
750
|
readonly head?: (ctx: RouteContext<RouteState>) => HeadConfig$1;
|
|
669
|
-
/** Static-generation param producer. */
|
|
670
|
-
readonly generate?: (
|
|
751
|
+
/** Static-generation param producer (receives a {@link GenerateContext}). */
|
|
752
|
+
readonly generate?: (ctx: GenerateContext) => unknown[] | Promise<unknown[]>;
|
|
671
753
|
/** JSON serializer. */
|
|
672
754
|
readonly toJson?: (ctx: RouteContext<RouteState>) => unknown;
|
|
673
755
|
/** Output file-path producer. */
|
|
@@ -691,28 +773,45 @@ interface RouteDefinition {
|
|
|
691
773
|
* base (erased) `RouteDefinition`; this is the documented generic-erasure boundary.
|
|
692
774
|
*/
|
|
693
775
|
type RouteMap = Record<string, RouteDefinition>;
|
|
776
|
+
/**
|
|
777
|
+
* A pure, app-free URL builder over a route map (the return type of `createUrls`).
|
|
778
|
+
* `toUrl` builds a route's path by name + params via pattern substitution — it needs
|
|
779
|
+
* NO running app, router instance, base URL, or i18n: just the route map the consumer
|
|
780
|
+
* already holds at module scope. Works identically at build, on client navigation,
|
|
781
|
+
* and inside hydrated islands. Reuses the SAME `buildUrl` as the runtime `RouterApi`,
|
|
782
|
+
* so the helper and the API can never diverge.
|
|
783
|
+
*
|
|
784
|
+
* @example
|
|
785
|
+
* const url = createUrls(routes);
|
|
786
|
+
* url.toUrl("article", { lang: "en", slug: "hello" }); // "/en/hello/"
|
|
787
|
+
*/
|
|
788
|
+
interface Urls<T extends RouteMap> {
|
|
789
|
+
/**
|
|
790
|
+
* Build a route's URL path from its name and params. The name is typed to the
|
|
791
|
+
* route map's keys — only declared routes are accepted.
|
|
792
|
+
*
|
|
793
|
+
* @param name - Route name key from the map (e.g. `"home"`, `"article"`).
|
|
794
|
+
* @param params - Path params to substitute into the pattern. Defaults to `{}`.
|
|
795
|
+
* @returns The resolved relative URL path.
|
|
796
|
+
* @throws {Error} If `name` is not present in the route map.
|
|
797
|
+
* @example
|
|
798
|
+
* url.toUrl("home", { lang: "en" }); // "/en/"
|
|
799
|
+
*/
|
|
800
|
+
toUrl<K extends keyof T & string>(name: K, params?: Record<string, string>): string;
|
|
801
|
+
}
|
|
694
802
|
/**
|
|
695
803
|
* Configuration for the router plugin.
|
|
696
804
|
*
|
|
697
805
|
* @remarks
|
|
698
|
-
* `routes` is
|
|
699
|
-
*
|
|
700
|
-
*
|
|
806
|
+
* `routes` is the declarative route map — registered the normal config way via
|
|
807
|
+
* `createApp({ pluginConfigs: { router: { routes } } })` and compiled into the matcher
|
|
808
|
+
* table in the router's `onInit`. An `import * as routes` namespace is a valid value. It is
|
|
809
|
+
* the SOLE registration path: omitting it leaves the matcher table empty, so every read
|
|
810
|
+
* (`match`/`toUrl`/`entries`/…) throws. The render `mode` is NOT here — it is a GLOBAL
|
|
811
|
+
* framework option (`createApp({ config: { mode } })`), read by the router via `ctx.global`.
|
|
701
812
|
*/
|
|
702
813
|
type RouterConfig = {
|
|
703
|
-
/**
|
|
704
|
-
* Named route definitions. Element type erases to the base `RouteDefinition`
|
|
705
|
-
* at this config boundary; per-route call-site types are preserved only through
|
|
706
|
-
* `defineRoutes()` + `route()` at the consumer and re-exposed via `manifest()`.
|
|
707
|
-
*/
|
|
708
|
-
routes: RouteMap;
|
|
709
|
-
/**
|
|
710
|
-
* Render mode for URL/file resolution. Defaults to `"hybrid"`.
|
|
711
|
-
* - `"ssg"` static generation only (no client router emitted).
|
|
712
|
-
* - `"spa"` client-side routing only.
|
|
713
|
-
* - `"hybrid"` static HTML + client navigation overlay.
|
|
714
|
-
*/
|
|
715
|
-
mode?: "ssg" | "spa" | "hybrid";
|
|
814
|
+
/** Declarative route map (route name → `route(...)`); compiled at init. An `import * as` namespace works. */readonly routes?: RouteMap;
|
|
716
815
|
};
|
|
717
816
|
/** A resolved route exposing URL utilities with typed params (port of legacy TypedRoute). */
|
|
718
817
|
interface TypedRoute<TParams = Record<string, string>> {
|
|
@@ -761,15 +860,14 @@ interface MatcherTable {
|
|
|
761
860
|
readonly byName: ReadonlyMap<string, CompiledRoute>;
|
|
762
861
|
}
|
|
763
862
|
/**
|
|
764
|
-
* Router plugin state
|
|
765
|
-
*
|
|
766
|
-
*
|
|
863
|
+
* Router plugin state — a mutable holder whose `table` is `null` until the router's
|
|
864
|
+
* `onInit` compiles `config.routes`. The render `mode` is NOT stored here; it is read
|
|
865
|
+
* from the global framework config via the API context. Keeps all mutable state in
|
|
866
|
+
* `ctx.state` (no singletons).
|
|
767
867
|
*/
|
|
768
868
|
interface RouterState {
|
|
769
|
-
/** Compiled matcher table; `null` until `onInit`
|
|
869
|
+
/** Compiled matcher table; `null` until `onInit` compiles `config.routes`. */
|
|
770
870
|
table: MatcherTable | null;
|
|
771
|
-
/** Resolved render mode (single source of truth; set in `onInit`). Defaults `"hybrid"`. */
|
|
772
|
-
mode: "ssg" | "spa" | "hybrid";
|
|
773
871
|
}
|
|
774
872
|
/** Plain-data input to `compileRoutes` — resolved DATA only, never the plugin ctx. */
|
|
775
873
|
interface CompileInput {
|
|
@@ -802,7 +900,7 @@ interface ClientRoute {
|
|
|
802
900
|
/** Route metadata bag from `.meta()`. MUST be JSON-serializable. */
|
|
803
901
|
readonly meta: Record<string, unknown>;
|
|
804
902
|
}
|
|
805
|
-
/** Public API exposed via `ctx.require(routerPlugin)`. */
|
|
903
|
+
/** Public API exposed via `ctx.require(routerPlugin)` and `app.router`. */
|
|
806
904
|
type RouterApi = {
|
|
807
905
|
/**
|
|
808
906
|
* Match a pathname against the compiled route table (specificity-sorted).
|
|
@@ -841,7 +939,7 @@ type RouterApi = {
|
|
|
841
939
|
*
|
|
842
940
|
* @returns Read-only array of the typed route definitions, in declaration order.
|
|
843
941
|
* @example
|
|
844
|
-
* for (const def of ctx.require(routerPlugin).manifest())
|
|
942
|
+
* for (const def of ctx.require(routerPlugin).manifest()) def._handlers.render?.(routeContext);
|
|
845
943
|
*/
|
|
846
944
|
manifest(): readonly RouteDefinition[];
|
|
847
945
|
/**
|
|
@@ -1220,7 +1318,7 @@ interface SpaKernelDeps {
|
|
|
1220
1318
|
/**
|
|
1221
1319
|
* The OPTIONAL `data` reader. Present only when the `data` plugin is composed.
|
|
1222
1320
|
* When present (and `router.mode() !== "ssg"`), navigation first tries the client
|
|
1223
|
-
* DATA path (match → `dataAt(path)` → `route.
|
|
1321
|
+
* DATA path (match → `dataAt(path)` → `route.render`); otherwise
|
|
1224
1322
|
* it always uses HTML-over-fetch.
|
|
1225
1323
|
*/
|
|
1226
1324
|
dataAt?: SpaDataReader;
|
|
@@ -1323,7 +1421,7 @@ type SpaApi = {
|
|
|
1323
1421
|
current(): string;
|
|
1324
1422
|
};
|
|
1325
1423
|
declare namespace types_d_exports {
|
|
1326
|
-
export { Api$3 as Api, BuildCacheEntry, BuildEvents, BuildResult, Config$3 as Config, ExtractApi, OgFont, OgImageConfig,
|
|
1424
|
+
export { Api$3 as Api, BuildCacheEntry, BuildEvents, BuildResult, Config$3 as Config, ExtractApi, OgFont, OgImageConfig, PhaseContext, PhaseEmit, PhaseLog, PhaseName, PhaseRequire, RichOgInput, State$3 as State };
|
|
1327
1425
|
}
|
|
1328
1426
|
/**
|
|
1329
1427
|
* Structural extraction of a plugin instance's public API from its `_phantom`
|
|
@@ -1401,26 +1499,15 @@ type PhaseRequire = <PluginCandidate extends {
|
|
|
1401
1499
|
*/
|
|
1402
1500
|
type PhaseContext = {
|
|
1403
1501
|
/** Mutable per-run build state (caches + runId). */state: State$3; /** Resolved, frozen build config. */
|
|
1404
|
-
readonly config: Readonly<Config$3>; /** Global framework config (mode
|
|
1502
|
+
readonly config: Readonly<Config$3>; /** Global framework config (deployment stage; render mode is read via `router.mode()`). */
|
|
1405
1503
|
readonly global: Readonly<{
|
|
1406
|
-
|
|
1504
|
+
stage: Stage;
|
|
1407
1505
|
}>; /** Resolve a depended-upon plugin instance to its public API. */
|
|
1408
1506
|
require: PhaseRequire; /** Whether a plugin is registered (by name) — used to detect the OPTIONAL `data` plugin. */
|
|
1409
1507
|
has: (name: string) => boolean; /** Emit a build event (notification-only). */
|
|
1410
1508
|
emit: PhaseEmit; /** Structured logger (core `log` API). */
|
|
1411
1509
|
readonly log: PhaseLog;
|
|
1412
1510
|
};
|
|
1413
|
-
/**
|
|
1414
|
-
* Injectable PNG renderer for the og-images phase. Defaults to the real
|
|
1415
|
-
* Satori → resvg pipeline; unit tests inject a fake to assert hash-cache skip
|
|
1416
|
-
* and the `p-limit` bound without rasterizing real images.
|
|
1417
|
-
*
|
|
1418
|
-
* @example
|
|
1419
|
-
* ```ts
|
|
1420
|
-
* const render: OgPngRenderer = async () => new Uint8Array();
|
|
1421
|
-
* ```
|
|
1422
|
-
*/
|
|
1423
|
-
type OgPngRenderer = (input: RichOgInput) => Promise<Uint8Array>;
|
|
1424
1511
|
/**
|
|
1425
1512
|
* Rich input handed to a custom OG `render` hook for a single article card. Carries
|
|
1426
1513
|
* the full article + site metadata so a consumer can compose any layout. Returned by
|
|
@@ -1521,11 +1608,11 @@ type Config$3 = {
|
|
|
1521
1608
|
publicDir?: string;
|
|
1522
1609
|
/**
|
|
1523
1610
|
* Emit `outDir/404.html`. `true` for the built-in default page, or
|
|
1524
|
-
* `{
|
|
1525
|
-
*
|
|
1611
|
+
* `{ body }` to supply the page's literal HTML body content (written into the
|
|
1612
|
+
* 404 page verbatim). Default `false`.
|
|
1526
1613
|
*/
|
|
1527
1614
|
notFound?: boolean | {
|
|
1528
|
-
|
|
1615
|
+
body?: string;
|
|
1529
1616
|
}; /** Emit per-path i18n bare-path redirect HTML pages. Default `false`. */
|
|
1530
1617
|
localeRedirects?: boolean; /** Authoritative client bundle entry path (overrides the conventional scan). */
|
|
1531
1618
|
clientEntry?: string; /** HTML shell template with `<!--moku:head-->`/`<!--moku:body-->`/`<!--moku:assets-->` placeholders. */
|
|
@@ -1773,11 +1860,6 @@ type DeployRunOptions = {
|
|
|
1773
1860
|
* /^[a-zA-Z0-9/_.-]+$/ — otherwise rejected with ERR_DEPLOY_INVALID_BRANCH.
|
|
1774
1861
|
*/
|
|
1775
1862
|
branch?: string;
|
|
1776
|
-
/**
|
|
1777
|
-
* Whether to run the build before deploying. When false, deploys the existing outDir.
|
|
1778
|
-
* Defaults to `true`.
|
|
1779
|
-
*/
|
|
1780
|
-
build?: boolean;
|
|
1781
1863
|
};
|
|
1782
1864
|
/**
|
|
1783
1865
|
* Options for DeployApi.init.
|
|
@@ -1804,14 +1886,14 @@ type Api$2 = {
|
|
|
1804
1886
|
* the branch argument, spawns wrangler (no shell), scrubs all subprocess output
|
|
1805
1887
|
* before logging, records lastDeployment, and emits deploy:complete.
|
|
1806
1888
|
*
|
|
1807
|
-
* @param options - Optional branch override
|
|
1889
|
+
* @param options - Optional branch override.
|
|
1808
1890
|
* @returns The deploy result (url, deploymentId, branch, durationMs).
|
|
1809
1891
|
* @throws {Error} With a `code` from the deploy error taxonomy on any failure.
|
|
1810
1892
|
* @example
|
|
1811
1893
|
* const result = await app.deploy.run();
|
|
1812
1894
|
* console.log(result.url); // https://my-site.pages.dev
|
|
1813
1895
|
* @example
|
|
1814
|
-
* await app.deploy.run({ branch: "preview/landing"
|
|
1896
|
+
* await app.deploy.run({ branch: "preview/landing" });
|
|
1815
1897
|
*/
|
|
1816
1898
|
run(options?: DeployRunOptions): Promise<DeployResult>;
|
|
1817
1899
|
/**
|
|
@@ -1871,14 +1953,15 @@ type ServerInfo = {
|
|
|
1871
1953
|
watching?: string[];
|
|
1872
1954
|
};
|
|
1873
1955
|
/**
|
|
1874
|
-
* Information rendered after a single `serve()` rebuild:
|
|
1875
|
-
* fresh build summary used to print the "rebuilt N pages"
|
|
1956
|
+
* Information rendered after a single `serve()` rebuild: the watched directory whose
|
|
1957
|
+
* subtree changed plus the fresh build summary used to print the "rebuilt N pages"
|
|
1958
|
+
* line.
|
|
1876
1959
|
*
|
|
1877
1960
|
* @example
|
|
1878
|
-
* const info: ReloadInfo = { file: "content
|
|
1961
|
+
* const info: ReloadInfo = { file: "content", pageCount: 12, durationMs: 84 };
|
|
1879
1962
|
*/
|
|
1880
1963
|
type ReloadInfo = {
|
|
1881
|
-
/** The
|
|
1964
|
+
/** The watched directory whose subtree changed (the rebuild is per-watchDir, not per-file). */file: string; /** Number of route pages rendered by the rebuild. */
|
|
1882
1965
|
pageCount: number; /** Wall-clock duration of the rebuild in milliseconds. */
|
|
1883
1966
|
durationMs: number;
|
|
1884
1967
|
};
|
|
@@ -1930,12 +2013,13 @@ type CliRenderer = {
|
|
|
1930
2013
|
*/
|
|
1931
2014
|
serverReady(info: ServerInfo): void;
|
|
1932
2015
|
/**
|
|
1933
|
-
* Render the post-rebuild line ("~
|
|
2016
|
+
* Render the post-rebuild line ("~ dir" + "✓ rebuilt N pages · Xms · reloaded"),
|
|
2017
|
+
* where the label is the watched directory whose subtree changed (see {@link ReloadInfo.file}).
|
|
1934
2018
|
*
|
|
1935
|
-
* @param info - The changed
|
|
2019
|
+
* @param info - The changed watched directory plus the rebuild's page count and duration.
|
|
1936
2020
|
* @returns Nothing.
|
|
1937
2021
|
* @example
|
|
1938
|
-
* render.reload({ file: "content
|
|
2022
|
+
* render.reload({ file: "content", pageCount: 12, durationMs: 84 });
|
|
1939
2023
|
*/
|
|
1940
2024
|
reload(info: ReloadInfo): void;
|
|
1941
2025
|
/**
|
|
@@ -2257,50 +2341,8 @@ type Api$1 = {
|
|
|
2257
2341
|
*/
|
|
2258
2342
|
declare const cliPlugin: import("@moku-labs/core").PluginInstance<"cli", Config$1, State$1, Api$1, {}> & Record<never, never>;
|
|
2259
2343
|
declare namespace types_d_exports$2 {
|
|
2260
|
-
export { Api, Article, ArticleCard, ComputedFields, Config, ContentApiContext, ContentEvents, Frontmatter, State };
|
|
2344
|
+
export { Api, Article, ArticleCard, ComputedFields, Config, ContentApiContext, ContentEvents, ContentProvider, ContentProviderState, FileSystemContentOptions, Frontmatter, State };
|
|
2261
2345
|
}
|
|
2262
|
-
/**
|
|
2263
|
-
* Configuration for the content plugin.
|
|
2264
|
-
*
|
|
2265
|
-
* @example
|
|
2266
|
-
* ```ts
|
|
2267
|
-
* { contentDir: "./src/content", trustedContent: false, shikiTheme: "github-dark" }
|
|
2268
|
-
* ```
|
|
2269
|
-
*/
|
|
2270
|
-
type Config = {
|
|
2271
|
-
/** Absolute or project-relative path to the content directory. Validated in onInit. */contentDir: string;
|
|
2272
|
-
/**
|
|
2273
|
-
* SECURITY GATE. When false (the default), rehype-sanitize runs as the final
|
|
2274
|
-
* pipeline step. Set true ONLY for fully author-controlled Markdown — true
|
|
2275
|
-
* disables sanitize and trusts all raw HTML.
|
|
2276
|
-
*/
|
|
2277
|
-
trustedContent: boolean; /** Additional remark plugins, concatenated AFTER framework defaults. Defaults to []. */
|
|
2278
|
-
extraRemarkPlugins?: readonly Pluggable[]; /** Additional rehype plugins, concatenated after custom transforms, before Shiki + sanitize. Defaults to []. */
|
|
2279
|
-
extraRehypePlugins?: readonly Pluggable[];
|
|
2280
|
-
/**
|
|
2281
|
-
* Shiki theme for syntax highlighting: a bundled theme NAME — typed as Shiki's
|
|
2282
|
-
* `BundledTheme` union so editors autocomplete the ~60 built-ins (default
|
|
2283
|
-
* "github-dark") — or a custom `ThemeRegistration` object. Passed straight through
|
|
2284
|
-
* to `@shikijs/rehype`'s `theme`. (Like Shiki's own theme type, an arbitrary string
|
|
2285
|
-
* still compiles via the object arm, so this is autocomplete, not typo-rejection.)
|
|
2286
|
-
*/
|
|
2287
|
-
shikiTheme?: BundledTheme | ThemeRegistrationAny; /** Author applied to articles whose frontmatter omits author. Defaults to undefined. */
|
|
2288
|
-
defaultAuthor?: string;
|
|
2289
|
-
};
|
|
2290
|
-
/**
|
|
2291
|
-
* Internal mutable state for the content plugin.
|
|
2292
|
-
*
|
|
2293
|
-
* @example
|
|
2294
|
-
* ```ts
|
|
2295
|
-
* { processor: null, articles: new Map(), slugs: null, dirtyPaths: new Set() }
|
|
2296
|
-
* ```
|
|
2297
|
-
*/
|
|
2298
|
-
type State = {
|
|
2299
|
-
/** Lazily-created unified processor singleton. null until first render()/loadAll(). */processor: Processor | null; /** Article cache keyed locale -> (slug -> Article). Starts empty. */
|
|
2300
|
-
articles: Map<string, Map<string, Article>>; /** Discovered, sorted slug list cached after first disk scan. null until first discovery. */
|
|
2301
|
-
slugs: string[] | null; /** Paths marked stale by invalidate(); next loadAll() re-reads only these. Starts empty. */
|
|
2302
|
-
dirtyPaths: Set<string>;
|
|
2303
|
-
};
|
|
2304
2346
|
/**
|
|
2305
2347
|
* YAML frontmatter parsed from each article file.
|
|
2306
2348
|
*
|
|
@@ -2315,7 +2357,7 @@ type Frontmatter = {
|
|
|
2315
2357
|
description: string; /** Topic tags. Required (may be empty array). */
|
|
2316
2358
|
tags: string[]; /** Source language code of this file. Required. */
|
|
2317
2359
|
language: string; /** Draft flag. Excluded from output in production mode. Defaults to false. */
|
|
2318
|
-
draft?: boolean; /** Author name. Falls back to
|
|
2360
|
+
draft?: boolean; /** Author name. Falls back to the provider's defaultAuthor when omitted. */
|
|
2319
2361
|
author?: string;
|
|
2320
2362
|
};
|
|
2321
2363
|
/**
|
|
@@ -2367,6 +2409,119 @@ type ArticleCard = {
|
|
|
2367
2409
|
readingTime: number; /** Canonical URL for this article in this locale. */
|
|
2368
2410
|
url: string;
|
|
2369
2411
|
};
|
|
2412
|
+
/**
|
|
2413
|
+
* A pluggable content SOURCE. The shell calls these to read articles; whether content
|
|
2414
|
+
* is read from the filesystem (Node) or some other source is chosen by which provider
|
|
2415
|
+
* you compose — exactly like `env` providers (`dotenv`/`processEnv` vs `browserEnv`).
|
|
2416
|
+
* The shell adds locale fallback, draft filtering, sorting, caching, and events on top.
|
|
2417
|
+
*
|
|
2418
|
+
* @example
|
|
2419
|
+
* ```ts
|
|
2420
|
+
* const provider = fileSystemContent({ contentDir: "./content" });
|
|
2421
|
+
* ```
|
|
2422
|
+
*/
|
|
2423
|
+
interface ContentProvider {
|
|
2424
|
+
/** Human-readable provider name, used in diagnostics. */
|
|
2425
|
+
readonly name: string;
|
|
2426
|
+
/** Source directory surfaced via `api.contentDir()` (filesystem providers; "" otherwise). */
|
|
2427
|
+
readonly contentDir: string;
|
|
2428
|
+
/**
|
|
2429
|
+
* Discover the article slugs this provider can supply.
|
|
2430
|
+
*
|
|
2431
|
+
* @returns The provider's slug list.
|
|
2432
|
+
*/
|
|
2433
|
+
slugs(): Promise<readonly string[]>;
|
|
2434
|
+
/**
|
|
2435
|
+
* Read + render ONE article for a file-locale; `null` if this provider has no such file.
|
|
2436
|
+
*
|
|
2437
|
+
* @param slug - Article directory name.
|
|
2438
|
+
* @param fileLocale - Locale whose source file is read.
|
|
2439
|
+
* @param outLocale - Locale the resulting Article represents (the requested locale).
|
|
2440
|
+
* @param isFallback - Whether this resolution used the default-locale fallback.
|
|
2441
|
+
* @returns The constructed Article, or `null` when absent.
|
|
2442
|
+
*/
|
|
2443
|
+
readArticle(slug: string, fileLocale: string, outLocale: string, isFallback: boolean): Promise<Article | null>;
|
|
2444
|
+
/**
|
|
2445
|
+
* Render a standalone Markdown string to HTML through the provider's pipeline.
|
|
2446
|
+
*
|
|
2447
|
+
* @param markdown - Raw Markdown source.
|
|
2448
|
+
* @returns The rendered HTML.
|
|
2449
|
+
*/
|
|
2450
|
+
render(markdown: string): Promise<string>;
|
|
2451
|
+
/**
|
|
2452
|
+
* Optional dev hook: drop cached discovery so stale paths are re-read next time.
|
|
2453
|
+
*
|
|
2454
|
+
* @param paths - Stale file paths.
|
|
2455
|
+
*/
|
|
2456
|
+
invalidate?(paths: readonly string[]): void;
|
|
2457
|
+
}
|
|
2458
|
+
/**
|
|
2459
|
+
* Options for the node filesystem provider {@link ContentProvider} `fileSystemContent`.
|
|
2460
|
+
* These are the markdown-pipeline + source concerns that used to live on the content
|
|
2461
|
+
* plugin config; they now belong to the provider you compose.
|
|
2462
|
+
*
|
|
2463
|
+
* @example
|
|
2464
|
+
* ```ts
|
|
2465
|
+
* fileSystemContent({ contentDir: "./content", shikiTheme: "github-dark", defaultAuthor: "Ada" });
|
|
2466
|
+
* ```
|
|
2467
|
+
*/
|
|
2468
|
+
type FileSystemContentOptions = {
|
|
2469
|
+
/** Absolute or project-relative path to the content directory. */contentDir: string;
|
|
2470
|
+
/**
|
|
2471
|
+
* SECURITY GATE. When false (the default), rehype-sanitize runs as the final
|
|
2472
|
+
* pipeline step. Set true ONLY for fully author-controlled Markdown.
|
|
2473
|
+
*/
|
|
2474
|
+
trustedContent?: boolean; /** Additional remark plugins, concatenated AFTER framework defaults. Defaults to []. */
|
|
2475
|
+
extraRemarkPlugins?: readonly Pluggable[]; /** Additional rehype plugins, concatenated after custom transforms, before Shiki + sanitize. Defaults to []. */
|
|
2476
|
+
extraRehypePlugins?: readonly Pluggable[];
|
|
2477
|
+
/**
|
|
2478
|
+
* Shiki theme for syntax highlighting: a bundled theme NAME (default "github-dark")
|
|
2479
|
+
* or a custom `ThemeRegistration` object. Passed straight through to `@shikijs/rehype`.
|
|
2480
|
+
*/
|
|
2481
|
+
shikiTheme?: BundledTheme | ThemeRegistrationAny; /** Author applied to articles whose frontmatter omits author. Defaults to undefined. */
|
|
2482
|
+
defaultAuthor?: string;
|
|
2483
|
+
};
|
|
2484
|
+
/**
|
|
2485
|
+
* Internal mutable state of the filesystem provider: the lazy unified processor and
|
|
2486
|
+
* the discovery caches. Owned by the provider closure, never by the plugin shell.
|
|
2487
|
+
*
|
|
2488
|
+
* @example
|
|
2489
|
+
* ```ts
|
|
2490
|
+
* { processor: null, slugs: null, dirtyPaths: new Set() }
|
|
2491
|
+
* ```
|
|
2492
|
+
*/
|
|
2493
|
+
type ContentProviderState = {
|
|
2494
|
+
/** Lazily-created unified processor singleton. null until first render()/readArticle(). */processor: Processor | null; /** Discovered, sorted slug list cached after first disk scan. null until first discovery. */
|
|
2495
|
+
slugs: string[] | null; /** Paths marked stale by invalidate(); next discovery re-reads only these. Starts empty. */
|
|
2496
|
+
dirtyPaths: Set<string>;
|
|
2497
|
+
};
|
|
2498
|
+
/**
|
|
2499
|
+
* Configuration for the content plugin (shell).
|
|
2500
|
+
*
|
|
2501
|
+
* @example
|
|
2502
|
+
* ```ts
|
|
2503
|
+
* { providers: [fileSystemContent({ contentDir: "./content" })] }
|
|
2504
|
+
* ```
|
|
2505
|
+
*/
|
|
2506
|
+
type Config = {
|
|
2507
|
+
/**
|
|
2508
|
+
* Ordered content sources. Compose at least one (e.g. `fileSystemContent(...)` on
|
|
2509
|
+
* Node). The first provider that supplies an article for a slug+locale wins;
|
|
2510
|
+
* `slugs()` are unioned. The plugin's own spec default is `[]` (a build must supply one).
|
|
2511
|
+
*/
|
|
2512
|
+
providers: ContentProvider[];
|
|
2513
|
+
};
|
|
2514
|
+
/**
|
|
2515
|
+
* Internal mutable state for the content plugin shell: the locale-keyed article cache.
|
|
2516
|
+
*
|
|
2517
|
+
* @example
|
|
2518
|
+
* ```ts
|
|
2519
|
+
* { articles: new Map() }
|
|
2520
|
+
* ```
|
|
2521
|
+
*/
|
|
2522
|
+
type State = {
|
|
2523
|
+
/** Article cache keyed locale -> (slug -> Article). Starts empty. */articles: Map<string, Map<string, Article>>;
|
|
2524
|
+
};
|
|
2370
2525
|
/**
|
|
2371
2526
|
* Notification-only events emitted by the content plugin.
|
|
2372
2527
|
*
|
|
@@ -2386,24 +2541,24 @@ type ContentEvents = {
|
|
|
2386
2541
|
};
|
|
2387
2542
|
/**
|
|
2388
2543
|
* Kernel-free domain context handed to createContentApi by the wiring harness.
|
|
2389
|
-
* Carries
|
|
2390
|
-
*
|
|
2544
|
+
* Carries the shell state (article cache), global flag, emit, the i18n-derived
|
|
2545
|
+
* locale helpers, and the resolved content {@link ContentProvider} — so api.ts stays
|
|
2546
|
+
* free of createPlugin/ctx AND of any node/pipeline import.
|
|
2391
2547
|
*
|
|
2392
2548
|
* @example
|
|
2393
2549
|
* ```ts
|
|
2394
|
-
* const apiContext: ContentApiContext = { state,
|
|
2550
|
+
* const apiContext: ContentApiContext = { state, global, emit, locales, defaultLocale, provider };
|
|
2395
2551
|
* ```
|
|
2396
2552
|
*/
|
|
2397
2553
|
type ContentApiContext = {
|
|
2398
|
-
/** Mutable
|
|
2399
|
-
config: Config; /** Global framework configuration (mode, etc.). */
|
|
2554
|
+
/** Mutable shell state (article cache). */state: State; /** Global framework configuration (deployment stage). */
|
|
2400
2555
|
global: {
|
|
2401
|
-
|
|
2556
|
+
stage: Stage;
|
|
2402
2557
|
}; /** Emit a registered content event. */
|
|
2403
2558
|
emit: <K extends keyof ContentEvents>(event: K, payload: ContentEvents[K]) => void; /** Active locale codes from i18n. */
|
|
2404
2559
|
locales: () => readonly string[]; /** Default locale code from i18n (fallback source). */
|
|
2405
|
-
defaultLocale: () => string; /**
|
|
2406
|
-
|
|
2560
|
+
defaultLocale: () => string; /** The resolved content source (merged from `config.providers`). */
|
|
2561
|
+
provider: ContentProvider;
|
|
2407
2562
|
};
|
|
2408
2563
|
/**
|
|
2409
2564
|
* Public API for the content plugin.
|
|
@@ -2445,29 +2600,29 @@ type Api = {
|
|
|
2445
2600
|
*/
|
|
2446
2601
|
articleToCard(article: Article): ArticleCard;
|
|
2447
2602
|
/**
|
|
2448
|
-
* The configured content source directory (e.g. `"./content"`)
|
|
2449
|
-
* article's co-located assets
|
|
2450
|
-
*
|
|
2603
|
+
* The configured content source directory (e.g. `"./content"`), from the first
|
|
2604
|
+
* provider. Lets the build copy each article's co-located assets
|
|
2605
|
+
* (`<contentDir>/<slug>/images/`) into the output.
|
|
2451
2606
|
*/
|
|
2452
2607
|
contentDir(): string;
|
|
2453
2608
|
};
|
|
2454
2609
|
//#endregion
|
|
2455
2610
|
//#region src/plugins/content/index.d.ts
|
|
2456
2611
|
/**
|
|
2457
|
-
* Content plugin —
|
|
2458
|
-
*
|
|
2459
|
-
*
|
|
2460
|
-
* `
|
|
2612
|
+
* Content plugin (shell) — provider-driven locale-keyed Article model. Orchestration
|
|
2613
|
+
* (locale fallback, draft filtering, sort, caching, events) lives here; source I/O +
|
|
2614
|
+
* the Markdown pipeline live in a {@link ContentProvider} you compose (like `env`
|
|
2615
|
+
* providers). The shell imports zero node code, so `contentPlugin` is browser-safe.
|
|
2616
|
+
* Depends on i18n; emits `content:ready` and `content:invalidated`.
|
|
2461
2617
|
*
|
|
2462
|
-
* @example
|
|
2618
|
+
* @example Compose the node filesystem provider with a content dir + Shiki theme
|
|
2463
2619
|
* ```ts
|
|
2620
|
+
* import { contentPlugin, fileSystemContent } from "@moku-labs/web";
|
|
2464
2621
|
* const app = createApp({
|
|
2622
|
+
* plugins: [contentPlugin],
|
|
2465
2623
|
* pluginConfigs: {
|
|
2466
2624
|
* content: {
|
|
2467
|
-
* contentDir: "./content",
|
|
2468
|
-
* shikiTheme: "github-dark",
|
|
2469
|
-
* defaultAuthor: "Ada Lovelace"
|
|
2470
|
-
* // trustedContent: true // ONLY for fully author-controlled Markdown — disables sanitize
|
|
2625
|
+
* providers: [fileSystemContent({ contentDir: "./content", shikiTheme: "github-dark", defaultAuthor: "Ada" })]
|
|
2471
2626
|
* }
|
|
2472
2627
|
* }
|
|
2473
2628
|
* });
|
|
@@ -2491,13 +2646,13 @@ declare namespace types_d_exports$3 {
|
|
|
2491
2646
|
* The `data` plugin is the **agnostic data provider** for the SSG→DATA→SPA pattern.
|
|
2492
2647
|
* It owns ONE thing: the contract `page path → persisted JSON file`. It knows
|
|
2493
2648
|
* NOTHING about what the data *is* — no domain types appear here. A route decides
|
|
2494
|
-
* its own data shape (`load`'s return)
|
|
2649
|
+
* its own data shape (`load`'s return).
|
|
2495
2650
|
*
|
|
2496
2651
|
* - **Node (build):** `write(entries)` persists one JSON file per page, keyed by
|
|
2497
2652
|
* the page's URL via {@link DataProvider.fileFor}. `build` supplies the entries
|
|
2498
2653
|
* (it already expanded the routes), so there is no duplicate expansion here.
|
|
2499
|
-
* - **Browser (runtime):** `at(path)` fetches + caches that file as `unknown
|
|
2500
|
-
* route
|
|
2654
|
+
* - **Browser (runtime):** `at(path)` fetches + caches that file as `unknown`, which
|
|
2655
|
+
* the route uses directly as `ctx.data` in `render`.
|
|
2501
2656
|
*
|
|
2502
2657
|
* The Node-only file-writing code (`node:fs`) is isolated behind a lazy `import()`
|
|
2503
2658
|
* inside `write()`, so composing `data` in a browser app keeps the bundle free of
|
|
@@ -2519,8 +2674,8 @@ type DataConfig = {
|
|
|
2519
2674
|
outputDir: string;
|
|
2520
2675
|
/**
|
|
2521
2676
|
* READ side (browser): site-root-relative URL the client fetches the per-page
|
|
2522
|
-
* JSON from.
|
|
2523
|
-
* path); keep consistent (`"/" + trim(outputDir) + "/"`). Default `"/_data/"`.
|
|
2677
|
+
* JSON from. The URL-space mirror of {@link DataConfig.outputDir} (a filesystem
|
|
2678
|
+
* path); keep them consistent (`"/" + trim(outputDir) + "/"`). Default `"/_data/"`.
|
|
2524
2679
|
*/
|
|
2525
2680
|
baseUrl: string;
|
|
2526
2681
|
};
|
|
@@ -2562,15 +2717,15 @@ interface DataState {
|
|
|
2562
2717
|
* // Node build (build supplies the entries it already expanded):
|
|
2563
2718
|
* await app.data.write([{ path: "/en/hello/", data: article }]);
|
|
2564
2719
|
*
|
|
2565
|
-
* // Browser (inside spa nav): fetch the page's data,
|
|
2566
|
-
* const raw = await app.data.at("/en/hello/"); // unknown | null
|
|
2720
|
+
* // Browser (inside spa nav): fetch the page's data, used directly as ctx.data:
|
|
2721
|
+
* const raw = await app.data.at("/en/hello/"); // unknown | null (null on failure)
|
|
2567
2722
|
* ```
|
|
2568
2723
|
*/
|
|
2569
2724
|
type DataProvider = {
|
|
2570
2725
|
/**
|
|
2571
2726
|
* READ (browser) — fetch (and cache) the persisted data for a page path from
|
|
2572
|
-
* `config.baseUrl`. Returns the raw parsed JSON as `unknown
|
|
2573
|
-
* `
|
|
2727
|
+
* `config.baseUrl`. Returns the raw parsed JSON as `unknown`, used directly as
|
|
2728
|
+
* the route's `ctx.data`; returns `null` if the fetch or JSON parse fails.
|
|
2574
2729
|
*
|
|
2575
2730
|
* @param path - The page URL path (e.g. `/en/hello/`).
|
|
2576
2731
|
* @returns The page's raw data, or `null` on failure.
|
|
@@ -2616,13 +2771,14 @@ type DataProvider = {
|
|
|
2616
2771
|
* @example
|
|
2617
2772
|
* ```ts
|
|
2618
2773
|
* // Node build: `build` calls app.data.write(...) during its pages phase when
|
|
2619
|
-
* // router.mode !== "ssg".
|
|
2774
|
+
* // router.mode() !== "ssg". Compose the plugin + set the global render mode:
|
|
2775
|
+
* import * as routes from "./routes";
|
|
2620
2776
|
* const app = createApp({
|
|
2621
2777
|
* plugins: [dataPlugin, contentPlugin, buildPlugin],
|
|
2622
|
-
*
|
|
2778
|
+
* config: { mode: "hybrid" },
|
|
2779
|
+
* pluginConfigs: { content: { providers: [fileSystemContent({ contentDir: "./content" })] }, router: { routes } }
|
|
2623
2780
|
* });
|
|
2624
|
-
* await app.
|
|
2625
|
-
* await app.build.run(); // writes HTML + per-page data sidecars
|
|
2781
|
+
* await app.build.run(); // writes HTML + per-page data sidecars (routes compiled at init)
|
|
2626
2782
|
*
|
|
2627
2783
|
* // Browser app: compose `dataPlugin` too; spa fetches via app.data.at(path) on nav.
|
|
2628
2784
|
* ```
|
|
@@ -2736,6 +2892,8 @@ declare function feedLink(title: string, url: string, type?: string): HeadElemen
|
|
|
2736
2892
|
* modified times, author, section, tags, plus a JSON-LD `Article` block and canonical.
|
|
2737
2893
|
*
|
|
2738
2894
|
* @param articleMeta - Article metadata (title, description, author, dates, tags, image…).
|
|
2895
|
+
* `image`, when present, is pushed to `og:image` verbatim and must therefore be
|
|
2896
|
+
* an absolute URL (this helper does not resolve relative paths against the site).
|
|
2739
2897
|
* @param canonicalUrl - The article's canonical absolute URL.
|
|
2740
2898
|
* @returns An ordered array of serializable head elements.
|
|
2741
2899
|
* @example buildArticleHead({ title: "Hi", author: "A", published: "2026-01-01" }, "https://x/p")
|
|
@@ -2818,7 +2976,7 @@ declare const logPlugin: import("@moku-labs/core").CorePluginInstance<"log", Log
|
|
|
2818
2976
|
* @example
|
|
2819
2977
|
* ```ts
|
|
2820
2978
|
* route("/{lang:?}/{slug}/")
|
|
2821
|
-
* .load((
|
|
2979
|
+
* .load((ctx) => loadArticle(ctx.params.slug))
|
|
2822
2980
|
* .render((ctx) => <Article a={ctx.data} />)
|
|
2823
2981
|
* .head((ctx) => ({ title: ctx.data.title }));
|
|
2824
2982
|
* ```
|
|
@@ -2836,31 +2994,46 @@ declare function route<P extends string>(pattern: P): RouteBuilder<RouteState<P>
|
|
|
2836
2994
|
* ```
|
|
2837
2995
|
*/
|
|
2838
2996
|
declare function defineRoutes<T extends RouteMap>(routes: T): T;
|
|
2997
|
+
/**
|
|
2998
|
+
* Build a pure, app-free URL builder from a route map. `toUrl(name, params)` resolves
|
|
2999
|
+
* a route's path by pattern substitution using the SAME `buildUrl` as the runtime
|
|
3000
|
+
* `RouterApi.toUrl`, so the helper and the API can never diverge. It needs no running
|
|
3001
|
+
* app, router instance, base URL, or i18n — just the route map the consumer already
|
|
3002
|
+
* holds at module scope. So components, layouts, and hydrated islands import it
|
|
3003
|
+
* directly: no `app.router` reference, no manual "bind", no module global, no
|
|
3004
|
+
* "not bound" guard, and no createApp ↔ routes cycle.
|
|
3005
|
+
*
|
|
3006
|
+
* @param routes - The route map (typically the value returned by {@link defineRoutes}).
|
|
3007
|
+
* @returns A {@link Urls} builder whose `toUrl` accepts only this map's route names.
|
|
3008
|
+
* @example
|
|
3009
|
+
* ```ts
|
|
3010
|
+
* const url = createUrls(routes);
|
|
3011
|
+
* url.toUrl("article", { lang: "en", slug: "hello" }); // "/en/hello/"
|
|
3012
|
+
* ```
|
|
3013
|
+
*/
|
|
3014
|
+
declare function createUrls<T extends RouteMap>(routes: T): Urls<T>;
|
|
2839
3015
|
//#endregion
|
|
2840
3016
|
//#region src/plugins/router/index.d.ts
|
|
2841
3017
|
/**
|
|
2842
3018
|
* Router plugin — typed, named route definitions with locale-aware URL generation
|
|
2843
|
-
* and matching. Author routes with {@link route}
|
|
2844
|
-
*
|
|
3019
|
+
* and matching. Author routes with {@link route}, then register them the normal config
|
|
3020
|
+
* way via `pluginConfigs.router.routes` (compiled at init). Depends on site (base URL)
|
|
3021
|
+
* and i18n (locales).
|
|
2845
3022
|
*
|
|
2846
|
-
* @example
|
|
3023
|
+
* @example Register routes via config, then start/build
|
|
2847
3024
|
* ```ts
|
|
3025
|
+
* import * as routes from "./routes";
|
|
2848
3026
|
* const app = createApp({
|
|
2849
|
-
*
|
|
2850
|
-
*
|
|
2851
|
-
* routes: defineRoutes({
|
|
2852
|
-
* home: route("/"),
|
|
2853
|
-
* article: route("/blog/{slug}/")
|
|
2854
|
-
* }),
|
|
2855
|
-
* mode: "hybrid" // "ssg" | "spa" | "hybrid" (default)
|
|
2856
|
-
* }
|
|
2857
|
-
* }
|
|
3027
|
+
* config: { mode: "hybrid" }, // render mode is GLOBAL config
|
|
3028
|
+
* pluginConfigs: { router: { routes } } // declarative route map (a namespace works)
|
|
2858
3029
|
* });
|
|
3030
|
+
* await app.build.run(); // or: await app.start(); — routes compiled at init
|
|
2859
3031
|
* ```
|
|
2860
3032
|
*/
|
|
2861
3033
|
declare const routerPlugin: import("@moku-labs/core").PluginInstance<"router", RouterConfig, RouterState, RouterApi, {}> & {
|
|
2862
3034
|
route: typeof route;
|
|
2863
3035
|
defineRoutes: typeof defineRoutes;
|
|
3036
|
+
createUrls: typeof createUrls;
|
|
2864
3037
|
};
|
|
2865
3038
|
//#endregion
|
|
2866
3039
|
//#region src/plugins/site/index.d.ts
|
|
@@ -2984,6 +3157,20 @@ declare function processEnv(): EnvProvider;
|
|
|
2984
3157
|
*/
|
|
2985
3158
|
declare function cloudflareBindings(): EnvProvider;
|
|
2986
3159
|
//#endregion
|
|
3160
|
+
//#region src/plugins/content/providers.d.ts
|
|
3161
|
+
/**
|
|
3162
|
+
* The node filesystem content provider: reads + renders Markdown from `contentDir`
|
|
3163
|
+
* through the full pipeline. Caches discovery + the unified processor internally.
|
|
3164
|
+
*
|
|
3165
|
+
* @param options - Filesystem + pipeline options (`contentDir`, `shikiTheme`, `trustedContent`, …).
|
|
3166
|
+
* @returns A {@link ContentProvider} backed by the local filesystem.
|
|
3167
|
+
* @example
|
|
3168
|
+
* ```ts
|
|
3169
|
+
* createApp({ pluginConfigs: { content: { providers: [fileSystemContent({ contentDir: "./content" })] } } });
|
|
3170
|
+
* ```
|
|
3171
|
+
*/
|
|
3172
|
+
declare function fileSystemContent(options: FileSystemContentOptions): ContentProvider;
|
|
3173
|
+
//#endregion
|
|
2987
3174
|
//#region src/index.d.ts
|
|
2988
3175
|
/**
|
|
2989
3176
|
* Create and initialize a `@moku-labs/web` application — the Layer-3 entry point.
|
|
@@ -2996,27 +3183,28 @@ declare function cloudflareBindings(): EnvProvider;
|
|
|
2996
3183
|
*
|
|
2997
3184
|
* @param options - Optional configuration:
|
|
2998
3185
|
* - `pluginConfigs` — per-plugin overrides, keyed by plugin name.
|
|
2999
|
-
* - `config` — global framework config (e.g. `{ mode: "
|
|
3186
|
+
* - `config` — global framework config (e.g. `{ mode: "spa" }`).
|
|
3000
3187
|
* - `plugins` — extra plugins (Node-only built-ins or your own) merged into the app and its type.
|
|
3001
3188
|
* - `onReady` / `onError` / `onStart` / `onStop` — lifecycle callbacks.
|
|
3002
3189
|
* @returns The initialized app: `start()`, `stop()`, every plugin's API, and `log`.
|
|
3003
3190
|
* @example
|
|
3004
3191
|
* ```ts
|
|
3005
|
-
* // Node SSG build — add the node-only plugins:
|
|
3192
|
+
* // Node SSG build — add the node-only plugins, register routes via config, then build:
|
|
3193
|
+
* import * as routes from "./routes";
|
|
3006
3194
|
* const app = createApp({
|
|
3007
3195
|
* plugins: [contentPlugin, buildPlugin, deployPlugin],
|
|
3008
3196
|
* pluginConfigs: {
|
|
3009
3197
|
* site: { name: "My Blog", url: "https://blog.dev", author: "Ada", description: "Notes" },
|
|
3010
|
-
* router: { routes
|
|
3198
|
+
* router: { routes }
|
|
3011
3199
|
* }
|
|
3012
3200
|
* });
|
|
3013
|
-
* await app.
|
|
3014
|
-
* await app.build.run();
|
|
3201
|
+
* await app.build.run(); // routes compiled at init
|
|
3015
3202
|
* ```
|
|
3016
3203
|
*/
|
|
3017
3204
|
declare const createApp: <const ExtraPlugins extends readonly import("@moku-labs/core").AnyPluginInstance[] = readonly []>(options?: import("@moku-labs/core").CreateAppOptions<Config$8, Events, (import("@moku-labs/core").PluginInstance<"site", Config$7, Record<string, never>, Api$7, {}> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"i18n", Config$6, Record<string, never>, Api$6, {}> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"router", RouterConfig, RouterState, RouterApi, {}> & {
|
|
3018
3205
|
route: typeof route;
|
|
3019
3206
|
defineRoutes: typeof defineRoutes;
|
|
3207
|
+
createUrls: typeof createUrls;
|
|
3020
3208
|
}) | (import("@moku-labs/core").PluginInstance<"head", Config$4, State$4, Api$4, {}> & {
|
|
3021
3209
|
meta: typeof meta;
|
|
3022
3210
|
og: typeof og;
|
|
@@ -3045,6 +3233,7 @@ declare const createApp: <const ExtraPlugins extends readonly import("@moku-labs
|
|
|
3045
3233
|
}> & Record<never, never>) | ExtraPlugins[number], [...ExtraPlugins], import("@moku-labs/core").CoreApisFromTuple<[import("@moku-labs/core").CorePluginInstance<"log", LogConfig, LogState, LogApi>, import("@moku-labs/core").CorePluginInstance<"env", EnvConfig, EnvState, EnvApi>]>> | undefined) => import("@moku-labs/core").App<Config$8, Events, (import("@moku-labs/core").PluginInstance<"site", Config$7, Record<string, never>, Api$7, {}> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"i18n", Config$6, Record<string, never>, Api$6, {}> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"router", RouterConfig, RouterState, RouterApi, {}> & {
|
|
3046
3234
|
route: typeof route;
|
|
3047
3235
|
defineRoutes: typeof defineRoutes;
|
|
3236
|
+
createUrls: typeof createUrls;
|
|
3048
3237
|
}) | (import("@moku-labs/core").PluginInstance<"head", Config$4, State$4, Api$4, {}> & {
|
|
3049
3238
|
meta: typeof meta;
|
|
3050
3239
|
og: typeof og;
|
|
@@ -3088,4 +3277,4 @@ declare const createApp: <const ExtraPlugins extends readonly import("@moku-labs
|
|
|
3088
3277
|
*/
|
|
3089
3278
|
declare const createPlugin: import("@moku-labs/core").BoundCreatePluginFunction<Config$8, Events, import("@moku-labs/core").CoreApisFromTuple<[import("@moku-labs/core").CorePluginInstance<"log", LogConfig, LogState, LogApi>, import("@moku-labs/core").CorePluginInstance<"env", EnvConfig, EnvState, EnvApi>]>>;
|
|
3090
3279
|
//#endregion
|
|
3091
|
-
export { types_d_exports as Build, types_d_exports$1 as Cli, types_d_exports$2 as Content, types_d_exports$3 as Data, types_d_exports$4 as Deploy, types_d_exports$5 as Env, types_d_exports$6 as Head, types_d_exports$7 as Log, types_d_exports$8 as Router, types_d_exports$9 as Spa, browserEnv, buildArticleHead, buildPlugin, canonical, cliPlugin, cloudflareBindings, contentPlugin, createApp, createComponent, createPlugin, dataPlugin, defineRoutes, deployPlugin, dotenv, envPlugin, feedLink, headPlugin, hreflang, i18nPlugin, jsonLd, logPlugin, meta, og, processEnv, route, routerPlugin, sitePlugin, spaPlugin, twitter };
|
|
3280
|
+
export { types_d_exports as Build, types_d_exports$1 as Cli, types_d_exports$2 as Content, types_d_exports$3 as Data, types_d_exports$4 as Deploy, types_d_exports$5 as Env, types_d_exports$6 as Head, types_d_exports$7 as Log, types_d_exports$8 as Router, types_d_exports$9 as Spa, browserEnv, buildArticleHead, buildPlugin, canonical, cliPlugin, cloudflareBindings, contentPlugin, createApp, createComponent, createPlugin, createUrls, dataPlugin, defineRoutes, deployPlugin, dotenv, envPlugin, feedLink, fileSystemContent, headPlugin, hreflang, i18nPlugin, jsonLd, logPlugin, meta, og, processEnv, route, routerPlugin, sitePlugin, spaPlugin, twitter };
|