@takazudo/zfb-adapter-cloudflare 0.1.0-next.7 → 0.1.0-next.70

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/bin/cli.mjs CHANGED
@@ -15,9 +15,8 @@
15
15
  // no TypeScript loader required) so there is a single source of truth.
16
16
  // invariant: no runtime npm deps — see SECURITY-DEPS.md
17
17
 
18
- import { copyFile, mkdir, writeFile } from "node:fs/promises";
19
18
  import { realpathSync } from "node:fs";
20
- import { join, resolve } from "node:path";
19
+ import { resolve } from "node:path";
21
20
  import { fileURLToPath } from "node:url";
22
21
 
23
22
  // ---------------------------------------------------------------------------
@@ -27,18 +26,14 @@ import { fileURLToPath } from "node:url";
27
26
  import { WORKER_WRAPPER_SOURCE } from "../src/worker-wrapper.mjs";
28
27
  export { WORKER_WRAPPER_SOURCE };
29
28
 
30
- export async function emitWorker({ inputBundlePath, outdir }) {
31
- const outdirAbs = resolve(outdir);
32
- const inputAbs = resolve(inputBundlePath);
33
-
34
- await mkdir(outdirAbs, { recursive: true });
35
- const innerBundlePath = join(outdirAbs, "_zfb_inner.mjs");
36
- await copyFile(inputAbs, innerBundlePath);
29
+ // ---------------------------------------------------------------------------
30
+ // emitWorker — shared implementation, no runtime npm deps.
31
+ // ---------------------------------------------------------------------------
37
32
 
38
- const workerPath = join(outdirAbs, "_worker.js");
39
- await writeFile(workerPath, WORKER_WRAPPER_SOURCE, "utf8");
33
+ import { emitWorker as _emitWorker } from "../src/emit-worker.mjs";
40
34
 
41
- return { workerPath, innerBundlePath };
35
+ export async function emitWorker({ inputBundlePath, outdir }) {
36
+ return _emitWorker({ inputBundlePath, outdir, workerWrapperSource: WORKER_WRAPPER_SOURCE });
42
37
  }
43
38
 
44
39
  // ---------------------------------------------------------------------------
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAcA;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,EAAE,MAA2B,CAAC;AAEhE;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;;OAMG;IACH,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAelF"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAiBA;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,EAAE,MAA2B,CAAC;AAEhE;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;;OAMG;IACH,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAMlF"}
package/dist/build.js CHANGED
@@ -10,6 +10,9 @@
10
10
  // @ts-expect-error worker-wrapper.mjs has no declaration file; the export
11
11
  // shape is narrowed explicitly below.
12
12
  import { WORKER_WRAPPER_SOURCE as _wrapper } from "./worker-wrapper.mjs";
13
+ // @ts-expect-error emit-worker.mjs has no declaration file; the export
14
+ // shape is narrowed via EmitWorkerInput/EmitWorkerOutput below.
15
+ import { emitWorker as _emitWorker } from "./emit-worker.mjs";
13
16
  /**
14
17
  * The wrapper source written to `_worker.js`. Imported from the single
15
18
  * canonical `.mjs` file so `src/build.ts` and `bin/cli.mjs` always stay
@@ -35,15 +38,10 @@ export const WORKER_WRAPPER_SOURCE = _wrapper;
35
38
  * runtime — it is just `node:fs` glue.
36
39
  */
37
40
  export async function emitWorker(input) {
38
- const { mkdir, copyFile, writeFile } = await import("node:fs/promises");
39
- const { resolve, join } = await import("node:path");
40
- const outdir = resolve(input.outdir);
41
- const inputBundle = resolve(input.inputBundlePath);
42
- await mkdir(outdir, { recursive: true });
43
- const innerBundlePath = join(outdir, "_zfb_inner.mjs");
44
- await copyFile(inputBundle, innerBundlePath);
45
- const workerPath = join(outdir, "_worker.js");
46
- await writeFile(workerPath, WORKER_WRAPPER_SOURCE, "utf8");
47
- return { workerPath, innerBundlePath };
41
+ return _emitWorker({
42
+ inputBundlePath: input.inputBundlePath,
43
+ outdir: input.outdir,
44
+ workerWrapperSource: WORKER_WRAPPER_SOURCE,
45
+ });
48
46
  }
49
47
  //# sourceMappingURL=build.js.map
package/dist/build.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"build.js","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,EAAE;AACF,0EAA0E;AAC1E,kEAAkE;AAClE,sEAAsE;AACtE,+DAA+D;AAC/D,EAAE;AACF,yEAAyE;AACzE,mEAAmE;AAEnE,0EAA0E;AAC1E,sCAAsC;AACtC,OAAO,EAAE,qBAAqB,IAAI,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAEzE;;;;GAIG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAW,QAAkB,CAAC;AA+BhE;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,KAAsB;IACrD,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC;IACxE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC;IAEpD,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACrC,MAAM,WAAW,GAAG,OAAO,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;IAEnD,MAAM,KAAK,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACzC,MAAM,eAAe,GAAG,IAAI,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IACvD,MAAM,QAAQ,CAAC,WAAW,EAAE,eAAe,CAAC,CAAC;IAE7C,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAC9C,MAAM,SAAS,CAAC,UAAU,EAAE,qBAAqB,EAAE,MAAM,CAAC,CAAC;IAE3D,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,CAAC;AACzC,CAAC"}
1
+ {"version":3,"file":"build.js","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,EAAE;AACF,0EAA0E;AAC1E,kEAAkE;AAClE,sEAAsE;AACtE,+DAA+D;AAC/D,EAAE;AACF,yEAAyE;AACzE,mEAAmE;AAEnE,0EAA0E;AAC1E,sCAAsC;AACtC,OAAO,EAAE,qBAAqB,IAAI,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACzE,uEAAuE;AACvE,gEAAgE;AAChE,OAAO,EAAE,UAAU,IAAI,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE9D;;;;GAIG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAW,QAAkB,CAAC;AA+BhE;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,KAAsB;IACrD,OAAO,WAAW,CAAC;QACjB,eAAe,EAAE,KAAK,CAAC,eAAe;QACtC,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,mBAAmB,EAAE,qBAAqB;KAC3C,CAA8B,CAAC;AAClC,CAAC"}
@@ -0,0 +1,38 @@
1
+ // Shared, dependency-free implementation of `emitWorker`.
2
+ //
3
+ // Kept as a plain `.mjs` module (no TypeScript) so it can be imported from
4
+ // both the typed TypeScript surface (`src/build.ts`) and the dependency-free
5
+ // CLI binary (`bin/cli.mjs`) without needing a TypeScript loader.
6
+ //
7
+ // Do NOT duplicate this logic elsewhere. The two consumers always stay in
8
+ // sync by importing from here.
9
+ //
10
+ // invariant: no runtime npm deps — see SECURITY-DEPS.md
11
+
12
+ import { copyFile, mkdir, writeFile } from "node:fs/promises";
13
+ import { join, resolve } from "node:path";
14
+
15
+ /**
16
+ * Emit a Cloudflare Pages `_worker.js` that wraps the zfb input bundle.
17
+ *
18
+ * Output shape (two files in `outdir`):
19
+ *
20
+ * _worker.js — entry imported by Cloudflare Pages advanced mode
21
+ * _zfb_inner.mjs — the input bundle, copied verbatim
22
+ *
23
+ * @param {{ inputBundlePath: string; outdir: string; workerWrapperSource: string }} input
24
+ * @returns {Promise<{ workerPath: string; innerBundlePath: string }>}
25
+ */
26
+ export async function emitWorker({ inputBundlePath, outdir, workerWrapperSource }) {
27
+ const outdirAbs = resolve(outdir);
28
+ const inputAbs = resolve(inputBundlePath);
29
+
30
+ await mkdir(outdirAbs, { recursive: true });
31
+ const innerBundlePath = join(outdirAbs, "_zfb_inner.mjs");
32
+ await copyFile(inputAbs, innerBundlePath);
33
+
34
+ const workerPath = join(outdirAbs, "_worker.js");
35
+ await writeFile(workerPath, workerWrapperSource, "utf8");
36
+
37
+ return { workerPath, innerBundlePath };
38
+ }
@@ -31,8 +31,7 @@ export const WORKER_WRAPPER_SOURCE = `// AUTO-GENERATED by @takazudo/zfb-adapter
31
31
  // islands-…">). If we let the inner Hono router handle them first,
32
32
  // it would dynamic-SSR the page WITHOUT the prod head injection
33
33
  // (which is a build-time post-process, not a runtime concern), and
34
- // islands would never hydrate. See zfb#XXX (filed by Wave 10 of
35
- // the zudo-doc#1355 zfb-pipeline-gaps epic).
34
+ // islands would never hydrate.
36
35
  // - On 404 from ASSETS, fall through to the inner zfb worker. This is
37
36
  // where genuinely dynamic routes (\`prerender = false\`, e.g.
38
37
  // \`pages/api/*.tsx\`) are served.
@@ -68,6 +67,16 @@ function isAssetProbeMethod(method) {
68
67
  export default {
69
68
  async fetch(request, env, ctx) {
70
69
  if (isAssetProbeMethod(request.method) && canDelegateToAssets(env)) {
70
+ // Trade-off: every GET/HEAD request that matches a prerendered route pays
71
+ // the cost of one env.ASSETS.fetch() round-trip before the inner worker
72
+ // sees it. The upside is that CF Pages' asset server handles trailing-
73
+ // slash canonicalisation (e.g. /docs/foo → 308 → /docs/foo/) and serves
74
+ // the build-time head-injected HTML with the hashed <link>/<script> tags.
75
+ // If we skipped this probe, prerendered routes would be dynamic-SSR'd by
76
+ // the inner Hono router without the prod head injection, and islands would
77
+ // never hydrate. For purely dynamic apps (prerender=false everywhere) the
78
+ // extra round-trip is pure overhead; splitting the wrapper into two
79
+ // variants is the accepted future escape hatch for that case.
71
80
  const assetResponse = await env.ASSETS.fetch(request);
72
81
  if (assetResponse.status !== 404) {
73
82
  return assetResponse;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takazudo/zfb-adapter-cloudflare",
3
- "version": "0.1.0-next.7",
3
+ "version": "0.1.0-next.70",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Rust-built static-site engine for Astro and Next.js users — millisecond rebuilds, single binary. Cloudflare Pages adapter.",
@@ -44,6 +44,7 @@
44
44
  "dist",
45
45
  "bin",
46
46
  "src/worker-wrapper.mjs",
47
+ "src/emit-worker.mjs",
47
48
  "README.md",
48
49
  "CHANGELOG.md",
49
50
  "LICENSE"
@@ -61,7 +62,7 @@
61
62
  "vitest": "^2.1.9"
62
63
  },
63
64
  "scripts": {
64
- "build": "tsc && cp src/worker-wrapper.mjs dist/worker-wrapper.mjs",
65
+ "build": "tsc && cp src/worker-wrapper.mjs dist/worker-wrapper.mjs && cp src/emit-worker.mjs dist/emit-worker.mjs",
65
66
  "test": "vitest run",
66
67
  "test:watch": "vitest",
67
68
  "typecheck": "tsc --noEmit"
@@ -0,0 +1,38 @@
1
+ // Shared, dependency-free implementation of `emitWorker`.
2
+ //
3
+ // Kept as a plain `.mjs` module (no TypeScript) so it can be imported from
4
+ // both the typed TypeScript surface (`src/build.ts`) and the dependency-free
5
+ // CLI binary (`bin/cli.mjs`) without needing a TypeScript loader.
6
+ //
7
+ // Do NOT duplicate this logic elsewhere. The two consumers always stay in
8
+ // sync by importing from here.
9
+ //
10
+ // invariant: no runtime npm deps — see SECURITY-DEPS.md
11
+
12
+ import { copyFile, mkdir, writeFile } from "node:fs/promises";
13
+ import { join, resolve } from "node:path";
14
+
15
+ /**
16
+ * Emit a Cloudflare Pages `_worker.js` that wraps the zfb input bundle.
17
+ *
18
+ * Output shape (two files in `outdir`):
19
+ *
20
+ * _worker.js — entry imported by Cloudflare Pages advanced mode
21
+ * _zfb_inner.mjs — the input bundle, copied verbatim
22
+ *
23
+ * @param {{ inputBundlePath: string; outdir: string; workerWrapperSource: string }} input
24
+ * @returns {Promise<{ workerPath: string; innerBundlePath: string }>}
25
+ */
26
+ export async function emitWorker({ inputBundlePath, outdir, workerWrapperSource }) {
27
+ const outdirAbs = resolve(outdir);
28
+ const inputAbs = resolve(inputBundlePath);
29
+
30
+ await mkdir(outdirAbs, { recursive: true });
31
+ const innerBundlePath = join(outdirAbs, "_zfb_inner.mjs");
32
+ await copyFile(inputAbs, innerBundlePath);
33
+
34
+ const workerPath = join(outdirAbs, "_worker.js");
35
+ await writeFile(workerPath, workerWrapperSource, "utf8");
36
+
37
+ return { workerPath, innerBundlePath };
38
+ }
@@ -31,8 +31,7 @@ export const WORKER_WRAPPER_SOURCE = `// AUTO-GENERATED by @takazudo/zfb-adapter
31
31
  // islands-…">). If we let the inner Hono router handle them first,
32
32
  // it would dynamic-SSR the page WITHOUT the prod head injection
33
33
  // (which is a build-time post-process, not a runtime concern), and
34
- // islands would never hydrate. See zfb#XXX (filed by Wave 10 of
35
- // the zudo-doc#1355 zfb-pipeline-gaps epic).
34
+ // islands would never hydrate.
36
35
  // - On 404 from ASSETS, fall through to the inner zfb worker. This is
37
36
  // where genuinely dynamic routes (\`prerender = false\`, e.g.
38
37
  // \`pages/api/*.tsx\`) are served.
@@ -68,6 +67,16 @@ function isAssetProbeMethod(method) {
68
67
  export default {
69
68
  async fetch(request, env, ctx) {
70
69
  if (isAssetProbeMethod(request.method) && canDelegateToAssets(env)) {
70
+ // Trade-off: every GET/HEAD request that matches a prerendered route pays
71
+ // the cost of one env.ASSETS.fetch() round-trip before the inner worker
72
+ // sees it. The upside is that CF Pages' asset server handles trailing-
73
+ // slash canonicalisation (e.g. /docs/foo → 308 → /docs/foo/) and serves
74
+ // the build-time head-injected HTML with the hashed <link>/<script> tags.
75
+ // If we skipped this probe, prerendered routes would be dynamic-SSR'd by
76
+ // the inner Hono router without the prod head injection, and islands would
77
+ // never hydrate. For purely dynamic apps (prerender=false everywhere) the
78
+ // extra round-trip is pure overhead; splitting the wrapper into two
79
+ // variants is the accepted future escape hatch for that case.
71
80
  const assetResponse = await env.ASSETS.fetch(request);
72
81
  if (assetResponse.status !== 404) {
73
82
  return assetResponse;