From 7f423581411648c9a69b68918ff930581f12cf16 Mon Sep 17 00:00:00 2001 From: Richard Cooke Date: Thu, 16 Jun 2022 15:12:25 +0100 Subject: [PATCH] feat: add SSR adaptor for cloudflare pages functions (#3600) --- .changeset/nine-dots-applaud.md | 5 ++ packages/integrations/cloudflare/README.md | 24 ++++++ packages/integrations/cloudflare/package.json | 33 +++++++++ packages/integrations/cloudflare/src/index.ts | 73 +++++++++++++++++++ .../integrations/cloudflare/src/server.ts | 34 +++++++++ packages/integrations/cloudflare/src/shim.ts | 4 + .../integrations/cloudflare/tsconfig.json | 10 +++ pnpm-lock.yaml | 11 +++ 8 files changed, 194 insertions(+) create mode 100644 .changeset/nine-dots-applaud.md create mode 100644 packages/integrations/cloudflare/README.md create mode 100644 packages/integrations/cloudflare/package.json create mode 100644 packages/integrations/cloudflare/src/index.ts create mode 100644 packages/integrations/cloudflare/src/server.ts create mode 100644 packages/integrations/cloudflare/src/shim.ts create mode 100644 packages/integrations/cloudflare/tsconfig.json diff --git a/.changeset/nine-dots-applaud.md b/.changeset/nine-dots-applaud.md new file mode 100644 index 000000000000..b1823dd26bb0 --- /dev/null +++ b/.changeset/nine-dots-applaud.md @@ -0,0 +1,5 @@ +--- +'@astrojs/cloudflare': minor +--- + +add SSR adaptor for Cloudflare Pages functions diff --git a/packages/integrations/cloudflare/README.md b/packages/integrations/cloudflare/README.md new file mode 100644 index 000000000000..73064e821aec --- /dev/null +++ b/packages/integrations/cloudflare/README.md @@ -0,0 +1,24 @@ +# @astrojs/cloudflare + +An SSR adapter for use with Cloudflare Pages Functions targets. Write your code in Astro/Node and deploy to Cloudflare Pages. + +In your astro.config.mjs use: + +```js +import { defineConfig } from 'astro/config'; +import cloudflare from '@astrojs/cloudflare'; + +export default defineConfig({ + adapter: cloudflare() +}); +``` + +## Enabling Preview + +In order for preview to work you must install `wrangler` + +```sh +$ pnpm install wrangler --save-dev +``` + +It's then possible to update the preview script in your `package.json` to `"preview": "wrangler pages dev ./dist"` diff --git a/packages/integrations/cloudflare/package.json b/packages/integrations/cloudflare/package.json new file mode 100644 index 000000000000..ff8f2c64102a --- /dev/null +++ b/packages/integrations/cloudflare/package.json @@ -0,0 +1,33 @@ +{ + "name": "@astrojs/cloudflare", + "description": "Deploy your site to cloudflare pages functions", + "version": "0.1.0", + "type": "module", + "types": "./dist/index.d.ts", + "author": "withastro", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/withastro/astro.git", + "directory": "packages/integrations/cloudflare" + }, + "bugs": "https://github.com/withastro/astro/issues", + "homepage": "https://astro.build", + "exports": { + ".": "./dist/index.js", + "./server.js": "./dist/server.js", + "./package.json": "./package.json" + }, + "scripts": { + "build": "astro-scripts build \"src/**/*.ts\" && tsc", + "build:ci": "astro-scripts build \"src/**/*.ts\"", + "dev": "astro-scripts dev \"src/**/*.ts\"" + }, + "dependencies": { + "esbuild": "^0.14.42" + }, + "devDependencies": { + "astro": "workspace:*", + "astro-scripts": "workspace:*" + } +} \ No newline at end of file diff --git a/packages/integrations/cloudflare/src/index.ts b/packages/integrations/cloudflare/src/index.ts new file mode 100644 index 000000000000..dc65f23ce5b0 --- /dev/null +++ b/packages/integrations/cloudflare/src/index.ts @@ -0,0 +1,73 @@ +import type { AstroAdapter, AstroConfig, AstroIntegration, BuildConfig } from 'astro'; +import esbuild from 'esbuild'; +import * as fs from 'fs'; +import { fileURLToPath } from 'url'; + +export function getAdapter(): AstroAdapter { + return { + name: '@astrojs/cloudflare', + serverEntrypoint: '@astrojs/cloudflare/server.js', + exports: ['default'], + }; +} + +export default function createIntegration(): AstroIntegration { + let _config: AstroConfig; + let _buildConfig: BuildConfig; + + return { + name: '@astrojs/cloudflare', + hooks: { + 'astro:config:done': ({ setAdapter, config }) => { + setAdapter(getAdapter()); + _config = config; + }, + 'astro:build:start': ({ buildConfig }) => { + _buildConfig = buildConfig; + buildConfig.serverEntry = '_worker.js'; + buildConfig.client = new URL('./static/', _config.outDir); + buildConfig.server = new URL('./', _config.outDir); + }, + 'astro:build:setup': ({ vite, target }) => { + if (target === 'server') { + vite.resolve = vite.resolve || {}; + vite.resolve.alias = vite.resolve.alias || {}; + + const aliases = [{ find: 'react-dom/server', replacement: 'react-dom/server.browser' }]; + + if (Array.isArray(vite.resolve.alias)) { + vite.resolve.alias = [...vite.resolve.alias, ...aliases]; + } else { + for (const alias of aliases) { + (vite.resolve.alias as Record)[alias.find] = alias.replacement; + } + } + + vite.ssr = { + target: 'webworker', + noExternal: true, + }; + } + }, + 'astro:build:done': async () => { + const entryUrl = new URL(_buildConfig.serverEntry, _buildConfig.server); + const pkg = fileURLToPath(entryUrl); + + await esbuild.build({ + target: 'es2020', + platform: 'browser', + entryPoints: [pkg], + outfile: pkg, + allowOverwrite: true, + format: 'esm', + bundle: true, + minify: true, + }); + + // throw the server folder in the bin + const chunksUrl = new URL('./chunks', _buildConfig.server); + await fs.promises.rm(chunksUrl, { recursive: true, force: true }); + }, + }, + }; +} diff --git a/packages/integrations/cloudflare/src/server.ts b/packages/integrations/cloudflare/src/server.ts new file mode 100644 index 000000000000..6a76c06ff48c --- /dev/null +++ b/packages/integrations/cloudflare/src/server.ts @@ -0,0 +1,34 @@ +import './shim.js'; + +import type { SSRManifest } from 'astro'; +import { App } from 'astro/app'; + +type Env = { + ASSETS: { fetch: (req: Request) => Promise }; +}; + +export function createExports(manifest: SSRManifest) { + const app = new App(manifest); + + const fetch = async (request: Request, env: Env) => { + const { origin, pathname } = new URL(request.url); + + // static assets + if (manifest.assets.has(pathname)) { + const assetRequest = new Request(`${origin}/static${pathname}`, request); + return env.ASSETS.fetch(assetRequest); + } + + if (app.match(request)) { + return app.render(request); + } + + // 404 + return new Response(null, { + status: 404, + statusText: 'Not found', + }); + }; + + return { default: { fetch } }; +} diff --git a/packages/integrations/cloudflare/src/shim.ts b/packages/integrations/cloudflare/src/shim.ts new file mode 100644 index 000000000000..1a4a6ee9be4c --- /dev/null +++ b/packages/integrations/cloudflare/src/shim.ts @@ -0,0 +1,4 @@ +(globalThis as any).process = { + argv: [], + env: {}, +}; diff --git a/packages/integrations/cloudflare/tsconfig.json b/packages/integrations/cloudflare/tsconfig.json new file mode 100644 index 000000000000..44baf375c882 --- /dev/null +++ b/packages/integrations/cloudflare/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../../tsconfig.base.json", + "include": ["src"], + "compilerOptions": { + "allowJs": true, + "module": "ES2020", + "outDir": "./dist", + "target": "ES2020" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 533f97ef70c1..50e86253c4e8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1664,6 +1664,17 @@ importers: mocha: 9.2.2 uvu: 0.5.3 + packages/integrations/cloudflare: + specifiers: + astro: workspace:* + astro-scripts: workspace:* + esbuild: ^0.14.42 + dependencies: + esbuild: 0.14.43 + devDependencies: + astro: link:../../astro + astro-scripts: link:../../../scripts + packages/integrations/deno: specifiers: astro: workspace:*