diff --git a/README.md b/README.md index fdf2f0998..7254e1696 100644 --- a/README.md +++ b/README.md @@ -292,6 +292,27 @@ Alternatively, you could create a project with something like `npm create waku@latest` and copy files from the example folder in the repository. +## Deploy + +### Vercel + +```sh +vercel +``` + +Then change the setting as follows (needs redeploy for the first time): + +![vercel](https://github.com/dai-shi/waku/assets/490574/6bd317a8-2772-42f4-92d4-b508af7d7460) + +### Cloudflare + +```sh +npm run build -- --with-cloudflare +rm -r node_modules +npm install --omit=dev --omit=peer +npx wrangler dev # or deploy +``` + ## Tweets diff --git a/packages/waku/src/cli.ts b/packages/waku/src/cli.ts index e4c30931a..cd3f356f5 100755 --- a/packages/waku/src/cli.ts +++ b/packages/waku/src/cli.ts @@ -22,6 +22,12 @@ const { values, positionals } = parseArgs({ 'with-ssr': { type: 'boolean', }, + 'with-vercel': { + type: 'boolean', + }, + 'with-cloudflare': { + type: 'boolean', + }, version: { type: 'boolean', short: 'v', @@ -46,7 +52,11 @@ if (values.version) { runDev({ ssr: !!values['with-ssr'] }); break; case 'build': - runBuild({ ssr: !!values['with-ssr'] }); + runBuild({ + ssr: !!values['with-ssr'], + vercel: values['with-vercel'], + cloudflare: !!values['with-cloudflare'], + }); break; case 'start': runStart({ ssr: !!values['with-ssr'] }); @@ -62,13 +72,17 @@ if (values.version) { async function runDev(options: { ssr: boolean }) { const app = new Hono(); - app.use('*', honoDevMiddleware({ ssr: options.ssr })); + app.use('*', honoDevMiddleware(options)); const port = parseInt(process.env.PORT || '3000', 10); startServer(app, port); } -async function runBuild(options: { ssr: boolean }) { - await build({ ssr: options.ssr }); +async function runBuild(options: { + ssr: boolean; + vercel: boolean | undefined; + cloudflare: boolean; +}) { + await build(options); } async function runStart(options: { ssr: boolean }) { @@ -77,7 +91,7 @@ async function runStart(options: { ssr: boolean }) { url.pathToFileURL(path.resolve(distDir, entriesJs)).toString() ); const app = new Hono(); - app.use('*', honoPrdMiddleware({ entries, ssr: options.ssr })); + app.use('*', honoPrdMiddleware({ entries, ...options })); app.use('*', serveStatic({ root: path.join(distDir, publicDir) })); const port = parseInt(process.env.PORT || '8080', 10); startServer(app, port); @@ -108,6 +122,8 @@ Commands: Options: --with-ssr Use opt-in SSR + --with-vercel Output for Vercel on build + --with-cloudflare Output for Cloudflare on build -v, --version Display the version number -h, --help Display this help message `); diff --git a/packages/waku/src/lib/builder/build.ts b/packages/waku/src/lib/builder/build.ts index 1bcf14005..cbabfc831 100644 --- a/packages/waku/src/lib/builder/build.ts +++ b/packages/waku/src/lib/builder/build.ts @@ -44,6 +44,7 @@ import { nonjsResolvePlugin } from '../plugins/vite-plugin-nonjs-resolve.js'; import { rscTransformPlugin } from '../plugins/vite-plugin-rsc-transform.js'; import { patchReactRefresh } from '../plugins/patch-react-refresh.js'; import { emitVercelOutput } from './output-vercel.js'; +import { emitCloudflareOutput } from './output-cloudflare.js'; // TODO this file and functions in it are too long. will fix. @@ -481,7 +482,8 @@ const resolveFileName = (fname: string) => { export async function build(options: { config?: Config; ssr?: boolean; - vercel?: boolean; + vercel?: boolean | undefined; + cloudflare?: boolean; }) { const config = await resolveConfig(options.config || {}); const rootDir = ( @@ -536,4 +538,8 @@ export async function build(options: { !!options?.ssr, ); } + + if (options?.cloudflare) { + await emitCloudflareOutput(rootDir, config, !!options?.ssr); + } } diff --git a/packages/waku/src/lib/builder/output-cloudflare.ts b/packages/waku/src/lib/builder/output-cloudflare.ts new file mode 100644 index 000000000..8d361bbf2 --- /dev/null +++ b/packages/waku/src/lib/builder/output-cloudflare.ts @@ -0,0 +1,54 @@ +import path from 'node:path'; +import { existsSync, writeFileSync } from 'node:fs'; + +import type { ResolvedConfig } from '../config.js'; + +// XXX this can be very limited. FIXME if anyone has better knowledge. +export const emitCloudflareOutput = async ( + rootDir: string, + config: ResolvedConfig, + ssr: boolean, +) => { + const outputDir = path.resolve('.'); + const relativeRootDir = path.relative(outputDir, rootDir); + const entriesFile = path.join( + relativeRootDir, + config.distDir, + config.entriesJs, + ); + const publicDir = path.join( + relativeRootDir, + config.distDir, + config.publicDir, + ); + if (!existsSync(path.join(outputDir, 'serve.js'))) { + writeFileSync( + path.join(outputDir, 'serve.js'), + ` +import { honoMiddleware } from 'waku'; +import { Hono } from 'hono'; +import { serveStatic } from 'hono/cloudflare-workers'; + +const entries = import('./${entriesFile}'); + +const app = new Hono(); +app.use('*', honoMiddleware({ entries, ssr: ${ssr} })); +app.use('*', serveStatic({ root: './' })); +export default app; +`, + ); + } + if (!existsSync(path.join(outputDir, 'wrangler.toml'))) { + writeFileSync( + path.join(outputDir, 'wrangler.toml'), + ` +name = "waku-project" +main = "serve.js" +compatibility_date = "2023-12-06" + +[site] +bucket = "./${publicDir}" +`, + ); + } +}; diff --git a/packages/waku/src/lib/rsc/worker-api.ts b/packages/waku/src/lib/rsc/worker-api.ts index 3c23366c2..96bcad48e 100644 --- a/packages/waku/src/lib/rsc/worker-api.ts +++ b/packages/waku/src/lib/rsc/worker-api.ts @@ -46,7 +46,14 @@ const getWorker = () => { return lastWorker; } return (lastWorker = new Promise((resolve, reject) => { - Promise.all([import('node:worker_threads'), import('node:module')]) + Promise.all([ + import('node:worker_threads').catch((e) => { + throw e; + }), + import('node:module').catch((e) => { + throw e; + }), + ]) .then(([{ Worker }, { default: module }]) => { const HAS_MODULE_REGISTER = typeof module.register === 'function'; const worker = new Worker(new URL('worker-impl.js', import.meta.url), {