@moku-labs/web 0.3.1 → 0.4.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 +52 -12
- package/dist/convention-Dr8jxG70.cjs +81 -0
- package/dist/convention-X3zLTlJ8.mjs +33 -0
- package/dist/index.cjs +1304 -318
- package/dist/index.d.cts +940 -622
- package/dist/index.d.mts +939 -621
- package/dist/index.mjs +1275 -268
- package/dist/render-BL9Fv6G6.mjs +20 -0
- package/dist/render-BSTM0Akv.cjs +20 -0
- package/dist/writer-BcWqa_7I.mjs +90 -0
- package/dist/writer-DAF0pM25.cjs +92 -0
- package/package.json +3 -2
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { render } from "preact";
|
|
2
|
+
//#region src/plugins/spa/render.ts
|
|
3
|
+
/**
|
|
4
|
+
* Render a route's `VNode` into the live swap region, replacing its contents.
|
|
5
|
+
* Reuses the build's component output verbatim (same `route.render`), so the
|
|
6
|
+
* client paint matches the SSG paint.
|
|
7
|
+
*
|
|
8
|
+
* @param vnode - The VNode produced by the matched route's `.render(ctx)`.
|
|
9
|
+
* @param region - The swap-region element to render into.
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const { renderVNode } = await import("./render");
|
|
13
|
+
* renderVNode(route._handlers.render(ctx), document.querySelector("main > section"));
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
function renderVNode(vnode, region) {
|
|
17
|
+
render(vnode, region);
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
export { renderVNode };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
let preact = require("preact");
|
|
2
|
+
//#region src/plugins/spa/render.ts
|
|
3
|
+
/**
|
|
4
|
+
* Render a route's `VNode` into the live swap region, replacing its contents.
|
|
5
|
+
* Reuses the build's component output verbatim (same `route.render`), so the
|
|
6
|
+
* client paint matches the SSG paint.
|
|
7
|
+
*
|
|
8
|
+
* @param vnode - The VNode produced by the matched route's `.render(ctx)`.
|
|
9
|
+
* @param region - The swap-region element to render into.
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const { renderVNode } = await import("./render");
|
|
13
|
+
* renderVNode(route._handlers.render(ctx), document.querySelector("main > section"));
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
function renderVNode(vnode, region) {
|
|
17
|
+
(0, preact.render)(vnode, region);
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
exports.renderVNode = renderVNode;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { t as dataSuffix } from "./convention-X3zLTlJ8.mjs";
|
|
2
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import pLimit from "p-limit";
|
|
5
|
+
//#region src/plugins/data/writer.ts
|
|
6
|
+
/**
|
|
7
|
+
* @file data plugin — Node write side of the agnostic provider (`write()`).
|
|
8
|
+
*
|
|
9
|
+
* This is the ONLY module in the `data` plugin that touches `node:*`. It is
|
|
10
|
+
* reached exclusively through a lazy `await import("./writer")` inside `api.ts`'s
|
|
11
|
+
* `write()`, so a browser bundle that composes `data` for the read side never
|
|
12
|
+
* statically pulls `node:fs` (see `__tests__/unit/isolation.test.ts`).
|
|
13
|
+
*
|
|
14
|
+
* It is domain-agnostic: it persists whatever `data` each {@link DataEntry} carries
|
|
15
|
+
* (the route's own `load`/projection output — `build` produced these), one JSON
|
|
16
|
+
* file per page, at the path {@link dataSuffix} mirrors from the page URL. No
|
|
17
|
+
* content/article knowledge, no expansion (build already expanded the routes).
|
|
18
|
+
*/
|
|
19
|
+
/** Default build output root, matching `build`'s `defaultBuildConfig.outDir`. */
|
|
20
|
+
const DEFAULT_OUT_DIR = "./dist";
|
|
21
|
+
/** Concurrency bound for per-page writes (matches the OG-image phase's pool). */
|
|
22
|
+
const WRITE_CONCURRENCY = 8;
|
|
23
|
+
/**
|
|
24
|
+
* Resolve the `outDir`-relative file for a page path using the shared convention,
|
|
25
|
+
* trimming a trailing slash from the config dir so the join stays clean.
|
|
26
|
+
*
|
|
27
|
+
* @param outputDir - The configured data output subdir (e.g. `"_data"`).
|
|
28
|
+
* @param pagePath - The page URL path (e.g. `/en/hello/`).
|
|
29
|
+
* @returns The `outDir`-relative file path (e.g. `_data/en/hello/index.json`).
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* relativeFile("_data", "/en/hello/"); // "_data/en/hello/index.json"
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
function relativeFile(outputDir, pagePath) {
|
|
36
|
+
return `${outputDir.endsWith("/") ? outputDir.slice(0, -1) : outputDir}/${dataSuffix(pagePath)}`;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Persist one entry's data as JSON under `<outDir>/<relativeFile>` and return the
|
|
40
|
+
* written `{ relative, bytes }`.
|
|
41
|
+
*
|
|
42
|
+
* @param entry - The page entry to persist.
|
|
43
|
+
* @param outDir - The build output root.
|
|
44
|
+
* @param outputDir - The configured data output subdir.
|
|
45
|
+
* @returns The written file's `outDir`-relative path and byte length.
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* await writeEntry({ path: "/en/hello/", data }, "./dist", "_data");
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
async function writeEntry(entry, outDir, outputDir) {
|
|
52
|
+
const relative = relativeFile(outputDir, entry.path);
|
|
53
|
+
const filePath = path.join(outDir, relative);
|
|
54
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
55
|
+
const body = JSON.stringify(entry.data);
|
|
56
|
+
await writeFile(filePath, body, "utf8");
|
|
57
|
+
return {
|
|
58
|
+
relative,
|
|
59
|
+
bytes: Buffer.byteLength(body, "utf8")
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The Node write side of the provider. Persists one JSON file per entry (bounded
|
|
64
|
+
* by `p-limit`) — domain-agnostic, no route expansion (`build` already did that).
|
|
65
|
+
* Records the summary in `ctx.state.lastWrite`.
|
|
66
|
+
*
|
|
67
|
+
* @param ctx - The data plugin context (state, config).
|
|
68
|
+
* @param entries - The per-page data to persist.
|
|
69
|
+
* @param options - Optional overrides.
|
|
70
|
+
* @param options.outDir - Build output directory to write under (default `./dist`).
|
|
71
|
+
* @returns A summary of the written files.
|
|
72
|
+
* @example
|
|
73
|
+
* ```ts
|
|
74
|
+
* const summary = await writeData(ctx, [{ path: "/en/hello/", data }], { outDir: "./dist" });
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
async function writeData(ctx, entries, options) {
|
|
78
|
+
const outDir = options?.outDir ?? DEFAULT_OUT_DIR;
|
|
79
|
+
const limit = pLimit(WRITE_CONCURRENCY);
|
|
80
|
+
const written = await Promise.all(entries.map((entry) => limit(() => writeEntry(entry, outDir, ctx.config.outputDir))));
|
|
81
|
+
const summary = {
|
|
82
|
+
fileCount: written.length,
|
|
83
|
+
bytes: written.reduce((total, file) => total + file.bytes, 0),
|
|
84
|
+
files: written.map((file) => file.relative)
|
|
85
|
+
};
|
|
86
|
+
ctx.state.lastWrite = summary;
|
|
87
|
+
return summary;
|
|
88
|
+
}
|
|
89
|
+
//#endregion
|
|
90
|
+
export { writeData };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
const require_convention = require("./convention-Dr8jxG70.cjs");
|
|
2
|
+
let node_fs_promises = require("node:fs/promises");
|
|
3
|
+
let node_path = require("node:path");
|
|
4
|
+
node_path = require_convention.__toESM(node_path, 1);
|
|
5
|
+
let p_limit = require("p-limit");
|
|
6
|
+
p_limit = require_convention.__toESM(p_limit, 1);
|
|
7
|
+
//#region src/plugins/data/writer.ts
|
|
8
|
+
/**
|
|
9
|
+
* @file data plugin — Node write side of the agnostic provider (`write()`).
|
|
10
|
+
*
|
|
11
|
+
* This is the ONLY module in the `data` plugin that touches `node:*`. It is
|
|
12
|
+
* reached exclusively through a lazy `await import("./writer")` inside `api.ts`'s
|
|
13
|
+
* `write()`, so a browser bundle that composes `data` for the read side never
|
|
14
|
+
* statically pulls `node:fs` (see `__tests__/unit/isolation.test.ts`).
|
|
15
|
+
*
|
|
16
|
+
* It is domain-agnostic: it persists whatever `data` each {@link DataEntry} carries
|
|
17
|
+
* (the route's own `load`/projection output — `build` produced these), one JSON
|
|
18
|
+
* file per page, at the path {@link dataSuffix} mirrors from the page URL. No
|
|
19
|
+
* content/article knowledge, no expansion (build already expanded the routes).
|
|
20
|
+
*/
|
|
21
|
+
/** Default build output root, matching `build`'s `defaultBuildConfig.outDir`. */
|
|
22
|
+
const DEFAULT_OUT_DIR = "./dist";
|
|
23
|
+
/** Concurrency bound for per-page writes (matches the OG-image phase's pool). */
|
|
24
|
+
const WRITE_CONCURRENCY = 8;
|
|
25
|
+
/**
|
|
26
|
+
* Resolve the `outDir`-relative file for a page path using the shared convention,
|
|
27
|
+
* trimming a trailing slash from the config dir so the join stays clean.
|
|
28
|
+
*
|
|
29
|
+
* @param outputDir - The configured data output subdir (e.g. `"_data"`).
|
|
30
|
+
* @param pagePath - The page URL path (e.g. `/en/hello/`).
|
|
31
|
+
* @returns The `outDir`-relative file path (e.g. `_data/en/hello/index.json`).
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* relativeFile("_data", "/en/hello/"); // "_data/en/hello/index.json"
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
function relativeFile(outputDir, pagePath) {
|
|
38
|
+
return `${outputDir.endsWith("/") ? outputDir.slice(0, -1) : outputDir}/${require_convention.dataSuffix(pagePath)}`;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Persist one entry's data as JSON under `<outDir>/<relativeFile>` and return the
|
|
42
|
+
* written `{ relative, bytes }`.
|
|
43
|
+
*
|
|
44
|
+
* @param entry - The page entry to persist.
|
|
45
|
+
* @param outDir - The build output root.
|
|
46
|
+
* @param outputDir - The configured data output subdir.
|
|
47
|
+
* @returns The written file's `outDir`-relative path and byte length.
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* await writeEntry({ path: "/en/hello/", data }, "./dist", "_data");
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
async function writeEntry(entry, outDir, outputDir) {
|
|
54
|
+
const relative = relativeFile(outputDir, entry.path);
|
|
55
|
+
const filePath = node_path.default.join(outDir, relative);
|
|
56
|
+
await (0, node_fs_promises.mkdir)(node_path.default.dirname(filePath), { recursive: true });
|
|
57
|
+
const body = JSON.stringify(entry.data);
|
|
58
|
+
await (0, node_fs_promises.writeFile)(filePath, body, "utf8");
|
|
59
|
+
return {
|
|
60
|
+
relative,
|
|
61
|
+
bytes: Buffer.byteLength(body, "utf8")
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The Node write side of the provider. Persists one JSON file per entry (bounded
|
|
66
|
+
* by `p-limit`) — domain-agnostic, no route expansion (`build` already did that).
|
|
67
|
+
* Records the summary in `ctx.state.lastWrite`.
|
|
68
|
+
*
|
|
69
|
+
* @param ctx - The data plugin context (state, config).
|
|
70
|
+
* @param entries - The per-page data to persist.
|
|
71
|
+
* @param options - Optional overrides.
|
|
72
|
+
* @param options.outDir - Build output directory to write under (default `./dist`).
|
|
73
|
+
* @returns A summary of the written files.
|
|
74
|
+
* @example
|
|
75
|
+
* ```ts
|
|
76
|
+
* const summary = await writeData(ctx, [{ path: "/en/hello/", data }], { outDir: "./dist" });
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
async function writeData(ctx, entries, options) {
|
|
80
|
+
const outDir = options?.outDir ?? DEFAULT_OUT_DIR;
|
|
81
|
+
const limit = (0, p_limit.default)(WRITE_CONCURRENCY);
|
|
82
|
+
const written = await Promise.all(entries.map((entry) => limit(() => writeEntry(entry, outDir, ctx.config.outputDir))));
|
|
83
|
+
const summary = {
|
|
84
|
+
fileCount: written.length,
|
|
85
|
+
bytes: written.reduce((total, file) => total + file.bytes, 0),
|
|
86
|
+
files: written.map((file) => file.relative)
|
|
87
|
+
};
|
|
88
|
+
ctx.state.lastWrite = summary;
|
|
89
|
+
return summary;
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|
|
92
|
+
exports.writeData = writeData;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moku-labs/web",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Content static-site generator + SPA web framework for TypeScript, built on @moku-labs/core.",
|
|
5
5
|
"author": "Oleksandr Kucherenko",
|
|
6
6
|
"license": "MIT",
|
|
@@ -42,13 +42,14 @@
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
},
|
|
45
|
+
"sideEffects": false,
|
|
45
46
|
"files": [
|
|
46
47
|
"dist",
|
|
47
48
|
"LICENSE",
|
|
48
49
|
"README.md"
|
|
49
50
|
],
|
|
50
51
|
"engines": {
|
|
51
|
-
"node": ">=
|
|
52
|
+
"node": ">=24.0.0",
|
|
52
53
|
"bun": ">=1.3.14"
|
|
53
54
|
},
|
|
54
55
|
"dependencies": {
|