Skip to content

Commit 2037f4d

Browse files
authored
Switch from Cloudflare Pages to Workers with Static Assets (#989)
Fixes #944
1 parent 82dea92 commit 2037f4d

File tree

2 files changed

+96
-101
lines changed

2 files changed

+96
-101
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1007,7 +1007,7 @@ Note: When rendering in static mode, please be sure to return `render: 'static'`
10071007

10081008
```
10091009
npm run build -- --with-cloudflare
1010-
npx wrangler pages dev # or deploy
1010+
npx wrangler dev # or deploy
10111011
```
10121012

10131013
### PartyKit (experimental)

packages/waku/src/lib/plugins/vite-plugin-deploy-cloudflare.ts

+95-100
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ import {
44
existsSync,
55
mkdirSync,
66
readdirSync,
7-
renameSync,
87
rmSync,
98
writeFileSync,
9+
copyFileSync,
1010
} from 'node:fs';
11+
import os from 'node:os';
12+
import { randomBytes } from 'node:crypto';
13+
1114
import type { Plugin } from 'vite';
1215

1316
import { unstable_getPlatformObject } from '../../server.js';
@@ -62,24 +65,82 @@ export default {
6265
};
6366
`;
6467

65-
const getFiles = (dir: string, files: string[] = []): string[] => {
66-
const entries = readdirSync(dir, { withFileTypes: true });
67-
for (const entry of entries) {
68-
const fullPath = path.join(dir, entry.name);
69-
if (entry.isDirectory()) {
70-
getFiles(fullPath, files);
71-
} else {
72-
files.push(fullPath);
68+
function copyFiles(srcDir: string, destDir: string, extensions: string[]) {
69+
const files = readdirSync(srcDir, { withFileTypes: true });
70+
for (const file of files) {
71+
const srcPath = path.join(srcDir, file.name);
72+
const destPath = path.join(destDir, file.name);
73+
if (file.isDirectory()) {
74+
mkdirSync(destPath, { recursive: true });
75+
copyFiles(srcPath, destPath, extensions);
76+
} else if (extensions.some((ext) => file.name.endsWith(ext))) {
77+
copyFileSync(srcPath, destPath);
7378
}
7479
}
75-
return files;
76-
};
80+
}
7781

78-
const WORKER_JS_NAME = '_worker.js';
79-
const ROUTES_JSON_NAME = '_routes.json';
80-
const HEADERS_NAME = '_headers';
82+
function copyDirectory(srcDir: string, destDir: string) {
83+
const files = readdirSync(srcDir, { withFileTypes: true });
84+
for (const file of files) {
85+
const srcPath = path.join(srcDir, file.name);
86+
const destPath = path.join(destDir, file.name);
87+
if (file.isDirectory()) {
88+
mkdirSync(destPath, { recursive: true });
89+
copyDirectory(srcPath, destPath);
90+
} else {
91+
copyFileSync(srcPath, destPath);
92+
}
93+
}
94+
}
8195

82-
type StaticRoutes = { version: number; include: string[]; exclude: string[] };
96+
function separatePublicAssetsFromFunctions({
97+
outDir,
98+
functionDir,
99+
assetsDir,
100+
}: {
101+
outDir: string;
102+
functionDir: string;
103+
assetsDir: string;
104+
}) {
105+
const tempDist = path.join(
106+
os.tmpdir(),
107+
`dist_${randomBytes(16).toString('hex')}`,
108+
);
109+
const tempPublicDir = path.join(tempDist, DIST_PUBLIC);
110+
const workerPublicDir = path.join(functionDir, DIST_PUBLIC);
111+
112+
// Create a temp dir to prepare the separated files
113+
rmSync(tempDist, { recursive: true, force: true });
114+
mkdirSync(tempDist, { recursive: true });
115+
116+
// Move the current dist dir to the temp dir
117+
// Folders are copied instead of moved to avoid issues on Windows
118+
copyDirectory(outDir, tempDist);
119+
rmSync(outDir, { recursive: true, force: true });
120+
121+
// Create empty directories at the desired deploy locations
122+
// for the function and the assets
123+
mkdirSync(functionDir, { recursive: true });
124+
mkdirSync(assetsDir, { recursive: true });
125+
126+
// Move tempDist/public to assetsDir
127+
copyDirectory(tempPublicDir, assetsDir);
128+
rmSync(tempPublicDir, { recursive: true, force: true });
129+
130+
// Move tempDist to functionDir
131+
copyDirectory(tempDist, functionDir);
132+
rmSync(tempDist, { recursive: true, force: true });
133+
134+
// Traverse assetsDir and copy specific files to functionDir/public
135+
mkdirSync(workerPublicDir, { recursive: true });
136+
copyFiles(assetsDir, workerPublicDir, [
137+
'.txt',
138+
'.html',
139+
'.json',
140+
'.js',
141+
'.css',
142+
]);
143+
}
83144

84145
export function deployCloudflarePlugin(opts: {
85146
srcDir: string;
@@ -136,93 +197,18 @@ export function deployCloudflarePlugin(opts: {
136197
}
137198

138199
const outDir = path.join(rootDir, opts.distDir);
139-
140-
// Advanced-mode Cloudflare Pages imports _worker.js
141-
// and can be configured with _routes.json to serve other static root files
142-
mkdirSync(path.join(outDir, WORKER_JS_NAME));
143-
const outPaths = readdirSync(outDir);
144-
for (const p of outPaths) {
145-
if (p === WORKER_JS_NAME) {
146-
continue;
147-
}
148-
renameSync(path.join(outDir, p), path.join(outDir, WORKER_JS_NAME, p));
149-
}
150-
151-
const workerEntrypoint = path.join(outDir, WORKER_JS_NAME, 'index.js');
152-
if (!existsSync(workerEntrypoint)) {
153-
writeFileSync(
154-
workerEntrypoint,
155-
`
156-
import server from './${SERVE_JS}'
157-
158-
export default {
159-
...server
160-
}
161-
`,
162-
);
163-
}
164-
165-
// Create _routes.json if one doesn't already exist in the public dir
166-
// https://developers.cloudflare.com/pages/functions/routing/#functions-invocation-routes
167-
const routesFile = path.join(outDir, ROUTES_JSON_NAME);
168-
const publicDir = path.join(outDir, WORKER_JS_NAME, DIST_PUBLIC);
169-
if (!existsSync(path.join(publicDir, ROUTES_JSON_NAME))) {
170-
// exclude strategy
171-
const staticPaths: string[] = ['/assets/*'];
172-
const paths = getFiles(publicDir);
173-
for (const p of paths) {
174-
const basePath = path.dirname(p.replace(publicDir, '')) || '/';
175-
const name = path.basename(p);
176-
const entry =
177-
name === 'index.html'
178-
? basePath + (basePath !== '/' ? '/' : '')
179-
: path.join(basePath, name.replace(/\.html$/, ''));
180-
if (
181-
entry.startsWith('/assets/') ||
182-
entry.startsWith('/' + WORKER_JS_NAME + '/') ||
183-
entry === '/' + WORKER_JS_NAME ||
184-
entry === '/' + ROUTES_JSON_NAME ||
185-
entry === '/' + HEADERS_NAME
186-
) {
187-
continue;
188-
}
189-
if (!staticPaths.includes(entry)) {
190-
staticPaths.push(entry);
191-
}
192-
}
193-
const MAX_CLOUDFLARE_RULES = 100;
194-
if (staticPaths.length + 1 > MAX_CLOUDFLARE_RULES) {
195-
throw new Error(
196-
`The number of static paths exceeds the limit of ${MAX_CLOUDFLARE_RULES}. ` +
197-
`You need to create a custom ${ROUTES_JSON_NAME} file in the public folder. ` +
198-
`See https://developers.cloudflare.com/pages/functions/routing/#functions-invocation-routes`,
199-
);
200-
}
201-
const staticRoutes: StaticRoutes = {
202-
version: 1,
203-
include: ['/*'],
204-
exclude: staticPaths,
205-
};
206-
writeFileSync(routesFile, JSON.stringify(staticRoutes));
207-
}
208-
209-
// Move the public files to the root of the dist folder
210-
const publicPaths = readdirSync(
211-
path.join(outDir, WORKER_JS_NAME, DIST_PUBLIC),
212-
);
213-
for (const p of publicPaths) {
214-
renameSync(
215-
path.join(outDir, WORKER_JS_NAME, DIST_PUBLIC, p),
216-
path.join(outDir, p),
217-
);
218-
}
219-
rmSync(path.join(outDir, WORKER_JS_NAME, DIST_PUBLIC), {
220-
recursive: true,
221-
force: true,
200+
const assetsDistDir = path.join(outDir, 'assets');
201+
const workerDistDir = path.join(outDir, 'worker');
202+
203+
// Move the public static assets to a separate folder from the server files
204+
separatePublicAssetsFromFunctions({
205+
outDir,
206+
assetsDir: assetsDistDir,
207+
functionDir: workerDistDir,
222208
});
223209

224210
appendFileSync(
225-
path.join(outDir, WORKER_JS_NAME, DIST_ENTRIES_JS),
211+
path.join(workerDistDir, DIST_ENTRIES_JS),
226212
`export const buildData = ${JSON.stringify(platformObject.buildData)};`,
227213
);
228214

@@ -233,9 +219,18 @@ export default {
233219
`
234220
# See https://developers.cloudflare.com/pages/functions/wrangler-configuration/
235221
name = "waku-project"
236-
compatibility_date = "2024-09-02"
222+
compatibility_date = "2024-09-23"
237223
compatibility_flags = [ "nodejs_als" ]
238224
pages_build_output_dir = "./dist"
225+
main = "./dist/worker/serve-cloudflare.js"
226+
227+
assets = {
228+
directory = "./dist/assets",
229+
binding = "ASSETS",
230+
html_handling = "drop-trailing-slash",
231+
# "single-page-application" | "404-page" | "none"
232+
not_found_handling = "404-page"
233+
}
239234
`,
240235
);
241236
}

0 commit comments

Comments
 (0)