From ed7d0db64237b4a20de4258511a28de20a918dc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20F=C3=B6ldi?= Date: Wed, 24 Aug 2022 23:33:08 +0200 Subject: [PATCH] feat: add cloudflare-pages preset (#210) --- docs/content/2.deploy/0.index.md | 1 + docs/content/2.deploy/providers/cloudflare.md | 55 ++++++++++++++++- src/presets/cloudflare.ts | 25 ++++++++ src/runtime/entries/cloudflare-pages.ts | 60 +++++++++++++++++++ src/utils/index.ts | 1 + 5 files changed, 139 insertions(+), 3 deletions(-) create mode 100644 src/runtime/entries/cloudflare-pages.ts diff --git a/docs/content/2.deploy/0.index.md b/docs/content/2.deploy/0.index.md index aa07ba5b0a..1f8aeb957c 100644 --- a/docs/content/2.deploy/0.index.md +++ b/docs/content/2.deploy/0.index.md @@ -17,6 +17,7 @@ When running Nitro in development mode, Nitro will always use a special preset c When deploying to the production using CI/CD, Nitro tries to automatically detect the provider environment and set the right one without any additional configuration. Currently, providers below can be auto-detected with zero config. - [azure](/deploy/providers/azure) +- [cloudflare_pages](/deploy/providers/cloudflare_pages) - [netlify](/deploy/providers/netlify) - [stormkit](/deploy/providers/stormkit) - [vercel](/deploy/providers/vercel) diff --git a/docs/content/2.deploy/providers/cloudflare.md b/docs/content/2.deploy/providers/cloudflare.md index 860e3942a9..eea3d750c7 100644 --- a/docs/content/2.deploy/providers/cloudflare.md +++ b/docs/content/2.deploy/providers/cloudflare.md @@ -3,8 +3,15 @@ title: Cloudflare description: 'Discover Cloudflare preset for Nitro!' --- +## Cloudflare Workers + **Preset:** `cloudflare` ([switch to this preset](/deploy/#changing-the-deployment-preset)) + +::alert{type="info"} +**Note:** This preset uses [service-worker syntax](https://developers.cloudflare.com/workers/learning/service-worker/) for deployment. +:: + Login to your [Cloudflare Workers](https://workers.cloudflare.com) account and obtain your `account_id` from the sidebar. Create a `wrangler.toml` in your root directory: @@ -27,7 +34,7 @@ command = "" upload.format = "service-worker" ``` -## Testing locally +### Testing locally You can use [miniflare](https://miniflare.dev/), a local Cloudflare Workers development server, to test your app locally: @@ -36,7 +43,7 @@ NITRO_PRESET=cloudflare yarn build npx miniflare .output/server/index.mjs --site .output/public ``` -## Deploy from your local machine using wrangler +### Deploy from your local machine using wrangler Install [wrangler](https://github.com/cloudflare/wrangler) and login to your Cloudflare account: @@ -63,7 +70,7 @@ Publish: wrangler publish ``` -## Deploy within CI/CD using GitHub Actions +### Deploy within CI/CD using GitHub Actions Create a token according to [the wrangler action docs](https://github.com/marketplace/actions/deploy-to-cloudflare-workers-with-wrangler#authentication) and set `CF_API_TOKEN` in your repository config on GitHub. @@ -119,3 +126,45 @@ jobs: ``` +## Cloudflare Pages + +**Preset:** `cloudflare_pages` ([switch to this preset](/deploy/#changing-the-deployment-preset)) + +::alert{type="warning"} +**Note:** This is an experimental preset. +:: + +::alert +**Zero Config Provider** +:br +Integration with this provider is possible with zero configuration. ([Learn More](/deploy/#zero-config-providers)) +:: + +### Git integration + +If you use the GitHub/GitLab [integration](https://developers.cloudflare.com/pages/get-started/#connect-your-git-provider-to-pages) with Pages, Nitro does not require any configuration. When you push to the repository, Pages will automatically build your project, and Nitro will detect the environment. + +### Direct Upload + +Alternatively, you can use [wrangler](https://github.com/cloudflare/wrangler2) to upload your project to Cloudflare. In this case, you will have to set the preset manually: + +### Deploy from your local machine using wrangler + +Install [wrangler](https://github.com/cloudflare/wrangler) and login to your Cloudflare account: + +```bash +npm i wrangler -g +wrangler login +``` + +Create project: + +```bash +wrangler pages project create +``` + +Publish: + +```bash +wrangler pages publish +``` diff --git a/src/presets/cloudflare.ts b/src/presets/cloudflare.ts index 5f60c238bb..cf0b247116 100644 --- a/src/presets/cloudflare.ts +++ b/src/presets/cloudflare.ts @@ -1,4 +1,5 @@ import { resolve } from 'pathe' +import { move } from 'fs-extra' import { writeFile } from '../utils' import { defineNitroPreset } from '../preset' import type { Nitro } from '../types' @@ -17,3 +18,27 @@ export const cloudflare = defineNitroPreset({ } } }) + +export const cloudflarePages = defineNitroPreset({ + extends: 'cloudflare', + entry: '#internal/nitro/entries/cloudflare-pages', + commands: { + preview: 'npx wrangler pages dev .output/public', + deploy: 'npx wrangler pages publish .output/public' + }, + output: { + serverDir: '{{ rootDir }}/functions' + }, + rollupConfig: { + output: { + entryFileNames: 'path.js', + format: 'esm' + } + }, + hooks: { + async 'compiled' (nitro: Nitro) { + await move(resolve(nitro.options.output.serverDir, 'path.js'), resolve(nitro.options.output.serverDir, '[[path]].js')) + await move(resolve(nitro.options.output.serverDir, 'path.js.map'), resolve(nitro.options.output.serverDir, '[[path]].js.map')) + } + } +}) diff --git a/src/runtime/entries/cloudflare-pages.ts b/src/runtime/entries/cloudflare-pages.ts new file mode 100644 index 0000000000..d3a601ee74 --- /dev/null +++ b/src/runtime/entries/cloudflare-pages.ts @@ -0,0 +1,60 @@ +import '#internal/nitro/virtual/polyfill' +import { requestHasBody, useRequestBody } from '../utils' +import { nitroApp } from '../app' + +/** @see https://developers.cloudflare.com/pages/platform/functions/#writing-your-first-function */ +interface CFRequestContext { + /** same as existing Worker API */ + request: any + /** same as existing Worker API */ + env: any + /** if filename includes [id] or [[path]] **/ + params: any + /** Same as ctx.waitUntil in existing Worker API */ + waitUntil: any + /** Used for middleware or to fetch assets */ + next: any + /** Arbitrary space for passing data between middlewares */ + data: any +} + +export async function onRequest (ctx: CFRequestContext) { + try { + // const asset = await env.ASSETS.fetch(request, { cacheControl: assetsCacheControl }) + const asset = await ctx.next() + if (asset.status !== 404) { + return asset + } + } catch (_err) { + // Ignore + } + + const url = new URL(ctx.request.url) + let body + if (requestHasBody(ctx.request)) { + body = await useRequestBody(ctx.request) + } + + const r = await nitroApp.localCall({ + url: url.pathname + url.search, + method: ctx.request.method, + headers: ctx.request.headers, + host: url.hostname, + protocol: url.protocol, + body + // TODO: Allow passing custom context + // cf: ctx, + // TODO: Handle redirects? + // redirect: ctx.request.redirect + }) + + return new Response(r.body, { + headers: normalizeOutgoingHeaders(r.headers), + status: r.status, + statusText: r.statusText + }) +} + +function normalizeOutgoingHeaders (headers: Record) { + return Object.entries(headers).map(([k, v]) => [k, Array.isArray(v) ? v.join(',') : v]) +} diff --git a/src/utils/index.ts b/src/utils/index.ts index 1ba749e6e5..69ba0516c7 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -67,6 +67,7 @@ export function replaceAll (input: string, from: string, to: string) { const autodetectableProviders = { azure_static: 'azure', + cloudflare_pages: 'cloudflare_pages', netlify: 'netlify', stormkit: 'stormkit', vercel: 'vercel'