@moku-labs/web 2.4.13 → 2.4.15
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/dist/browser.d.mts +21 -1
- package/dist/browser.mjs +82 -12
- package/dist/{convention-CLIDd9k-.cjs → convention-BO-VoFLO.cjs} +8 -0
- package/dist/{convention-Dp650o3y.mjs → convention-BtVf429P.mjs} +3 -1
- package/dist/index.cjs +82 -12
- package/dist/index.d.cts +21 -1
- package/dist/index.d.mts +21 -1
- package/dist/index.mjs +82 -12
- package/dist/{writer-CaoyORyZ.mjs → writer-Dwd02T9h.mjs} +30 -1
- package/dist/{writer-BEM-Pbgb.cjs → writer-ryba09yV.cjs} +30 -1
- package/package.json +1 -1
package/dist/browser.d.mts
CHANGED
|
@@ -1931,7 +1931,7 @@ declare const spaPlugin: import("@moku-labs/core").PluginInstance<"spa", SpaConf
|
|
|
1931
1931
|
};
|
|
1932
1932
|
}> & Record<never, never>;
|
|
1933
1933
|
declare namespace types_d_exports$2 {
|
|
1934
|
-
export { DataConfig, DataEntry, DataProvider, DataState, DataWriteSummary };
|
|
1934
|
+
export { DataConfig, DataEntry, DataPreload, DataProvider, DataState, DataWriteSummary };
|
|
1935
1935
|
}
|
|
1936
1936
|
/**
|
|
1937
1937
|
* @file data plugin — type definitions (Standard tier).
|
|
@@ -1951,6 +1951,12 @@ declare namespace types_d_exports$2 {
|
|
|
1951
1951
|
* inside `write()`, so composing `data` in a browser app keeps the bundle free of
|
|
1952
1952
|
* `node:*`.
|
|
1953
1953
|
*/
|
|
1954
|
+
/**
|
|
1955
|
+
* How the browser loads page data. `"none"` fetches one page file per navigation
|
|
1956
|
+
* (lazy). `"all"` also loads every page's data once, from one bundle file, after
|
|
1957
|
+
* the app starts and the main thread is idle.
|
|
1958
|
+
*/
|
|
1959
|
+
type DataPreload = "none" | "all";
|
|
1954
1960
|
/**
|
|
1955
1961
|
* Configuration for {@link dataPlugin}. All fields have defaults (see `./config`).
|
|
1956
1962
|
*
|
|
@@ -1971,6 +1977,13 @@ type DataConfig = {
|
|
|
1971
1977
|
* path); keep them consistent (`"/" + trim(outputDir) + "/"`). Default `"/_data/"`.
|
|
1972
1978
|
*/
|
|
1973
1979
|
baseUrl: string;
|
|
1980
|
+
/**
|
|
1981
|
+
* BOTH sides. `"all"`: the Node `write()` also writes one bundle file
|
|
1982
|
+
* `<outputDir>/_all.json` (`{ "<page path>": data }`), and the browser fetches
|
|
1983
|
+
* it once when idle and fills the `at()` cache. Set the same value in the Node
|
|
1984
|
+
* app and the browser app. Default `"none"`: no bundle, lazy per-page fetches.
|
|
1985
|
+
*/
|
|
1986
|
+
preload?: DataPreload;
|
|
1974
1987
|
};
|
|
1975
1988
|
/** One page's data to persist — `build` produces these from its route expansion. */
|
|
1976
1989
|
interface DataEntry {
|
|
@@ -1987,6 +2000,11 @@ interface DataWriteSummary {
|
|
|
1987
2000
|
bytes: number;
|
|
1988
2001
|
/** The written file paths, relative to the build `outDir`. */
|
|
1989
2002
|
files: string[];
|
|
2003
|
+
/** The preload bundle, present only when `preload` is `"all"`. Not counted in `fileCount`/`bytes`. */
|
|
2004
|
+
bundle?: {
|
|
2005
|
+
file: string;
|
|
2006
|
+
bytes: number;
|
|
2007
|
+
};
|
|
1990
2008
|
}
|
|
1991
2009
|
/**
|
|
1992
2010
|
* Internal data state. `lastWrite` records the most recent `write()` (Node);
|
|
@@ -2004,6 +2022,8 @@ interface DataState {
|
|
|
2004
2022
|
* the fetch settles, on success and on failure, so a failure is never cached.
|
|
2005
2023
|
*/
|
|
2006
2024
|
pending: Map<string, Promise<unknown | null>>;
|
|
2025
|
+
/** Cancels the scheduled idle callback or aborts the bundle fetch. `null` when nothing is scheduled. */
|
|
2026
|
+
cancelPreload: (() => void) | null;
|
|
2007
2027
|
}
|
|
2008
2028
|
/**
|
|
2009
2029
|
* Public API mounted at `app.data` — the agnostic data provider. `write()` is the
|
package/dist/browser.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-D7D4PA-g.mjs";
|
|
2
|
-
import { n as relativeDataFile, t as
|
|
2
|
+
import { n as dataSuffix, r as relativeDataFile, t as BUNDLE_FILE } from "./convention-BtVf429P.mjs";
|
|
3
3
|
import { envPlugin as envPlugin$1, logPlugin as logPlugin$1 } from "@moku-labs/common";
|
|
4
4
|
import { createCoreConfig } from "@moku-labs/core";
|
|
5
5
|
import { browserEnv, browserEnv as browserEnv$1, envPlugin, logPlugin } from "@moku-labs/common/browser";
|
|
@@ -2393,7 +2393,7 @@ async function fetchAndCache(ctx, path) {
|
|
|
2393
2393
|
/**
|
|
2394
2394
|
* Builds the data provider — the agnostic bridge. `write()` is the Node persist
|
|
2395
2395
|
* side; `at()` is the browser read side; `urlFor`/`fileFor` are the pure
|
|
2396
|
-
* convention.
|
|
2396
|
+
* convention. The optional preload lives in `./preload` (started by `onStart`).
|
|
2397
2397
|
*
|
|
2398
2398
|
* @param ctx - The data plugin context.
|
|
2399
2399
|
* @returns The {@link DataProvider} mounted at `app.data`.
|
|
@@ -2408,7 +2408,8 @@ function dataApi(ctx) {
|
|
|
2408
2408
|
return {
|
|
2409
2409
|
/**
|
|
2410
2410
|
* READ (browser) — fetch (and cache) the persisted data for a page path.
|
|
2411
|
-
* Concurrent calls for one path share one in-flight request.
|
|
2411
|
+
* Concurrent calls for one path share one in-flight request. It never waits
|
|
2412
|
+
* for an in-flight preload bundle: an uncached path is fetched at once.
|
|
2412
2413
|
* Returns the raw JSON as `unknown`, which the route uses directly as `ctx.data`
|
|
2413
2414
|
* (no route `.parse()`); returns `null` if the fetch or JSON parse fails (so
|
|
2414
2415
|
* `spa` can fall back to HTML).
|
|
@@ -2443,7 +2444,7 @@ function dataApi(ctx) {
|
|
|
2443
2444
|
* ```
|
|
2444
2445
|
*/
|
|
2445
2446
|
async write(entries, options) {
|
|
2446
|
-
const { writeData } = await import("./writer-
|
|
2447
|
+
const { writeData } = await import("./writer-Dwd02T9h.mjs");
|
|
2447
2448
|
return writeData(ctx, entries, options);
|
|
2448
2449
|
},
|
|
2449
2450
|
/**
|
|
@@ -2480,7 +2481,7 @@ function dataApi(ctx) {
|
|
|
2480
2481
|
* Typed default data config (R6: no inline `as`). `outputDir` is the WRITE path
|
|
2481
2482
|
* (filesystem, relative to the build `outDir`); `baseUrl` is the matching READ URL
|
|
2482
2483
|
* (site-root-relative) the browser fetches from — the defaults agree
|
|
2483
|
-
* (`"_data"` ↔ `"/_data/"`).
|
|
2484
|
+
* (`"_data"` ↔ `"/_data/"`). `preload` is `"none"`: lazy per-page fetches only.
|
|
2484
2485
|
*
|
|
2485
2486
|
* @example
|
|
2486
2487
|
* ```ts
|
|
@@ -2489,14 +2490,77 @@ function dataApi(ctx) {
|
|
|
2489
2490
|
*/
|
|
2490
2491
|
const defaultDataConfig = {
|
|
2491
2492
|
outputDir: "_data",
|
|
2492
|
-
baseUrl: "/_data/"
|
|
2493
|
+
baseUrl: "/_data/",
|
|
2494
|
+
preload: "none"
|
|
2493
2495
|
};
|
|
2494
2496
|
//#endregion
|
|
2497
|
+
//#region src/plugins/data/preload.ts
|
|
2498
|
+
/**
|
|
2499
|
+
* Fetches the bundle and caches every path not already cached. Never rejects:
|
|
2500
|
+
* a failed fetch leaves lazy `at()` working. Clears `cancelPreload` when done.
|
|
2501
|
+
*
|
|
2502
|
+
* @param ctx - The data plugin context.
|
|
2503
|
+
* @param signal - Aborts the fetch on stop.
|
|
2504
|
+
* @returns Resolves when the fill is done or failed.
|
|
2505
|
+
* @example
|
|
2506
|
+
* ```ts
|
|
2507
|
+
* await fill(ctx, signal);
|
|
2508
|
+
* ```
|
|
2509
|
+
*/
|
|
2510
|
+
async function fill(ctx, signal) {
|
|
2511
|
+
try {
|
|
2512
|
+
const response = await fetch(`${ctx.config.baseUrl}${BUNDLE_FILE}`, { signal });
|
|
2513
|
+
const bundle = response.ok ? await response.json() : {};
|
|
2514
|
+
for (const [path, data] of Object.entries(bundle)) if (!signal.aborted && !ctx.state.cache.has(path)) ctx.state.cache.set(path, data);
|
|
2515
|
+
} catch {} finally {
|
|
2516
|
+
ctx.state.cancelPreload = null;
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
/**
|
|
2520
|
+
* From `onStart`: when idle, fetch `<baseUrl>_all.json` once and fill the cache.
|
|
2521
|
+
* No-op on Node, with `preload: "none"`, with save-data on, or if already scheduled.
|
|
2522
|
+
*
|
|
2523
|
+
* @param ctx - The data plugin context.
|
|
2524
|
+
* @example
|
|
2525
|
+
* ```ts
|
|
2526
|
+
* startPreload(ctx);
|
|
2527
|
+
* ```
|
|
2528
|
+
*/
|
|
2529
|
+
function startPreload(ctx) {
|
|
2530
|
+
const nav = globalThis.navigator;
|
|
2531
|
+
if (ctx.config.preload !== "all" || typeof document === "undefined" || ctx.state.cancelPreload !== null || nav?.connection?.saveData === true) return;
|
|
2532
|
+
const controller = new AbortController();
|
|
2533
|
+
const run = () => {
|
|
2534
|
+
fill(ctx, controller.signal);
|
|
2535
|
+
};
|
|
2536
|
+
const idle = typeof requestIdleCallback === "function";
|
|
2537
|
+
const handle = idle ? requestIdleCallback(run, { timeout: 2e3 }) : setTimeout(run, 0);
|
|
2538
|
+
ctx.state.cancelPreload = () => {
|
|
2539
|
+
if (idle) cancelIdleCallback(handle);
|
|
2540
|
+
else clearTimeout(handle);
|
|
2541
|
+
controller.abort();
|
|
2542
|
+
};
|
|
2543
|
+
}
|
|
2544
|
+
/**
|
|
2545
|
+
* From `onStop`: cancel the idle callback or abort the bundle fetch.
|
|
2546
|
+
*
|
|
2547
|
+
* @param ctx - The data plugin context.
|
|
2548
|
+
* @example
|
|
2549
|
+
* ```ts
|
|
2550
|
+
* stopPreload(ctx);
|
|
2551
|
+
* ```
|
|
2552
|
+
*/
|
|
2553
|
+
function stopPreload(ctx) {
|
|
2554
|
+
ctx.state.cancelPreload?.();
|
|
2555
|
+
ctx.state.cancelPreload = null;
|
|
2556
|
+
}
|
|
2557
|
+
//#endregion
|
|
2495
2558
|
//#region src/plugins/data/state.ts
|
|
2496
2559
|
/**
|
|
2497
2560
|
* Creates initial data state: a null `lastWrite` slot (populated by the Node
|
|
2498
2561
|
* `write()` side), an empty `cache` (populated lazily by the browser `at(path)`
|
|
2499
|
-
* side on first fetch)
|
|
2562
|
+
* side on first fetch), an empty `pending` map of in-flight fetches, and no
|
|
2563
|
+
* preload scheduled.
|
|
2500
2564
|
*
|
|
2501
2565
|
* @param _ctx - Minimal context with global and config.
|
|
2502
2566
|
* @param _ctx.global - Global framework configuration.
|
|
@@ -2511,7 +2575,8 @@ function createDataState(_ctx) {
|
|
|
2511
2575
|
return {
|
|
2512
2576
|
lastWrite: null,
|
|
2513
2577
|
cache: /* @__PURE__ */ new Map(),
|
|
2514
|
-
pending: /* @__PURE__ */ new Map()
|
|
2578
|
+
pending: /* @__PURE__ */ new Map(),
|
|
2579
|
+
cancelPreload: null
|
|
2515
2580
|
};
|
|
2516
2581
|
}
|
|
2517
2582
|
//#endregion
|
|
@@ -2533,10 +2598,10 @@ function isInvalidBaseUrl(baseUrl) {
|
|
|
2533
2598
|
}
|
|
2534
2599
|
/**
|
|
2535
2600
|
* Validates the resolved data config: the browser `baseUrl` must be a non-empty,
|
|
2536
|
-
* site-root-relative URL path
|
|
2601
|
+
* site-root-relative URL path, and `preload` must be `"none"` or `"all"`.
|
|
2537
2602
|
*
|
|
2538
2603
|
* @param config - The resolved plugin configuration.
|
|
2539
|
-
* @throws {Error} If `baseUrl` is empty or not a rooted URL path.
|
|
2604
|
+
* @throws {Error} If `baseUrl` is empty or not a rooted URL path, or `preload` is unknown.
|
|
2540
2605
|
* @example
|
|
2541
2606
|
* ```ts
|
|
2542
2607
|
* validateDataConfig({ outputDir: "_data", baseUrl: "/_data/" });
|
|
@@ -2544,6 +2609,8 @@ function isInvalidBaseUrl(baseUrl) {
|
|
|
2544
2609
|
*/
|
|
2545
2610
|
function validateDataConfig(config) {
|
|
2546
2611
|
if (isInvalidBaseUrl(config.baseUrl)) throw new Error(`[web] data.baseUrl: must be a site-root-relative URL path starting with "/" (e.g. "/_data/").`);
|
|
2612
|
+
const { preload } = config;
|
|
2613
|
+
if (preload !== void 0 && preload !== "none" && preload !== "all") throw new Error(`[web] data.preload: must be "none" or "all".\n Set it to "all" in both apps, or remove it.`);
|
|
2547
2614
|
}
|
|
2548
2615
|
//#endregion
|
|
2549
2616
|
//#region src/plugins/data/index.ts
|
|
@@ -2560,7 +2627,8 @@ function validateDataConfig(config) {
|
|
|
2560
2627
|
* **No hard `depends`** — fully browser-composable; the `node:fs` writer is behind
|
|
2561
2628
|
* a lazy `import()` inside `write()`. Build ordering is a call-site contract: build
|
|
2562
2629
|
* writes data during its pages phase (after its Phase-0 clean), via `app.data.write`.
|
|
2563
|
-
*
|
|
2630
|
+
* `onStart` schedules the optional `preload: "all"` bundle fetch (browser, idle);
|
|
2631
|
+
* `onStop` cancels it.
|
|
2564
2632
|
* @see README.md
|
|
2565
2633
|
*/
|
|
2566
2634
|
/**
|
|
@@ -2586,7 +2654,9 @@ const dataPlugin = createPlugin$1("data", {
|
|
|
2586
2654
|
config: defaultDataConfig,
|
|
2587
2655
|
createState: createDataState,
|
|
2588
2656
|
onInit: (ctx) => validateDataConfig(ctx.config),
|
|
2589
|
-
api: dataApi
|
|
2657
|
+
api: dataApi,
|
|
2658
|
+
onStart: (ctx) => startPreload(ctx),
|
|
2659
|
+
onStop: (ctx) => stopPreload(ctx)
|
|
2590
2660
|
});
|
|
2591
2661
|
//#endregion
|
|
2592
2662
|
//#region src/plugins/spa/head.ts
|
|
@@ -70,7 +70,15 @@ function decodeSuffix(suffix) {
|
|
|
70
70
|
function relativeDataFile(outputDir, path) {
|
|
71
71
|
return `${outputDir.endsWith("/") ? outputDir.slice(0, -1) : outputDir}/${decodeSuffix(dataSuffix(path))}`;
|
|
72
72
|
}
|
|
73
|
+
/** Preload bundle file name. Page files end in `/index.json`, so it never collides. */
|
|
74
|
+
const BUNDLE_FILE = "_all.json";
|
|
73
75
|
//#endregion
|
|
76
|
+
Object.defineProperty(exports, "BUNDLE_FILE", {
|
|
77
|
+
enumerable: true,
|
|
78
|
+
get: function() {
|
|
79
|
+
return BUNDLE_FILE;
|
|
80
|
+
}
|
|
81
|
+
});
|
|
74
82
|
Object.defineProperty(exports, "dataSuffix", {
|
|
75
83
|
enumerable: true,
|
|
76
84
|
get: function() {
|
|
@@ -70,5 +70,7 @@ function decodeSuffix(suffix) {
|
|
|
70
70
|
function relativeDataFile(outputDir, path) {
|
|
71
71
|
return `${outputDir.endsWith("/") ? outputDir.slice(0, -1) : outputDir}/${decodeSuffix(dataSuffix(path))}`;
|
|
72
72
|
}
|
|
73
|
+
/** Preload bundle file name. Page files end in `/index.json`, so it never collides. */
|
|
74
|
+
const BUNDLE_FILE = "_all.json";
|
|
73
75
|
//#endregion
|
|
74
|
-
export {
|
|
76
|
+
export { dataSuffix as n, relativeDataFile as r, BUNDLE_FILE as t };
|
package/dist/index.cjs
CHANGED
|
@@ -30,7 +30,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
30
30
|
enumerable: true
|
|
31
31
|
}) : target, mod));
|
|
32
32
|
//#endregion
|
|
33
|
-
const require_convention = require("./convention-
|
|
33
|
+
const require_convention = require("./convention-BO-VoFLO.cjs");
|
|
34
34
|
const require_convention$1 = require("./convention-CsJw7CML.cjs");
|
|
35
35
|
let _moku_labs_common = require("@moku-labs/common");
|
|
36
36
|
let _moku_labs_core = require("@moku-labs/core");
|
|
@@ -4651,7 +4651,7 @@ async function fetchAndCache(ctx, path) {
|
|
|
4651
4651
|
/**
|
|
4652
4652
|
* Builds the data provider — the agnostic bridge. `write()` is the Node persist
|
|
4653
4653
|
* side; `at()` is the browser read side; `urlFor`/`fileFor` are the pure
|
|
4654
|
-
* convention.
|
|
4654
|
+
* convention. The optional preload lives in `./preload` (started by `onStart`).
|
|
4655
4655
|
*
|
|
4656
4656
|
* @param ctx - The data plugin context.
|
|
4657
4657
|
* @returns The {@link DataProvider} mounted at `app.data`.
|
|
@@ -4666,7 +4666,8 @@ function dataApi(ctx) {
|
|
|
4666
4666
|
return {
|
|
4667
4667
|
/**
|
|
4668
4668
|
* READ (browser) — fetch (and cache) the persisted data for a page path.
|
|
4669
|
-
* Concurrent calls for one path share one in-flight request.
|
|
4669
|
+
* Concurrent calls for one path share one in-flight request. It never waits
|
|
4670
|
+
* for an in-flight preload bundle: an uncached path is fetched at once.
|
|
4670
4671
|
* Returns the raw JSON as `unknown`, which the route uses directly as `ctx.data`
|
|
4671
4672
|
* (no route `.parse()`); returns `null` if the fetch or JSON parse fails (so
|
|
4672
4673
|
* `spa` can fall back to HTML).
|
|
@@ -4701,7 +4702,7 @@ function dataApi(ctx) {
|
|
|
4701
4702
|
* ```
|
|
4702
4703
|
*/
|
|
4703
4704
|
async write(entries, options) {
|
|
4704
|
-
const { writeData } = await Promise.resolve().then(() => require("./writer-
|
|
4705
|
+
const { writeData } = await Promise.resolve().then(() => require("./writer-ryba09yV.cjs"));
|
|
4705
4706
|
return writeData(ctx, entries, options);
|
|
4706
4707
|
},
|
|
4707
4708
|
/**
|
|
@@ -4738,7 +4739,7 @@ function dataApi(ctx) {
|
|
|
4738
4739
|
* Typed default data config (R6: no inline `as`). `outputDir` is the WRITE path
|
|
4739
4740
|
* (filesystem, relative to the build `outDir`); `baseUrl` is the matching READ URL
|
|
4740
4741
|
* (site-root-relative) the browser fetches from — the defaults agree
|
|
4741
|
-
* (`"_data"` ↔ `"/_data/"`).
|
|
4742
|
+
* (`"_data"` ↔ `"/_data/"`). `preload` is `"none"`: lazy per-page fetches only.
|
|
4742
4743
|
*
|
|
4743
4744
|
* @example
|
|
4744
4745
|
* ```ts
|
|
@@ -4747,14 +4748,77 @@ function dataApi(ctx) {
|
|
|
4747
4748
|
*/
|
|
4748
4749
|
const defaultDataConfig = {
|
|
4749
4750
|
outputDir: "_data",
|
|
4750
|
-
baseUrl: "/_data/"
|
|
4751
|
+
baseUrl: "/_data/",
|
|
4752
|
+
preload: "none"
|
|
4751
4753
|
};
|
|
4752
4754
|
//#endregion
|
|
4755
|
+
//#region src/plugins/data/preload.ts
|
|
4756
|
+
/**
|
|
4757
|
+
* Fetches the bundle and caches every path not already cached. Never rejects:
|
|
4758
|
+
* a failed fetch leaves lazy `at()` working. Clears `cancelPreload` when done.
|
|
4759
|
+
*
|
|
4760
|
+
* @param ctx - The data plugin context.
|
|
4761
|
+
* @param signal - Aborts the fetch on stop.
|
|
4762
|
+
* @returns Resolves when the fill is done or failed.
|
|
4763
|
+
* @example
|
|
4764
|
+
* ```ts
|
|
4765
|
+
* await fill(ctx, signal);
|
|
4766
|
+
* ```
|
|
4767
|
+
*/
|
|
4768
|
+
async function fill(ctx, signal) {
|
|
4769
|
+
try {
|
|
4770
|
+
const response = await fetch(`${ctx.config.baseUrl}${require_convention.BUNDLE_FILE}`, { signal });
|
|
4771
|
+
const bundle = response.ok ? await response.json() : {};
|
|
4772
|
+
for (const [path, data] of Object.entries(bundle)) if (!signal.aborted && !ctx.state.cache.has(path)) ctx.state.cache.set(path, data);
|
|
4773
|
+
} catch {} finally {
|
|
4774
|
+
ctx.state.cancelPreload = null;
|
|
4775
|
+
}
|
|
4776
|
+
}
|
|
4777
|
+
/**
|
|
4778
|
+
* From `onStart`: when idle, fetch `<baseUrl>_all.json` once and fill the cache.
|
|
4779
|
+
* No-op on Node, with `preload: "none"`, with save-data on, or if already scheduled.
|
|
4780
|
+
*
|
|
4781
|
+
* @param ctx - The data plugin context.
|
|
4782
|
+
* @example
|
|
4783
|
+
* ```ts
|
|
4784
|
+
* startPreload(ctx);
|
|
4785
|
+
* ```
|
|
4786
|
+
*/
|
|
4787
|
+
function startPreload(ctx) {
|
|
4788
|
+
const nav = globalThis.navigator;
|
|
4789
|
+
if (ctx.config.preload !== "all" || typeof document === "undefined" || ctx.state.cancelPreload !== null || nav?.connection?.saveData === true) return;
|
|
4790
|
+
const controller = new AbortController();
|
|
4791
|
+
const run = () => {
|
|
4792
|
+
fill(ctx, controller.signal);
|
|
4793
|
+
};
|
|
4794
|
+
const idle = typeof requestIdleCallback === "function";
|
|
4795
|
+
const handle = idle ? requestIdleCallback(run, { timeout: 2e3 }) : setTimeout(run, 0);
|
|
4796
|
+
ctx.state.cancelPreload = () => {
|
|
4797
|
+
if (idle) cancelIdleCallback(handle);
|
|
4798
|
+
else clearTimeout(handle);
|
|
4799
|
+
controller.abort();
|
|
4800
|
+
};
|
|
4801
|
+
}
|
|
4802
|
+
/**
|
|
4803
|
+
* From `onStop`: cancel the idle callback or abort the bundle fetch.
|
|
4804
|
+
*
|
|
4805
|
+
* @param ctx - The data plugin context.
|
|
4806
|
+
* @example
|
|
4807
|
+
* ```ts
|
|
4808
|
+
* stopPreload(ctx);
|
|
4809
|
+
* ```
|
|
4810
|
+
*/
|
|
4811
|
+
function stopPreload(ctx) {
|
|
4812
|
+
ctx.state.cancelPreload?.();
|
|
4813
|
+
ctx.state.cancelPreload = null;
|
|
4814
|
+
}
|
|
4815
|
+
//#endregion
|
|
4753
4816
|
//#region src/plugins/data/state.ts
|
|
4754
4817
|
/**
|
|
4755
4818
|
* Creates initial data state: a null `lastWrite` slot (populated by the Node
|
|
4756
4819
|
* `write()` side), an empty `cache` (populated lazily by the browser `at(path)`
|
|
4757
|
-
* side on first fetch)
|
|
4820
|
+
* side on first fetch), an empty `pending` map of in-flight fetches, and no
|
|
4821
|
+
* preload scheduled.
|
|
4758
4822
|
*
|
|
4759
4823
|
* @param _ctx - Minimal context with global and config.
|
|
4760
4824
|
* @param _ctx.global - Global framework configuration.
|
|
@@ -4769,7 +4833,8 @@ function createDataState(_ctx) {
|
|
|
4769
4833
|
return {
|
|
4770
4834
|
lastWrite: null,
|
|
4771
4835
|
cache: /* @__PURE__ */ new Map(),
|
|
4772
|
-
pending: /* @__PURE__ */ new Map()
|
|
4836
|
+
pending: /* @__PURE__ */ new Map(),
|
|
4837
|
+
cancelPreload: null
|
|
4773
4838
|
};
|
|
4774
4839
|
}
|
|
4775
4840
|
//#endregion
|
|
@@ -4791,10 +4856,10 @@ function isInvalidBaseUrl$1(baseUrl) {
|
|
|
4791
4856
|
}
|
|
4792
4857
|
/**
|
|
4793
4858
|
* Validates the resolved data config: the browser `baseUrl` must be a non-empty,
|
|
4794
|
-
* site-root-relative URL path
|
|
4859
|
+
* site-root-relative URL path, and `preload` must be `"none"` or `"all"`.
|
|
4795
4860
|
*
|
|
4796
4861
|
* @param config - The resolved plugin configuration.
|
|
4797
|
-
* @throws {Error} If `baseUrl` is empty or not a rooted URL path.
|
|
4862
|
+
* @throws {Error} If `baseUrl` is empty or not a rooted URL path, or `preload` is unknown.
|
|
4798
4863
|
* @example
|
|
4799
4864
|
* ```ts
|
|
4800
4865
|
* validateDataConfig({ outputDir: "_data", baseUrl: "/_data/" });
|
|
@@ -4802,6 +4867,8 @@ function isInvalidBaseUrl$1(baseUrl) {
|
|
|
4802
4867
|
*/
|
|
4803
4868
|
function validateDataConfig(config) {
|
|
4804
4869
|
if (isInvalidBaseUrl$1(config.baseUrl)) throw new Error(`[web] data.baseUrl: must be a site-root-relative URL path starting with "/" (e.g. "/_data/").`);
|
|
4870
|
+
const { preload } = config;
|
|
4871
|
+
if (preload !== void 0 && preload !== "none" && preload !== "all") throw new Error(`[web] data.preload: must be "none" or "all".\n Set it to "all" in both apps, or remove it.`);
|
|
4805
4872
|
}
|
|
4806
4873
|
//#endregion
|
|
4807
4874
|
//#region src/plugins/data/index.ts
|
|
@@ -4818,7 +4885,8 @@ function validateDataConfig(config) {
|
|
|
4818
4885
|
* **No hard `depends`** — fully browser-composable; the `node:fs` writer is behind
|
|
4819
4886
|
* a lazy `import()` inside `write()`. Build ordering is a call-site contract: build
|
|
4820
4887
|
* writes data during its pages phase (after its Phase-0 clean), via `app.data.write`.
|
|
4821
|
-
*
|
|
4888
|
+
* `onStart` schedules the optional `preload: "all"` bundle fetch (browser, idle);
|
|
4889
|
+
* `onStop` cancels it.
|
|
4822
4890
|
* @see README.md
|
|
4823
4891
|
*/
|
|
4824
4892
|
/**
|
|
@@ -4844,7 +4912,9 @@ const dataPlugin = createPlugin$1("data", {
|
|
|
4844
4912
|
config: defaultDataConfig,
|
|
4845
4913
|
createState: createDataState,
|
|
4846
4914
|
onInit: (ctx) => validateDataConfig(ctx.config),
|
|
4847
|
-
api: dataApi
|
|
4915
|
+
api: dataApi,
|
|
4916
|
+
onStart: (ctx) => startPreload(ctx),
|
|
4917
|
+
onStop: (ctx) => stopPreload(ctx)
|
|
4848
4918
|
});
|
|
4849
4919
|
//#endregion
|
|
4850
4920
|
//#region src/plugins/build/phases/pages.tsx
|
package/dist/index.d.cts
CHANGED
|
@@ -3400,7 +3400,7 @@ declare const contentPlugin: import("@moku-labs/core").PluginInstance<"content",
|
|
|
3400
3400
|
};
|
|
3401
3401
|
}> & Record<never, never>;
|
|
3402
3402
|
declare namespace types_d_exports$4 {
|
|
3403
|
-
export { DataConfig, DataEntry, DataProvider, DataState, DataWriteSummary };
|
|
3403
|
+
export { DataConfig, DataEntry, DataPreload, DataProvider, DataState, DataWriteSummary };
|
|
3404
3404
|
}
|
|
3405
3405
|
/**
|
|
3406
3406
|
* @file data plugin — type definitions (Standard tier).
|
|
@@ -3420,6 +3420,12 @@ declare namespace types_d_exports$4 {
|
|
|
3420
3420
|
* inside `write()`, so composing `data` in a browser app keeps the bundle free of
|
|
3421
3421
|
* `node:*`.
|
|
3422
3422
|
*/
|
|
3423
|
+
/**
|
|
3424
|
+
* How the browser loads page data. `"none"` fetches one page file per navigation
|
|
3425
|
+
* (lazy). `"all"` also loads every page's data once, from one bundle file, after
|
|
3426
|
+
* the app starts and the main thread is idle.
|
|
3427
|
+
*/
|
|
3428
|
+
type DataPreload = "none" | "all";
|
|
3423
3429
|
/**
|
|
3424
3430
|
* Configuration for {@link dataPlugin}. All fields have defaults (see `./config`).
|
|
3425
3431
|
*
|
|
@@ -3440,6 +3446,13 @@ type DataConfig = {
|
|
|
3440
3446
|
* path); keep them consistent (`"/" + trim(outputDir) + "/"`). Default `"/_data/"`.
|
|
3441
3447
|
*/
|
|
3442
3448
|
baseUrl: string;
|
|
3449
|
+
/**
|
|
3450
|
+
* BOTH sides. `"all"`: the Node `write()` also writes one bundle file
|
|
3451
|
+
* `<outputDir>/_all.json` (`{ "<page path>": data }`), and the browser fetches
|
|
3452
|
+
* it once when idle and fills the `at()` cache. Set the same value in the Node
|
|
3453
|
+
* app and the browser app. Default `"none"`: no bundle, lazy per-page fetches.
|
|
3454
|
+
*/
|
|
3455
|
+
preload?: DataPreload;
|
|
3443
3456
|
};
|
|
3444
3457
|
/** One page's data to persist — `build` produces these from its route expansion. */
|
|
3445
3458
|
interface DataEntry {
|
|
@@ -3456,6 +3469,11 @@ interface DataWriteSummary {
|
|
|
3456
3469
|
bytes: number;
|
|
3457
3470
|
/** The written file paths, relative to the build `outDir`. */
|
|
3458
3471
|
files: string[];
|
|
3472
|
+
/** The preload bundle, present only when `preload` is `"all"`. Not counted in `fileCount`/`bytes`. */
|
|
3473
|
+
bundle?: {
|
|
3474
|
+
file: string;
|
|
3475
|
+
bytes: number;
|
|
3476
|
+
};
|
|
3459
3477
|
}
|
|
3460
3478
|
/**
|
|
3461
3479
|
* Internal data state. `lastWrite` records the most recent `write()` (Node);
|
|
@@ -3473,6 +3491,8 @@ interface DataState {
|
|
|
3473
3491
|
* the fetch settles, on success and on failure, so a failure is never cached.
|
|
3474
3492
|
*/
|
|
3475
3493
|
pending: Map<string, Promise<unknown | null>>;
|
|
3494
|
+
/** Cancels the scheduled idle callback or aborts the bundle fetch. `null` when nothing is scheduled. */
|
|
3495
|
+
cancelPreload: (() => void) | null;
|
|
3476
3496
|
}
|
|
3477
3497
|
/**
|
|
3478
3498
|
* Public API mounted at `app.data` — the agnostic data provider. `write()` is the
|
package/dist/index.d.mts
CHANGED
|
@@ -3398,7 +3398,7 @@ declare const contentPlugin: import("@moku-labs/core").PluginInstance<"content",
|
|
|
3398
3398
|
};
|
|
3399
3399
|
}> & Record<never, never>;
|
|
3400
3400
|
declare namespace types_d_exports$4 {
|
|
3401
|
-
export { DataConfig, DataEntry, DataProvider, DataState, DataWriteSummary };
|
|
3401
|
+
export { DataConfig, DataEntry, DataPreload, DataProvider, DataState, DataWriteSummary };
|
|
3402
3402
|
}
|
|
3403
3403
|
/**
|
|
3404
3404
|
* @file data plugin — type definitions (Standard tier).
|
|
@@ -3418,6 +3418,12 @@ declare namespace types_d_exports$4 {
|
|
|
3418
3418
|
* inside `write()`, so composing `data` in a browser app keeps the bundle free of
|
|
3419
3419
|
* `node:*`.
|
|
3420
3420
|
*/
|
|
3421
|
+
/**
|
|
3422
|
+
* How the browser loads page data. `"none"` fetches one page file per navigation
|
|
3423
|
+
* (lazy). `"all"` also loads every page's data once, from one bundle file, after
|
|
3424
|
+
* the app starts and the main thread is idle.
|
|
3425
|
+
*/
|
|
3426
|
+
type DataPreload = "none" | "all";
|
|
3421
3427
|
/**
|
|
3422
3428
|
* Configuration for {@link dataPlugin}. All fields have defaults (see `./config`).
|
|
3423
3429
|
*
|
|
@@ -3438,6 +3444,13 @@ type DataConfig = {
|
|
|
3438
3444
|
* path); keep them consistent (`"/" + trim(outputDir) + "/"`). Default `"/_data/"`.
|
|
3439
3445
|
*/
|
|
3440
3446
|
baseUrl: string;
|
|
3447
|
+
/**
|
|
3448
|
+
* BOTH sides. `"all"`: the Node `write()` also writes one bundle file
|
|
3449
|
+
* `<outputDir>/_all.json` (`{ "<page path>": data }`), and the browser fetches
|
|
3450
|
+
* it once when idle and fills the `at()` cache. Set the same value in the Node
|
|
3451
|
+
* app and the browser app. Default `"none"`: no bundle, lazy per-page fetches.
|
|
3452
|
+
*/
|
|
3453
|
+
preload?: DataPreload;
|
|
3441
3454
|
};
|
|
3442
3455
|
/** One page's data to persist — `build` produces these from its route expansion. */
|
|
3443
3456
|
interface DataEntry {
|
|
@@ -3454,6 +3467,11 @@ interface DataWriteSummary {
|
|
|
3454
3467
|
bytes: number;
|
|
3455
3468
|
/** The written file paths, relative to the build `outDir`. */
|
|
3456
3469
|
files: string[];
|
|
3470
|
+
/** The preload bundle, present only when `preload` is `"all"`. Not counted in `fileCount`/`bytes`. */
|
|
3471
|
+
bundle?: {
|
|
3472
|
+
file: string;
|
|
3473
|
+
bytes: number;
|
|
3474
|
+
};
|
|
3457
3475
|
}
|
|
3458
3476
|
/**
|
|
3459
3477
|
* Internal data state. `lastWrite` records the most recent `write()` (Node);
|
|
@@ -3471,6 +3489,8 @@ interface DataState {
|
|
|
3471
3489
|
* the fetch settles, on success and on failure, so a failure is never cached.
|
|
3472
3490
|
*/
|
|
3473
3491
|
pending: Map<string, Promise<unknown | null>>;
|
|
3492
|
+
/** Cancels the scheduled idle callback or aborts the bundle fetch. `null` when nothing is scheduled. */
|
|
3493
|
+
cancelPreload: (() => void) | null;
|
|
3474
3494
|
}
|
|
3475
3495
|
/**
|
|
3476
3496
|
* Public API mounted at `app.data` — the agnostic data provider. `write()` is the
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-D7D4PA-g.mjs";
|
|
2
|
-
import { n as relativeDataFile, t as
|
|
2
|
+
import { n as dataSuffix, r as relativeDataFile, t as BUNDLE_FILE } from "./convention-BtVf429P.mjs";
|
|
3
3
|
import { n as relativeShardFile, t as collectionUrl } from "./convention-Bs8SrQS9.mjs";
|
|
4
4
|
import { browserEnv, cloudflareBindings, dotenv, envPlugin, envPlugin as envPlugin$1, logPlugin, logPlugin as logPlugin$1, processEnv } from "@moku-labs/common";
|
|
5
5
|
import { createCoreConfig } from "@moku-labs/core";
|
|
@@ -4607,7 +4607,7 @@ async function fetchAndCache(ctx, path) {
|
|
|
4607
4607
|
/**
|
|
4608
4608
|
* Builds the data provider — the agnostic bridge. `write()` is the Node persist
|
|
4609
4609
|
* side; `at()` is the browser read side; `urlFor`/`fileFor` are the pure
|
|
4610
|
-
* convention.
|
|
4610
|
+
* convention. The optional preload lives in `./preload` (started by `onStart`).
|
|
4611
4611
|
*
|
|
4612
4612
|
* @param ctx - The data plugin context.
|
|
4613
4613
|
* @returns The {@link DataProvider} mounted at `app.data`.
|
|
@@ -4622,7 +4622,8 @@ function dataApi(ctx) {
|
|
|
4622
4622
|
return {
|
|
4623
4623
|
/**
|
|
4624
4624
|
* READ (browser) — fetch (and cache) the persisted data for a page path.
|
|
4625
|
-
* Concurrent calls for one path share one in-flight request.
|
|
4625
|
+
* Concurrent calls for one path share one in-flight request. It never waits
|
|
4626
|
+
* for an in-flight preload bundle: an uncached path is fetched at once.
|
|
4626
4627
|
* Returns the raw JSON as `unknown`, which the route uses directly as `ctx.data`
|
|
4627
4628
|
* (no route `.parse()`); returns `null` if the fetch or JSON parse fails (so
|
|
4628
4629
|
* `spa` can fall back to HTML).
|
|
@@ -4657,7 +4658,7 @@ function dataApi(ctx) {
|
|
|
4657
4658
|
* ```
|
|
4658
4659
|
*/
|
|
4659
4660
|
async write(entries, options) {
|
|
4660
|
-
const { writeData } = await import("./writer-
|
|
4661
|
+
const { writeData } = await import("./writer-Dwd02T9h.mjs");
|
|
4661
4662
|
return writeData(ctx, entries, options);
|
|
4662
4663
|
},
|
|
4663
4664
|
/**
|
|
@@ -4694,7 +4695,7 @@ function dataApi(ctx) {
|
|
|
4694
4695
|
* Typed default data config (R6: no inline `as`). `outputDir` is the WRITE path
|
|
4695
4696
|
* (filesystem, relative to the build `outDir`); `baseUrl` is the matching READ URL
|
|
4696
4697
|
* (site-root-relative) the browser fetches from — the defaults agree
|
|
4697
|
-
* (`"_data"` ↔ `"/_data/"`).
|
|
4698
|
+
* (`"_data"` ↔ `"/_data/"`). `preload` is `"none"`: lazy per-page fetches only.
|
|
4698
4699
|
*
|
|
4699
4700
|
* @example
|
|
4700
4701
|
* ```ts
|
|
@@ -4703,14 +4704,77 @@ function dataApi(ctx) {
|
|
|
4703
4704
|
*/
|
|
4704
4705
|
const defaultDataConfig = {
|
|
4705
4706
|
outputDir: "_data",
|
|
4706
|
-
baseUrl: "/_data/"
|
|
4707
|
+
baseUrl: "/_data/",
|
|
4708
|
+
preload: "none"
|
|
4707
4709
|
};
|
|
4708
4710
|
//#endregion
|
|
4711
|
+
//#region src/plugins/data/preload.ts
|
|
4712
|
+
/**
|
|
4713
|
+
* Fetches the bundle and caches every path not already cached. Never rejects:
|
|
4714
|
+
* a failed fetch leaves lazy `at()` working. Clears `cancelPreload` when done.
|
|
4715
|
+
*
|
|
4716
|
+
* @param ctx - The data plugin context.
|
|
4717
|
+
* @param signal - Aborts the fetch on stop.
|
|
4718
|
+
* @returns Resolves when the fill is done or failed.
|
|
4719
|
+
* @example
|
|
4720
|
+
* ```ts
|
|
4721
|
+
* await fill(ctx, signal);
|
|
4722
|
+
* ```
|
|
4723
|
+
*/
|
|
4724
|
+
async function fill(ctx, signal) {
|
|
4725
|
+
try {
|
|
4726
|
+
const response = await fetch(`${ctx.config.baseUrl}${BUNDLE_FILE}`, { signal });
|
|
4727
|
+
const bundle = response.ok ? await response.json() : {};
|
|
4728
|
+
for (const [path, data] of Object.entries(bundle)) if (!signal.aborted && !ctx.state.cache.has(path)) ctx.state.cache.set(path, data);
|
|
4729
|
+
} catch {} finally {
|
|
4730
|
+
ctx.state.cancelPreload = null;
|
|
4731
|
+
}
|
|
4732
|
+
}
|
|
4733
|
+
/**
|
|
4734
|
+
* From `onStart`: when idle, fetch `<baseUrl>_all.json` once and fill the cache.
|
|
4735
|
+
* No-op on Node, with `preload: "none"`, with save-data on, or if already scheduled.
|
|
4736
|
+
*
|
|
4737
|
+
* @param ctx - The data plugin context.
|
|
4738
|
+
* @example
|
|
4739
|
+
* ```ts
|
|
4740
|
+
* startPreload(ctx);
|
|
4741
|
+
* ```
|
|
4742
|
+
*/
|
|
4743
|
+
function startPreload(ctx) {
|
|
4744
|
+
const nav = globalThis.navigator;
|
|
4745
|
+
if (ctx.config.preload !== "all" || typeof document === "undefined" || ctx.state.cancelPreload !== null || nav?.connection?.saveData === true) return;
|
|
4746
|
+
const controller = new AbortController();
|
|
4747
|
+
const run = () => {
|
|
4748
|
+
fill(ctx, controller.signal);
|
|
4749
|
+
};
|
|
4750
|
+
const idle = typeof requestIdleCallback === "function";
|
|
4751
|
+
const handle = idle ? requestIdleCallback(run, { timeout: 2e3 }) : setTimeout(run, 0);
|
|
4752
|
+
ctx.state.cancelPreload = () => {
|
|
4753
|
+
if (idle) cancelIdleCallback(handle);
|
|
4754
|
+
else clearTimeout(handle);
|
|
4755
|
+
controller.abort();
|
|
4756
|
+
};
|
|
4757
|
+
}
|
|
4758
|
+
/**
|
|
4759
|
+
* From `onStop`: cancel the idle callback or abort the bundle fetch.
|
|
4760
|
+
*
|
|
4761
|
+
* @param ctx - The data plugin context.
|
|
4762
|
+
* @example
|
|
4763
|
+
* ```ts
|
|
4764
|
+
* stopPreload(ctx);
|
|
4765
|
+
* ```
|
|
4766
|
+
*/
|
|
4767
|
+
function stopPreload(ctx) {
|
|
4768
|
+
ctx.state.cancelPreload?.();
|
|
4769
|
+
ctx.state.cancelPreload = null;
|
|
4770
|
+
}
|
|
4771
|
+
//#endregion
|
|
4709
4772
|
//#region src/plugins/data/state.ts
|
|
4710
4773
|
/**
|
|
4711
4774
|
* Creates initial data state: a null `lastWrite` slot (populated by the Node
|
|
4712
4775
|
* `write()` side), an empty `cache` (populated lazily by the browser `at(path)`
|
|
4713
|
-
* side on first fetch)
|
|
4776
|
+
* side on first fetch), an empty `pending` map of in-flight fetches, and no
|
|
4777
|
+
* preload scheduled.
|
|
4714
4778
|
*
|
|
4715
4779
|
* @param _ctx - Minimal context with global and config.
|
|
4716
4780
|
* @param _ctx.global - Global framework configuration.
|
|
@@ -4725,7 +4789,8 @@ function createDataState(_ctx) {
|
|
|
4725
4789
|
return {
|
|
4726
4790
|
lastWrite: null,
|
|
4727
4791
|
cache: /* @__PURE__ */ new Map(),
|
|
4728
|
-
pending: /* @__PURE__ */ new Map()
|
|
4792
|
+
pending: /* @__PURE__ */ new Map(),
|
|
4793
|
+
cancelPreload: null
|
|
4729
4794
|
};
|
|
4730
4795
|
}
|
|
4731
4796
|
//#endregion
|
|
@@ -4747,10 +4812,10 @@ function isInvalidBaseUrl$1(baseUrl) {
|
|
|
4747
4812
|
}
|
|
4748
4813
|
/**
|
|
4749
4814
|
* Validates the resolved data config: the browser `baseUrl` must be a non-empty,
|
|
4750
|
-
* site-root-relative URL path
|
|
4815
|
+
* site-root-relative URL path, and `preload` must be `"none"` or `"all"`.
|
|
4751
4816
|
*
|
|
4752
4817
|
* @param config - The resolved plugin configuration.
|
|
4753
|
-
* @throws {Error} If `baseUrl` is empty or not a rooted URL path.
|
|
4818
|
+
* @throws {Error} If `baseUrl` is empty or not a rooted URL path, or `preload` is unknown.
|
|
4754
4819
|
* @example
|
|
4755
4820
|
* ```ts
|
|
4756
4821
|
* validateDataConfig({ outputDir: "_data", baseUrl: "/_data/" });
|
|
@@ -4758,6 +4823,8 @@ function isInvalidBaseUrl$1(baseUrl) {
|
|
|
4758
4823
|
*/
|
|
4759
4824
|
function validateDataConfig(config) {
|
|
4760
4825
|
if (isInvalidBaseUrl$1(config.baseUrl)) throw new Error(`[web] data.baseUrl: must be a site-root-relative URL path starting with "/" (e.g. "/_data/").`);
|
|
4826
|
+
const { preload } = config;
|
|
4827
|
+
if (preload !== void 0 && preload !== "none" && preload !== "all") throw new Error(`[web] data.preload: must be "none" or "all".\n Set it to "all" in both apps, or remove it.`);
|
|
4761
4828
|
}
|
|
4762
4829
|
//#endregion
|
|
4763
4830
|
//#region src/plugins/data/index.ts
|
|
@@ -4774,7 +4841,8 @@ function validateDataConfig(config) {
|
|
|
4774
4841
|
* **No hard `depends`** — fully browser-composable; the `node:fs` writer is behind
|
|
4775
4842
|
* a lazy `import()` inside `write()`. Build ordering is a call-site contract: build
|
|
4776
4843
|
* writes data during its pages phase (after its Phase-0 clean), via `app.data.write`.
|
|
4777
|
-
*
|
|
4844
|
+
* `onStart` schedules the optional `preload: "all"` bundle fetch (browser, idle);
|
|
4845
|
+
* `onStop` cancels it.
|
|
4778
4846
|
* @see README.md
|
|
4779
4847
|
*/
|
|
4780
4848
|
/**
|
|
@@ -4800,7 +4868,9 @@ const dataPlugin = createPlugin$1("data", {
|
|
|
4800
4868
|
config: defaultDataConfig,
|
|
4801
4869
|
createState: createDataState,
|
|
4802
4870
|
onInit: (ctx) => validateDataConfig(ctx.config),
|
|
4803
|
-
api: dataApi
|
|
4871
|
+
api: dataApi,
|
|
4872
|
+
onStart: (ctx) => startPreload(ctx),
|
|
4873
|
+
onStop: (ctx) => stopPreload(ctx)
|
|
4804
4874
|
});
|
|
4805
4875
|
//#endregion
|
|
4806
4876
|
//#region src/plugins/build/phases/pages.tsx
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { r as relativeDataFile, t as BUNDLE_FILE } from "./convention-BtVf429P.mjs";
|
|
2
2
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import pLimit from "p-limit";
|
|
@@ -63,8 +63,36 @@ function compileSummary(written) {
|
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
65
|
/**
|
|
66
|
+
* Persist the preload bundle: one JSON object `{ "<page path>": data }` keyed by
|
|
67
|
+
* each entry's `path`, exactly the key `at(path)` caches under. A later entry for
|
|
68
|
+
* the same path wins, like the per-page file it overwrites.
|
|
69
|
+
*
|
|
70
|
+
* @param entries - The per-page data to bundle.
|
|
71
|
+
* @param outDir - The build output root.
|
|
72
|
+
* @param outputDir - The configured data output subdir.
|
|
73
|
+
* @returns The written bundle's `outDir`-relative path and byte length.
|
|
74
|
+
* @example
|
|
75
|
+
* ```ts
|
|
76
|
+
* await writeBundle([{ path: "/en/hello/", data }], "./dist", "_data");
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
async function writeBundle(entries, outDir, outputDir) {
|
|
80
|
+
const bundle = {};
|
|
81
|
+
for (const entry of entries) bundle[entry.path] = entry.data;
|
|
82
|
+
const file = `${outputDir.endsWith("/") ? outputDir.slice(0, -1) : outputDir}/${BUNDLE_FILE}`;
|
|
83
|
+
const filePath = path.join(outDir, file);
|
|
84
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
85
|
+
const body = JSON.stringify(bundle);
|
|
86
|
+
await writeFile(filePath, body, "utf8");
|
|
87
|
+
return {
|
|
88
|
+
file,
|
|
89
|
+
bytes: Buffer.byteLength(body, "utf8")
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
66
93
|
* The Node write side of the provider. Persists one JSON file per entry (bounded
|
|
67
94
|
* by `p-limit`) — domain-agnostic, no route expansion (`build` already did that).
|
|
95
|
+
* With `preload: "all"` it also writes the bundle file (see {@link writeBundle}).
|
|
68
96
|
* Records the summary in `ctx.state.lastWrite`.
|
|
69
97
|
*
|
|
70
98
|
* @param ctx - The data plugin context (state, config).
|
|
@@ -81,6 +109,7 @@ async function writeData(ctx, entries, options) {
|
|
|
81
109
|
const outDir = options?.outDir ?? DEFAULT_OUT_DIR;
|
|
82
110
|
const limit = pLimit(WRITE_CONCURRENCY);
|
|
83
111
|
const summary = compileSummary(await Promise.all(entries.map((entry) => limit(() => writeEntry(entry, outDir, ctx.config.outputDir)))));
|
|
112
|
+
if (ctx.config.preload === "all") summary.bundle = await writeBundle(entries, outDir, ctx.config.outputDir);
|
|
84
113
|
ctx.state.lastWrite = summary;
|
|
85
114
|
return summary;
|
|
86
115
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_convention = require("./convention-
|
|
1
|
+
const require_convention = require("./convention-BO-VoFLO.cjs");
|
|
2
2
|
const require_index = require("./index.cjs");
|
|
3
3
|
let node_fs_promises = require("node:fs/promises");
|
|
4
4
|
let node_path = require("node:path");
|
|
@@ -66,8 +66,36 @@ function compileSummary(written) {
|
|
|
66
66
|
};
|
|
67
67
|
}
|
|
68
68
|
/**
|
|
69
|
+
* Persist the preload bundle: one JSON object `{ "<page path>": data }` keyed by
|
|
70
|
+
* each entry's `path`, exactly the key `at(path)` caches under. A later entry for
|
|
71
|
+
* the same path wins, like the per-page file it overwrites.
|
|
72
|
+
*
|
|
73
|
+
* @param entries - The per-page data to bundle.
|
|
74
|
+
* @param outDir - The build output root.
|
|
75
|
+
* @param outputDir - The configured data output subdir.
|
|
76
|
+
* @returns The written bundle's `outDir`-relative path and byte length.
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* await writeBundle([{ path: "/en/hello/", data }], "./dist", "_data");
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
async function writeBundle(entries, outDir, outputDir) {
|
|
83
|
+
const bundle = {};
|
|
84
|
+
for (const entry of entries) bundle[entry.path] = entry.data;
|
|
85
|
+
const file = `${outputDir.endsWith("/") ? outputDir.slice(0, -1) : outputDir}/${require_convention.BUNDLE_FILE}`;
|
|
86
|
+
const filePath = node_path.default.join(outDir, file);
|
|
87
|
+
await (0, node_fs_promises.mkdir)(node_path.default.dirname(filePath), { recursive: true });
|
|
88
|
+
const body = JSON.stringify(bundle);
|
|
89
|
+
await (0, node_fs_promises.writeFile)(filePath, body, "utf8");
|
|
90
|
+
return {
|
|
91
|
+
file,
|
|
92
|
+
bytes: Buffer.byteLength(body, "utf8")
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
69
96
|
* The Node write side of the provider. Persists one JSON file per entry (bounded
|
|
70
97
|
* by `p-limit`) — domain-agnostic, no route expansion (`build` already did that).
|
|
98
|
+
* With `preload: "all"` it also writes the bundle file (see {@link writeBundle}).
|
|
71
99
|
* Records the summary in `ctx.state.lastWrite`.
|
|
72
100
|
*
|
|
73
101
|
* @param ctx - The data plugin context (state, config).
|
|
@@ -84,6 +112,7 @@ async function writeData(ctx, entries, options) {
|
|
|
84
112
|
const outDir = options?.outDir ?? DEFAULT_OUT_DIR;
|
|
85
113
|
const limit = (0, p_limit.default)(WRITE_CONCURRENCY);
|
|
86
114
|
const summary = compileSummary(await Promise.all(entries.map((entry) => limit(() => writeEntry(entry, outDir, ctx.config.outputDir)))));
|
|
115
|
+
if (ctx.config.preload === "all") summary.bundle = await writeBundle(entries, outDir, ctx.config.outputDir);
|
|
87
116
|
ctx.state.lastWrite = summary;
|
|
88
117
|
return summary;
|
|
89
118
|
}
|
package/package.json
CHANGED