Skip to content

Commit

Permalink
feat: migrate cloudflare workers adapter to cloudflare pages
Browse files Browse the repository at this point in the history
  • Loading branch information
rmarscher committed Jul 17, 2024
1 parent f61b264 commit 2175ac0
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 32 deletions.
88 changes: 80 additions & 8 deletions packages/waku/src/lib/builder/output-cloudflare.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,99 @@
import path from 'node:path';
import { existsSync, writeFileSync } from 'node:fs';

import {
existsSync,
readdirSync,
writeFileSync,
mkdirSync,
renameSync,
rmSync,
} from 'node:fs';
import type { ResolvedConfig } from '../config.js';
import { DIST_PUBLIC } from './constants.js';

// XXX this can be very limited. FIXME if anyone has better knowledge.
const WORKER_JS_NAME = '_worker.js'
const ROUTES_JSON_NAME = '_routes.json';

type StaticRoutes = { version: number; include: string[]; exclude: string[] }

export const emitCloudflareOutput = async (
rootDir: string,
config: ResolvedConfig,
serveJs: string,
) => {
const outDir = path.join(rootDir, config.distDir);

// Advanced-mode Cloudflare Pages imports _worker.js
// and can be configured with _routes.json to serve other static root files
mkdirSync(path.join(outDir, WORKER_JS_NAME))
const outPaths = readdirSync(outDir)
for (const p of outPaths) {
if (p === WORKER_JS_NAME) {
continue;
}
renameSync(path.join(outDir, p), path.join(outDir, WORKER_JS_NAME, p))
}

const workerEntrypoint = path.join(outDir, WORKER_JS_NAME, "index.js");
if (!existsSync(workerEntrypoint)) {
writeFileSync(
workerEntrypoint,
`
import server from './${serveJs}'
export default {
...server
}
`,
);
}

// Create _routes.json if one doesn't already exist in the public dir
// https://developers.cloudflare.com/pages/functions/routing/#functions-invocation-routes
const routesFile = path.join(outDir, ROUTES_JSON_NAME);
const publicDir = path.join(outDir, WORKER_JS_NAME, DIST_PUBLIC);
if (!existsSync(path.join(publicDir, ROUTES_JSON_NAME)) {
const staticPaths: string[] = [];
const paths = readdirSync(publicDir, {
withFileTypes: true,
})
for (const p of paths) {
if (p.isDirectory()) {
const entry = `/${p.name}/*`
if (!staticPaths.includes(entry)) {
staticPaths.push(entry)
}
} else {
if (p.name === WORKER_JS_NAME) {
return
}
staticPaths.push(`/${p.name}`)
}
}
const staticRoutes: StaticRoutes = {
version: 1,
include: ['/*'],
exclude: staticPaths,
}
writeFileSync(routesFile, JSON.stringify(staticRoutes))
}

// Move the public files to the root of the dist folder
const publicPaths = readdirSync(path.join(outDir, WORKER_JS_NAME, DIST_PUBLIC))
for (const p of publicPaths) {
renameSync(path.join(outDir, WORKER_JS_NAME, DIST_PUBLIC, p), path.join(outDir, p))
}
rmSync(path.join(outDir, WORKER_JS_NAME, DIST_PUBLIC), { recursive: true, force: true })

const wranglerTomlFile = path.join(rootDir, 'wrangler.toml');
if (!existsSync(wranglerTomlFile)) {
writeFileSync(
wranglerTomlFile,
`
# See https://developers.cloudflare.com/pages/functions/wrangler-configuration/
name = "waku-project"
main = "${config.distDir}/${serveJs}"
compatibility_date = "2023-12-06"
compatibility_date = "2024-04-03"
compatibility_flags = [ "nodejs_als" ]
[site]
bucket = "./${config.distDir}/${DIST_PUBLIC}"
pages_build_output_dir = "./dist"
`,
);
}
Expand Down
34 changes: 17 additions & 17 deletions packages/waku/src/lib/builder/serve-cloudflare.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
import { Hono } from 'hono';
import { serveStatic } from 'hono/cloudflare-workers';
// @ts-expect-error no types
// eslint-disable-next-line import/no-unresolved
import manifest from '__STATIC_CONTENT_MANIFEST';

import { runner } from '../hono/runner.js';

const loadEntries = () => import(import.meta.env.WAKU_ENTRIES_FILE!);
let serveWaku: ReturnType<typeof runner> | undefined;
let staticContent: any;

const parsedManifest: Record<string, string> = JSON.parse(manifest);
export interface CloudflareEnv {
ASSETS: {
fetch: (input: RequestInit | URL, init?: RequestInit) => Promise<Response>
};
}

const app = new Hono();
app.use('*', serveStatic({ root: './', manifest }));
export const app = new Hono<{ Bindings: CloudflareEnv & { [k: string]: unknown } }>();
app.use('*', (c, next) => serveWaku!(c, next));
app.notFound(async (c) => {
const path = parsedManifest['404.html'];
const content: ArrayBuffer | undefined =
path && (await staticContent?.get(path, { type: 'arrayBuffer' }));
if (content) {
c.header('Content-Type', 'text/html; charset=utf-8');
return c.body(content, 404);
const assetsFetcher = c.env.ASSETS;
const url = new URL(c.req.raw.url);
const errorHtmlUrl = `${url.origin}/404.html`;
const notFoundStaticAssetResponse = await assetsFetcher.fetch(new URL(errorHtmlUrl));
if (notFoundStaticAssetResponse && notFoundStaticAssetResponse.status < 400) {
return new Response(notFoundStaticAssetResponse.body, {
status: 404,
statusText: "Not Found",
headers: notFoundStaticAssetResponse.headers
});
}
return c.text('404 Not Found', 404);
return c.text("404 Not Found", 404);
});

export default {
Expand All @@ -34,7 +35,6 @@ export default {
) {
if (!serveWaku) {
serveWaku = runner({ cmd: 'start', loadEntries, env });
staticContent = env.__STATIC_CONTENT;
}
return app.fetch(request, env, ctx);
},
Expand Down
14 changes: 7 additions & 7 deletions packages/waku/src/lib/plugins/vite-plugin-rsc-serve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,18 +59,18 @@ export function rscServePlugin(opts: {
opts.distPublic,
),
};
if (opts.serve === 'cloudflare' || opts.serve === 'partykit') {
if (opts.serve === 'partykit') {
viteConfig.build ||= {};
viteConfig.build.rollupOptions ||= {};
viteConfig.build.rollupOptions.external ||= [];
if (Array.isArray(viteConfig.build.rollupOptions.external)) {
viteConfig.build.rollupOptions.external.push('hono');
if (opts.serve === 'cloudflare') {
viteConfig.build.rollupOptions.external.push(
'hono/cloudflare-workers',
'__STATIC_CONTENT_MANIFEST',
);
}
// if (opts.serve === 'cloudflare') {
// viteConfig.build.rollupOptions.external.push(
// 'hono/cloudflare-workers',
// '__STATIC_CONTENT_MANIFEST',
// );
// }
} else {
throw new Error(
'Unsupported: build.rollupOptions.external is not an array',
Expand Down

0 comments on commit 2175ac0

Please sign in to comment.