@torpor/adapter-cloudflare 0.2.3 → 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 +15 -3
- package/dist/_worker.dev.ts +35 -0
- package/dist/_worker.ts +4 -1
- package/dist/index.d.mts +0 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +81 -13
- package/dist/index.mjs.map +1 -1
- package/package.json +14 -10
package/README.md
CHANGED
|
@@ -2,8 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
A @torpor/build adapter for deploying to Cloudflare Pages.
|
|
4
4
|
|
|
5
|
-
🚧 WARNING: WORK IN PROGRESS 🚧
|
|
6
|
-
|
|
7
5
|
## Installation
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
Install the adapter using `npm` (or your preferred package manager):
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @torpor/adapter-cloudflare
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Then use the adapter in your `site.config`:
|
|
14
|
+
|
|
15
|
+
```javascript
|
|
16
|
+
import { cloudflare } from "@torpor/adapter-cloudflare";
|
|
17
|
+
import { Site } from "@torpor/build";
|
|
18
|
+
|
|
19
|
+
const site: Site = new Site();
|
|
20
|
+
site.adapter = cloudflare;
|
|
21
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// The dev counterpart of the production `_worker.ts`. This file is loaded by
|
|
2
|
+
// `@cloudflare/vite-plugin` into workerd (via the Vite module runner, so HMR
|
|
3
|
+
// works) and is pointed at by the `main` override in the adapter's dev plugin.
|
|
4
|
+
//
|
|
5
|
+
// Imports here resolve from the consuming site's `node_modules` (just like the
|
|
6
|
+
// Node-runtime dev plugin's `vite.ssrLoadModule` paths), and the template is
|
|
7
|
+
// provided by the `virtual:torpor-template` module exposed by the adapter's
|
|
8
|
+
// companion Vite plugin (which applies `transformIndexHtml` Node-side).
|
|
9
|
+
|
|
10
|
+
import { Server, load } from "@torpor/build/dev";
|
|
11
|
+
// @ts-ignore
|
|
12
|
+
import template from "virtual:torpor-template";
|
|
13
|
+
|
|
14
|
+
// Send all requests through Server to handle middleware, cookies, headers, etc
|
|
15
|
+
const server = new Server();
|
|
16
|
+
server.add("*", async (ev: any) => {
|
|
17
|
+
try {
|
|
18
|
+
return await load(ev, template);
|
|
19
|
+
} catch (e: any) {
|
|
20
|
+
console.log("ERROR:", e);
|
|
21
|
+
return new Response(null, {
|
|
22
|
+
status: 500,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export default {
|
|
28
|
+
async fetch(request: Request, env: any): Promise<Response> {
|
|
29
|
+
// Put adapter-specific functionality in the `adapter` property of
|
|
30
|
+
// globalThis for now, matching the production _worker.ts
|
|
31
|
+
// @ts-ignore
|
|
32
|
+
globalThis.adapter = { env };
|
|
33
|
+
return await server.fetch(request);
|
|
34
|
+
},
|
|
35
|
+
};
|
package/dist/_worker.ts
CHANGED
|
@@ -6,7 +6,10 @@ import { load } from "%SERVER_SCRIPT%";
|
|
|
6
6
|
// This file has the above imports replaced and is then compiled to create a
|
|
7
7
|
// Cloudflare Pages worker file
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
// The backticks around %HTML_TEMPLATE% keep this file parseable; postbuild
|
|
10
|
+
// replaces the whole literal with a JSON string, or `undefined` for
|
|
11
|
+
// endpoints-only sites with no site.html
|
|
12
|
+
const template: string | undefined = `%HTML_TEMPLATE%`;
|
|
10
13
|
|
|
11
14
|
// Send all requests through Server to handle middleware, cookies, headers, etc
|
|
12
15
|
const server = new Server();
|
package/dist/index.d.mts
CHANGED
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/adapter.ts"],"
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/adapter.ts"],"mappings":";;cASA,UAwBuB"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { existsSync, promises } from "node:fs";
|
|
2
4
|
import path from "node:path";
|
|
3
5
|
import { build, defineConfig } from "vite";
|
|
4
|
-
|
|
6
|
+
import { cloudflare } from "@cloudflare/vite-plugin";
|
|
5
7
|
//#region src/prepareTemplate.ts
|
|
6
8
|
function prepareTemplate(template, clientScript) {
|
|
7
9
|
let result = template;
|
|
@@ -20,13 +22,77 @@ function regexIndexOf(string, regex, position) {
|
|
|
20
22
|
var indexOf = string.substring(position || 0).search(regex);
|
|
21
23
|
return indexOf >= 0 ? indexOf + (position || 0) : indexOf;
|
|
22
24
|
}
|
|
23
|
-
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/cloudflareDev.ts
|
|
27
|
+
const TEMPLATE_ID = "virtual:torpor-template";
|
|
28
|
+
const RESOLVED_TEMPLATE_ID = `\0${TEMPLATE_ID}`;
|
|
29
|
+
/**
|
|
30
|
+
* Vite plugin(s) for Cloudflare dev. Delegates the workerd runtime + bindings +
|
|
31
|
+
* HMR to `@cloudflare/vite-plugin` (assigning the Worker to Vite's `ssr`
|
|
32
|
+
* environment, so torpor's server code runs in workerd), overriding `main` to
|
|
33
|
+
* point at the adapter's dev worker, and exposing the prepared `site.html`
|
|
34
|
+
* template through a virtual module (since `transformIndexHtml` has to run
|
|
35
|
+
* Node-side but the template is consumed inside workerd).
|
|
36
|
+
*/
|
|
37
|
+
function cloudflareDev(site) {
|
|
38
|
+
let viteServer;
|
|
39
|
+
let templatePromise;
|
|
40
|
+
return [{
|
|
41
|
+
name: "torpor-cloudflare-template",
|
|
42
|
+
configureServer(server) {
|
|
43
|
+
viteServer = server;
|
|
44
|
+
},
|
|
45
|
+
resolveId(id) {
|
|
46
|
+
if (id === TEMPLATE_ID) return RESOLVED_TEMPLATE_ID;
|
|
47
|
+
},
|
|
48
|
+
async load(id) {
|
|
49
|
+
if (id !== RESOLVED_TEMPLATE_ID) return;
|
|
50
|
+
templatePromise ??= buildDevTemplate(viteServer, site);
|
|
51
|
+
return `export default ${JSON.stringify(await templatePromise)};`;
|
|
52
|
+
}
|
|
53
|
+
}, ...cloudflare({
|
|
54
|
+
viteEnvironment: { name: "ssr" },
|
|
55
|
+
config: { main: "./node_modules/@torpor/adapter-cloudflare/dist/_worker.dev.ts" }
|
|
56
|
+
})];
|
|
57
|
+
}
|
|
58
|
+
async function buildDevTemplate(vite, site) {
|
|
59
|
+
const templateFile = path.resolve(site.root, "src/site.html");
|
|
60
|
+
if (!existsSync(templateFile)) return;
|
|
61
|
+
let template = await promises.readFile(templateFile, "utf-8");
|
|
62
|
+
template = await vite.transformIndexHtml("", template);
|
|
63
|
+
const siteFolder = path.resolve(site.root, "./node_modules/@torpor/build/src/site/");
|
|
64
|
+
const clientScript = path.join(siteFolder, "clientEntry.ts");
|
|
65
|
+
const clientDevScript = path.join(siteFolder, "clientEntryDev.ts");
|
|
66
|
+
template = prepareTemplate(template, clientScript);
|
|
67
|
+
const clientTag = `<script type="module" src="${clientScript}"><\/script>`;
|
|
68
|
+
template = template.replace(clientTag, `<script type="module" src="${clientDevScript}"><\/script>\n${clientTag}`);
|
|
69
|
+
return template;
|
|
70
|
+
}
|
|
24
71
|
//#endregion
|
|
25
72
|
//#region src/adapter.ts
|
|
26
73
|
var adapter_default = {
|
|
27
74
|
postbuild,
|
|
28
|
-
|
|
29
|
-
|
|
75
|
+
dev: (site) => cloudflareDev(site),
|
|
76
|
+
serve: (_server, site) => {
|
|
77
|
+
const args = [
|
|
78
|
+
"dev",
|
|
79
|
+
"--config",
|
|
80
|
+
path.join(site.root, "dist", "wrangler.toml")
|
|
81
|
+
];
|
|
82
|
+
let child;
|
|
83
|
+
try {
|
|
84
|
+
const wranglerBin = createRequire(path.join(site.root, "package.json")).resolve("wrangler/bin/wrangler.js");
|
|
85
|
+
child = spawn(process.execPath, [wranglerBin, ...args], {
|
|
86
|
+
stdio: "inherit",
|
|
87
|
+
cwd: site.root
|
|
88
|
+
});
|
|
89
|
+
} catch {
|
|
90
|
+
child = spawn("wrangler", args, {
|
|
91
|
+
stdio: "inherit",
|
|
92
|
+
cwd: site.root
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
|
30
96
|
}
|
|
31
97
|
};
|
|
32
98
|
async function postbuild(site) {
|
|
@@ -39,15 +105,17 @@ async function postbuild(site) {
|
|
|
39
105
|
const adapterDistFolder = path.resolve(site.root, "./node_modules/@torpor/adapter-cloudflare/dist/");
|
|
40
106
|
let workerFile = path.join(adapterDistFolder, "_worker.ts");
|
|
41
107
|
let workerSource = await promises.readFile(workerFile, "utf-8");
|
|
42
|
-
let
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
108
|
+
let template;
|
|
109
|
+
const siteHtml = path.join(clientFolder, "site.html");
|
|
110
|
+
if (existsSync(siteHtml)) {
|
|
111
|
+
let clientScript = (await promises.readdir(path.join(clientFolder, "assets"))).find((f) => f.startsWith("clientEntry-"));
|
|
112
|
+
if (!clientScript) throw new Error("clientEntry.js not found");
|
|
113
|
+
clientScript = `/assets/${clientScript}`;
|
|
114
|
+
template = prepareTemplate(await promises.readFile(siteHtml, "utf-8"), clientScript);
|
|
115
|
+
}
|
|
48
116
|
const serverClass = path.join(buildSrcFolder, "server", "Server.ts");
|
|
49
117
|
const serverScript = path.join(serverFolder, "serverEntry.js");
|
|
50
|
-
workerSource = workerSource.replace("%SERVER_CLASS%", serverClass).replace("%SERVER_SCRIPT%", serverScript).replace("
|
|
118
|
+
workerSource = workerSource.replace("%SERVER_CLASS%", serverClass).replace("%SERVER_SCRIPT%", serverScript).replace("`%HTML_TEMPLATE%`", () => template ? JSON.stringify(template) : "undefined");
|
|
51
119
|
workerFile = path.join(tempFolder, "_worker.ts");
|
|
52
120
|
await promises.mkdir(tempFolder);
|
|
53
121
|
await promises.writeFile(workerFile, workerSource);
|
|
@@ -88,7 +156,7 @@ _worker.js
|
|
|
88
156
|
await promises.writeFile(assetsIgnoreFile, assetsIgnore);
|
|
89
157
|
await promises.rm(tempFolder, { recursive: true });
|
|
90
158
|
}
|
|
91
|
-
|
|
92
159
|
//#endregion
|
|
93
160
|
export { adapter_default as cloudflare };
|
|
161
|
+
|
|
94
162
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["fs"],"sources":["../src/prepareTemplate.ts","../src/adapter.ts"],"sourcesContent":["export default function prepareTemplate(template: string, clientScript: string): string {\n\tlet result = template;\n\n\t// Put %COMPONENT_HEAD% before </head>\n\t// This is where styles will go\n\tlet headStart = result.indexOf(\"</head>\");\n\tif (headStart === -1) {\n\t\tthrow new Error(`Couldn't find <head> end tag`);\n\t}\n\tresult = result.substring(0, headStart) + \"%COMPONENT_HEAD%\" + result.substring(headStart);\n\n\t// Put %COMPONENT_BODY% inside <div id=\"app\"></div>\n\t// This is where the component's HTML will go\n\tlet bodyStart = regexIndexOf(result, /<div\\s+id=(\"app\"|'app'|app)\\s+/);\n\tbodyStart = result.indexOf(\">\", bodyStart) + 1;\n\tlet bodyEnd = result.indexOf(\"</div>\", bodyStart);\n\tif (bodyStart === -1 || bodyEnd === -1) {\n\t\tthrow new Error(`Couldn't find <div id=\"app\"></div>`);\n\t}\n\tresult = result.substring(0, bodyStart) + \"%COMPONENT_BODY%\" + result.substring(bodyEnd);\n\n\t// Add the clientEntry script at the very end, for hydrating and navigating\n\tresult += `<script type=\"module\" src=\"${clientScript}\"></script>`;\n\n\treturn result;\n}\n\n// From https://stackoverflow.com/a/274094\nfunction regexIndexOf(string: string, regex: RegExp, position?: number) {\n\tvar indexOf = string.substring(position || 0).search(regex);\n\treturn indexOf >= 0 ? indexOf + (position || 0) : indexOf;\n}\n","import { type Adapter, Site } from \"@torpor/build\";\nimport { promises as fs } from \"node:fs\";\nimport path from \"node:path\";\nimport { build, defineConfig } from \"vite\";\nimport prepareTemplate from \"./prepareTemplate\";\n\nexport default {\n\tpostbuild,\n\tserve: (/* server: Server, site: Site*/) => {\n\t\tconsole.log(\"TODO: Cloudflare dev server\");\n\t},\n} satisfies Adapter as Adapter;\n\nasync function postbuild(site: Site): Promise<void> {\n\t// Build _worker.js as per\n\t// https://developers.cloudflare.com/pages/functions/advanced-mode/\n\n\tconst distFolder = path.resolve(site.root, \"dist\");\n\tconst clientFolder = path.join(distFolder, \"client\");\n\tconst serverFolder = path.join(distFolder, \"server\");\n\tconst tempFolder = path.join(distFolder, \"temp\");\n\tconst cloudflareFolder = path.join(distFolder, \"cloudflare\");\n\n\tconst buildSrcFolder = path.resolve(site.root, \"./node_modules/@torpor/build/src/\");\n\tconst adapterDistFolder = path.resolve(\n\t\tsite.root,\n\t\t\"./node_modules/@torpor/adapter-cloudflare/dist/\",\n\t);\n\n\t// Compile _worker.ts\n\tlet workerFile = path.join(adapterDistFolder, \"_worker.ts\");\n\tlet workerSource = await fs.readFile(workerFile, \"utf-8\");\n\n\t// Find the client script file in /assets\n\tlet clientScript = (await fs.readdir(path.join(clientFolder, \"assets\"))).find((f) =>\n\t\tf.startsWith(\"clientEntry-\"),\n\t);\n\tif (!clientScript) {\n\t\tthrow new Error(\"clientEntry.js not found\");\n\t}\n\tclientScript = `/assets/${clientScript}`;\n\n\t// Read and prepare site.html so that we can just splice components into it\n\tlet siteHtml = path.join(clientFolder, \"site.html\");\n\tlet template = await fs.readFile(siteHtml, \"utf-8\");\n\ttemplate = prepareTemplate(template, clientScript);\n\n\tconst serverClass = path.join(buildSrcFolder, \"server\", \"Server.ts\");\n\tconst serverScript = path.join(serverFolder, \"serverEntry.js\");\n\n\t// Splice file names into _worker.ts\n\tworkerSource = workerSource\n\t\t.replace(\"%SERVER_CLASS%\", serverClass)\n\t\t.replace(\"%SERVER_SCRIPT%\", serverScript)\n\t\t.replace(\"%HTML_TEMPLATE%\", template);\n\n\tworkerFile = path.join(tempFolder, \"_worker.ts\");\n\tawait fs.mkdir(tempFolder);\n\tawait fs.writeFile(workerFile, workerSource);\n\n\tconst cloudflareConfig = structuredClone(site.viteConfig ?? {});\n\tcloudflareConfig.build ??= {};\n\tcloudflareConfig.build.outDir = cloudflareFolder;\n\tcloudflareConfig.build.rollupOptions ??= {};\n\tcloudflareConfig.build.rollupOptions.input = [workerFile, ...site.inputs];\n\tcloudflareConfig.build.rollupOptions.output = {\n\t\tentryFileNames: `[name].js`,\n\t\tchunkFileNames: `[name].js`,\n\t\tassetFileNames: `[name].[ext]`,\n\t};\n\tcloudflareConfig.build.rollupOptions.preserveEntrySignatures = \"strict\";\n\t// HACK: We don't need to minify as the worker isn't downloaded?\n\t// And it makes debugging easier\n\tcloudflareConfig.build.minify = false;\n\t// HACK: Build for SSR so we don't get document and window references\n\tcloudflareConfig.build.ssr = true;\n\tawait build(defineConfig(cloudflareConfig));\n\n\t// Copy the client assets into the Cloudflare folder\n\tawait fs.mkdir(path.join(cloudflareFolder, \"assets\"));\n\tfor (let file of await fs.readdir(path.join(clientFolder, \"assets\"))) {\n\t\tawait fs.copyFile(\n\t\t\tpath.join(clientFolder, \"assets\", file),\n\t\t\tpath.join(cloudflareFolder, \"assets\", file),\n\t\t);\n\t}\n\n\t// Build a wrangler.toml file for running with `wrangler dev`\n\tconst wranglerConfig = `\nname = \"torpor-miniflare\"\nmain = \"./cloudflare/_worker.js\"\ncompatibility_date = \"2025-01-01\"\n\n[assets]\ndirectory = \"./cloudflare\"\nbinding = \"ASSETS\"\n\t`;\n\tconst wranglerFile = path.join(distFolder, \"wrangler.toml\");\n\tawait fs.writeFile(wranglerFile, wranglerConfig);\n\n\t// Build a .assetsignore file\n\tconst assetsIgnore = `\n**/node_modules\n**/.DS_Store\n**/.git\n_worker.js\n`;\n\tconst assetsIgnoreFile = path.join(cloudflareFolder, \".assetsignore\");\n\tawait fs.writeFile(assetsIgnoreFile, assetsIgnore);\n\n\tawait fs.rm(tempFolder, { recursive: true });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["fs","fs"],"sources":["../src/prepareTemplate.ts","../src/cloudflareDev.ts","../src/adapter.ts"],"sourcesContent":["export default function prepareTemplate(template: string, clientScript: string): string {\n\tlet result = template;\n\n\t// Put %COMPONENT_HEAD% before </head>\n\t// This is where styles will go\n\tlet headStart = result.indexOf(\"</head>\");\n\tif (headStart === -1) {\n\t\tthrow new Error(`Couldn't find <head> end tag`);\n\t}\n\tresult = result.substring(0, headStart) + \"%COMPONENT_HEAD%\" + result.substring(headStart);\n\n\t// Put %COMPONENT_BODY% inside <div id=\"app\"></div>\n\t// This is where the component's HTML will go\n\tlet bodyStart = regexIndexOf(result, /<div\\s+id=(\"app\"|'app'|app)\\s+/);\n\tbodyStart = result.indexOf(\">\", bodyStart) + 1;\n\tlet bodyEnd = result.indexOf(\"</div>\", bodyStart);\n\tif (bodyStart === -1 || bodyEnd === -1) {\n\t\tthrow new Error(`Couldn't find <div id=\"app\"></div>`);\n\t}\n\tresult = result.substring(0, bodyStart) + \"%COMPONENT_BODY%\" + result.substring(bodyEnd);\n\n\t// Add the clientEntry script at the very end, for hydrating and navigating\n\tresult += `<script type=\"module\" src=\"${clientScript}\"></script>`;\n\n\treturn result;\n}\n\n// From https://stackoverflow.com/a/274094\nfunction regexIndexOf(string: string, regex: RegExp, position?: number) {\n\tvar indexOf = string.substring(position || 0).search(regex);\n\treturn indexOf >= 0 ? indexOf + (position || 0) : indexOf;\n}\n","import { cloudflare } from \"@cloudflare/vite-plugin\";\nimport { type Site } from \"@torpor/build\";\nimport { existsSync, promises as fs } from \"node:fs\";\nimport path from \"node:path\";\nimport { type Plugin, type ViteDevServer } from \"vite\";\nimport prepareTemplate from \"./prepareTemplate\";\n\nconst TEMPLATE_ID = \"virtual:torpor-template\";\nconst RESOLVED_TEMPLATE_ID = `\\0${TEMPLATE_ID}`;\n\n/**\n * Vite plugin(s) for Cloudflare dev. Delegates the workerd runtime + bindings +\n * HMR to `@cloudflare/vite-plugin` (assigning the Worker to Vite's `ssr`\n * environment, so torpor's server code runs in workerd), overriding `main` to\n * point at the adapter's dev worker, and exposing the prepared `site.html`\n * template through a virtual module (since `transformIndexHtml` has to run\n * Node-side but the template is consumed inside workerd).\n */\nexport default function cloudflareDev(site: Site): Plugin[] {\n\tlet viteServer: ViteDevServer | undefined;\n\tlet templatePromise: Promise<string | undefined> | undefined;\n\n\tconst templatePlugin: Plugin = {\n\t\tname: \"torpor-cloudflare-template\",\n\t\tconfigureServer(server) {\n\t\t\tviteServer = server;\n\t\t},\n\t\tresolveId(id) {\n\t\t\tif (id === TEMPLATE_ID) return RESOLVED_TEMPLATE_ID;\n\t\t},\n\t\tasync load(id) {\n\t\t\tif (id !== RESOLVED_TEMPLATE_ID) return;\n\t\t\ttemplatePromise ??= buildDevTemplate(viteServer!, site);\n\t\t\treturn `export default ${JSON.stringify(await templatePromise)};`;\n\t\t},\n\t};\n\n\tconst cfPlugins = cloudflare({\n\t\tviteEnvironment: { name: \"ssr\" },\n\t\tconfig: {\n\t\t\tmain: \"./node_modules/@torpor/adapter-cloudflare/dist/_worker.dev.ts\",\n\t\t},\n\t});\n\n\treturn [templatePlugin, ...cfPlugins];\n}\n\nasync function buildDevTemplate(vite: ViteDevServer, site: Site): Promise<string | undefined> {\n\t// Read site.html if present. An endpoints-only site has no site.html, and\n\t// no template is needed\n\tconst templateFile = path.resolve(site.root, \"src/site.html\");\n\tif (!existsSync(templateFile)) {\n\t\treturn undefined;\n\t}\n\tlet template = await fs.readFile(templateFile, \"utf-8\");\n\ttemplate = await vite.transformIndexHtml(\"\", template);\n\n\tconst siteFolder = path.resolve(site.root, \"./node_modules/@torpor/build/src/site/\");\n\tconst clientScript = path.join(siteFolder, \"clientEntry.ts\");\n\tconst clientDevScript = path.join(siteFolder, \"clientEntryDev.ts\");\n\n\t// Splice component placeholders and append the production client entry\n\ttemplate = prepareTemplate(template, clientScript);\n\n\t// Inject the dev client entry (for HMR) just before the client entry\n\tconst clientTag = `<script type=\"module\" src=\"${clientScript}\"></script>`;\n\ttemplate = template.replace(\n\t\tclientTag,\n\t\t`<script type=\"module\" src=\"${clientDevScript}\"></script>\\n${clientTag}`,\n\t);\n\n\treturn template;\n}\n","import { type Adapter, Site } from \"@torpor/build\";\nimport { spawn } from \"node:child_process\";\nimport { createRequire } from \"node:module\";\nimport { existsSync, promises as fs } from \"node:fs\";\nimport path from \"node:path\";\nimport { build, defineConfig } from \"vite\";\nimport cloudflareDev from \"./cloudflareDev\";\nimport prepareTemplate from \"./prepareTemplate\";\n\nexport default {\n\tpostbuild,\n\tdev: (site: Site) => cloudflareDev(site),\n\tserve: (_server, site: Site) => {\n\t\t// Preview the built Cloudflare worker (from postbuild) with `wrangler dev`\n\t\t// rather than a Node server, so preview matches production (workerd).\n\t\tconst configPath = path.join(site.root, \"dist\", \"wrangler.toml\");\n\t\tconst args = [\"dev\", \"--config\", configPath];\n\n\t\t// Resolve the wrangler bin from the consuming site's node_modules; fall\n\t\t// back to `wrangler` on PATH (e.g. when run via a package manager).\n\t\tlet child;\n\t\ttry {\n\t\t\tconst require = createRequire(path.join(site.root, \"package.json\"));\n\t\t\tconst wranglerBin = require.resolve(\"wrangler/bin/wrangler.js\");\n\t\t\tchild = spawn(process.execPath, [wranglerBin, ...args], {\n\t\t\t\tstdio: \"inherit\",\n\t\t\t\tcwd: site.root,\n\t\t\t});\n\t\t} catch {\n\t\t\tchild = spawn(\"wrangler\", args, { stdio: \"inherit\", cwd: site.root });\n\t\t}\n\t\tchild.on(\"exit\", (code) => process.exit(code ?? 0));\n\t},\n} satisfies Adapter as Adapter;\n\nasync function postbuild(site: Site): Promise<void> {\n\t// Build _worker.js as per\n\t// https://developers.cloudflare.com/pages/functions/advanced-mode/\n\n\tconst distFolder = path.resolve(site.root, \"dist\");\n\tconst clientFolder = path.join(distFolder, \"client\");\n\tconst serverFolder = path.join(distFolder, \"server\");\n\tconst tempFolder = path.join(distFolder, \"temp\");\n\tconst cloudflareFolder = path.join(distFolder, \"cloudflare\");\n\n\tconst buildSrcFolder = path.resolve(site.root, \"./node_modules/@torpor/build/src/\");\n\tconst adapterDistFolder = path.resolve(\n\t\tsite.root,\n\t\t\"./node_modules/@torpor/adapter-cloudflare/dist/\",\n\t);\n\n\t// Compile _worker.ts\n\tlet workerFile = path.join(adapterDistFolder, \"_worker.ts\");\n\tlet workerSource = await fs.readFile(workerFile, \"utf-8\");\n\n\t// Read and prepare site.html so that we can just splice components into it.\n\t// An endpoints-only site has no site.html, and no template is needed\n\tlet template: string | undefined;\n\tconst siteHtml = path.join(clientFolder, \"site.html\");\n\tif (existsSync(siteHtml)) {\n\t\tlet clientScript = (await fs.readdir(path.join(clientFolder, \"assets\"))).find((f) =>\n\t\t\tf.startsWith(\"clientEntry-\"),\n\t\t);\n\t\tif (!clientScript) {\n\t\t\tthrow new Error(\"clientEntry.js not found\");\n\t\t}\n\t\tclientScript = `/assets/${clientScript}`;\n\n\t\ttemplate = prepareTemplate(await fs.readFile(siteHtml, \"utf-8\"), clientScript);\n\t}\n\n\tconst serverClass = path.join(buildSrcFolder, \"server\", \"Server.ts\");\n\tconst serverScript = path.join(serverFolder, \"serverEntry.js\");\n\n\t// Splice file names into _worker.ts\n\tworkerSource = workerSource\n\t\t.replace(\"%SERVER_CLASS%\", serverClass)\n\t\t.replace(\"%SERVER_SCRIPT%\", serverScript)\n\t\t.replace(\"`%HTML_TEMPLATE%`\", () => (template ? JSON.stringify(template) : \"undefined\"));\n\n\tworkerFile = path.join(tempFolder, \"_worker.ts\");\n\tawait fs.mkdir(tempFolder);\n\tawait fs.writeFile(workerFile, workerSource);\n\n\tconst cloudflareConfig = structuredClone(site.viteConfig ?? {});\n\tcloudflareConfig.build ??= {};\n\tcloudflareConfig.build.outDir = cloudflareFolder;\n\tcloudflareConfig.build.rollupOptions ??= {};\n\tcloudflareConfig.build.rollupOptions.input = [workerFile, ...site.inputs];\n\tcloudflareConfig.build.rollupOptions.output = {\n\t\tentryFileNames: `[name].js`,\n\t\tchunkFileNames: `[name].js`,\n\t\tassetFileNames: `[name].[ext]`,\n\t};\n\tcloudflareConfig.build.rollupOptions.preserveEntrySignatures = \"strict\";\n\t// HACK: We don't need to minify as the worker isn't downloaded?\n\t// And it makes debugging easier\n\tcloudflareConfig.build.minify = false;\n\t// HACK: Build for SSR so we don't get document and window references\n\tcloudflareConfig.build.ssr = true;\n\tawait build(defineConfig(cloudflareConfig));\n\n\t// Copy the client assets into the Cloudflare folder\n\tawait fs.mkdir(path.join(cloudflareFolder, \"assets\"));\n\tfor (let file of await fs.readdir(path.join(clientFolder, \"assets\"))) {\n\t\tawait fs.copyFile(\n\t\t\tpath.join(clientFolder, \"assets\", file),\n\t\t\tpath.join(cloudflareFolder, \"assets\", file),\n\t\t);\n\t}\n\n\t// Build a wrangler.toml file for running with `wrangler dev`\n\tconst wranglerConfig = `\nname = \"torpor-miniflare\"\nmain = \"./cloudflare/_worker.js\"\ncompatibility_date = \"2025-01-01\"\n\n[assets]\ndirectory = \"./cloudflare\"\nbinding = \"ASSETS\"\n\t`;\n\tconst wranglerFile = path.join(distFolder, \"wrangler.toml\");\n\tawait fs.writeFile(wranglerFile, wranglerConfig);\n\n\t// Build a .assetsignore file\n\tconst assetsIgnore = `\n**/node_modules\n**/.DS_Store\n**/.git\n_worker.js\n`;\n\tconst assetsIgnoreFile = path.join(cloudflareFolder, \".assetsignore\");\n\tawait fs.writeFile(assetsIgnoreFile, assetsIgnore);\n\n\tawait fs.rm(tempFolder, { recursive: true });\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@torpor/adapter-cloudflare",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "A @torpor/build adapter for deploying to Cloudflare Pages",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -17,19 +17,23 @@
|
|
|
17
17
|
"author": "",
|
|
18
18
|
"license": "ISC",
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"vite": "
|
|
21
|
-
"@torpor/build": "^0.
|
|
20
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@0.2.7",
|
|
21
|
+
"@torpor/build": "^1.0.0"
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"@cloudflare/vite-plugin": "^1.0.0",
|
|
25
|
+
"wrangler": "^4.118.0"
|
|
22
26
|
},
|
|
23
27
|
"devDependencies": {
|
|
24
|
-
"@
|
|
25
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
28
|
+
"@cloudflare/vite-plugin": "^1.50.0",
|
|
29
|
+
"@typescript/native-preview": "7.0.0-dev.20260707.2",
|
|
30
|
+
"typescript": "^7.0.2",
|
|
31
|
+
"vite-plus": "0.2.7",
|
|
32
|
+
"wrangler": "^4.118.0"
|
|
29
33
|
},
|
|
30
34
|
"scripts": {
|
|
31
35
|
"check": "tsgo --noEmit && pnpm dlx oxlint --type-aware",
|
|
32
|
-
"build": "tsgo --noEmit &&
|
|
33
|
-
"build:watch": "
|
|
36
|
+
"build": "tsgo --noEmit && vp pack",
|
|
37
|
+
"build:watch": "vp pack --watch"
|
|
34
38
|
}
|
|
35
39
|
}
|