feature(@astrojs/cloudflare): port functionPerRoute (#8078)

* port functionPerRoute to cloudflare

* add changeset

* port bugfix to next

* update changeset

* Update packages/astro/src/core/build/generate.ts

Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>

* update changeset

* update README

* add TODO comment

* Update .changeset/wise-cameras-agree.md

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* Update .changeset/wise-cameras-agree.md

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* Update .changeset/nasty-garlics-listen.md

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* update README

* Update .changeset/wise-cameras-agree.md

Co-authored-by: Yan Thomas <61414485+Yan-Thomas@users.noreply.github.com>

* Update packages/integrations/cloudflare/README.md

Co-authored-by: Yan Thomas <61414485+Yan-Thomas@users.noreply.github.com>

---------

Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>
Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
Co-authored-by: Yan Thomas <61414485+Yan-Thomas@users.noreply.github.com>
This commit is contained in:
Alexander Niebuhr 2023-08-17 23:40:28 +02:00 committed by GitHub
parent bbf0b7470b
commit 2540feedb0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 101 additions and 38 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Reimplement https://github.com/withastro/astro/pull/7509 to correctly emit pre-rendered pages now that `build.split` is deprecated and this configuration has been moved to `functionPerRoute` inside the adapter.

View file

@ -0,0 +1,23 @@
---
'@astrojs/cloudflare': major
---
The configuration `build.split` and `build.excludeMiddleware` are deprecated.
You can now configure this behavior using `functionPerRoute` in your Cloudflare integration config:
```diff
import {defineConfig} from "astro/config";
import cloudflare from '@astrojs/cloudflare';
export default defineConfig({
- build: {
- split: true
- },
- adapter: cloudflare()
+ adapter: cloudflare({
+ mode: 'directory',
+ functionPerRoute: true
+ })
})
```

View file

@ -158,7 +158,11 @@ export async function generatePages(opts: StaticBuildOptions, internals: BuildIn
if (pageData.route.prerender) { if (pageData.route.prerender) {
const ssrEntryURLPage = createEntryURL(filePath, outFolder); const ssrEntryURLPage = createEntryURL(filePath, outFolder);
const ssrEntryPage = await import(ssrEntryURLPage.toString()); const ssrEntryPage = await import(ssrEntryURLPage.toString());
if (opts.settings.config.build.split) { if (
// TODO: remove in Astro 4.0
opts.settings.config.build.split ||
opts.settings.adapter?.adapterFeatures?.functionPerRoute
) {
// forcing to use undefined, so we fail in an expected way if the module is not even there. // forcing to use undefined, so we fail in an expected way if the module is not even there.
const ssrEntry = ssrEntryPage?.manifest?.pageModule; const ssrEntry = ssrEntryPage?.manifest?.pageModule;
if (ssrEntry) { if (ssrEntry) {

View file

@ -44,23 +44,37 @@ export default defineConfig({
default `"advanced"` default `"advanced"`
Cloudflare Pages has 2 different modes for deploying functions, `advanced` mode which picks up the `_worker.js` in `dist`, or a directory mode where pages will compile the worker out of a functions folder in the project root. Cloudflare Pages has 2 different modes for deploying functions, `advanced` mode which picks up the `_worker.js` in `dist`, or a directory mode where pages will compile the worker out of a functions folder in the project root. For most projects the adapter default of `advanced` will be sufficient; the `dist` folder will contain your compiled project.
For most projects the adapter default of `advanced` will be sufficient; the `dist` folder will contain your compiled project. Switching to directory mode allows you to use [pages plugins](https://developers.cloudflare.com/pages/platform/functions/plugins/) such as [Sentry](https://developers.cloudflare.com/pages/platform/functions/plugins/sentry/) or write custom code to enable logging. #### `mode:directory`
In directory mode, the adapter will compile the client side part of your app the same way by default, but moves the worker script into a `functions` folder in the project root. In this case, the adapter will only ever place a `[[path]].js` in that folder, allowing you to add additional plugins and pages middleware which can be checked into version control. Switching to directory mode allows you to use [pages plugins](https://developers.cloudflare.com/pages/platform/functions/plugins/) such as [Sentry](https://developers.cloudflare.com/pages/platform/functions/plugins/sentry/) or write custom code to enable logging.
With the build configuration `split: true`, the adapter instead compiles a separate bundle for each page. This option requires some manual maintenance of the `functions` folder. Files emitted by Astro will overwrite existing `functions` files with identical names, so you must choose unique file names for each file you manually add. Additionally, the adapter will never empty the `functions` folder of outdated files, so you must clean up the folder manually when you remove pages.
Note that this adapter does not support using [Cloudflare Pages Middleware](https://developers.cloudflare.com/pages/platform/functions/middleware/). Astro will bundle the [Astro middleware](https://docs.astro.build/en/guides/middleware/) into each page.
```ts ```ts
// directory mode // astro.config.mjs
export default defineConfig({ export default defineConfig({
adapter: cloudflare({ mode: 'directory' }), adapter: cloudflare({ mode: 'directory' }),
}); });
``` ```
In `directory` mode, the adapter will compile the client-side part of your app the same way as in `advanced` mode by default, but moves the worker script into a `functions` folder in the project root. In this case, the adapter will only ever place a `[[path]].js` in that folder, allowing you to add additional plugins and pages middleware which can be checked into version control.
To instead compile a separate bundle for each page, set the `functionPerPath` option in your Cloudflare adapter config. This option requires some manual maintenance of the `functions` folder. Files emitted by Astro will overwrite existing `functions` files with identical names, so you must choose unique file names for each file you manually add. Additionally, the adapter will never empty the `functions` folder of outdated files, so you must clean up the folder manually when you remove pages.
```diff
import {defineConfig} from "astro/config";
import cloudflare from '@astrojs/cloudflare';
export default defineConfig({
adapter: cloudflare({
mode: 'directory',
+ functionPerRoute: true
})
})
```
Note that this adapter does not support using [Cloudflare Pages Middleware](https://developers.cloudflare.com/pages/platform/functions/middleware/). Astro will bundle the [Astro middleware](https://docs.astro.build/en/guides/middleware/) into each page.
## Enabling Preview ## Enabling Preview
In order for preview to work you must install `wrangler` In order for preview to work you must install `wrangler`

View file

@ -9,6 +9,7 @@ import glob from 'tiny-glob';
type Options = { type Options = {
mode: 'directory' | 'advanced'; mode: 'directory' | 'advanced';
functionPerRoute?: boolean;
}; };
interface BuildConfig { interface BuildConfig {
@ -18,12 +19,22 @@ interface BuildConfig {
split?: boolean; split?: boolean;
} }
export function getAdapter(isModeDirectory: boolean): AstroAdapter { export function getAdapter({
isModeDirectory,
functionPerRoute,
}: {
isModeDirectory: boolean;
functionPerRoute: boolean;
}): AstroAdapter {
return isModeDirectory return isModeDirectory
? { ? {
name: '@astrojs/cloudflare', name: '@astrojs/cloudflare',
serverEntrypoint: '@astrojs/cloudflare/server.directory.js', serverEntrypoint: '@astrojs/cloudflare/server.directory.js',
exports: ['onRequest', 'manifest'], exports: ['onRequest', 'manifest'],
adapterFeatures: {
functionPerRoute,
edgeMiddleware: false,
},
supportedAstroFeatures: { supportedAstroFeatures: {
hybridOutput: 'stable', hybridOutput: 'stable',
staticOutput: 'unsupported', staticOutput: 'unsupported',
@ -67,9 +78,11 @@ const potentialFunctionRouteTypes = ['endpoint', 'page'];
export default function createIntegration(args?: Options): AstroIntegration { export default function createIntegration(args?: Options): AstroIntegration {
let _config: AstroConfig; let _config: AstroConfig;
let _buildConfig: BuildConfig; let _buildConfig: BuildConfig;
const isModeDirectory = args?.mode === 'directory';
let _entryPoints = new Map<RouteData, URL>(); let _entryPoints = new Map<RouteData, URL>();
const isModeDirectory = args?.mode === 'directory';
const functionPerRoute = args?.functionPerRoute ?? false;
return { return {
name: '@astrojs/cloudflare', name: '@astrojs/cloudflare',
hooks: { hooks: {
@ -84,7 +97,7 @@ export default function createIntegration(args?: Options): AstroIntegration {
}); });
}, },
'astro:config:done': ({ setAdapter, config }) => { 'astro:config:done': ({ setAdapter, config }) => {
setAdapter(getAdapter(isModeDirectory)); setAdapter(getAdapter({ isModeDirectory, functionPerRoute }));
_config = config; _config = config;
_buildConfig = config.build; _buildConfig = config.build;
@ -136,7 +149,8 @@ export default function createIntegration(args?: Options): AstroIntegration {
await fs.promises.mkdir(functionsUrl, { recursive: true }); await fs.promises.mkdir(functionsUrl, { recursive: true });
} }
if (isModeDirectory && _buildConfig.split) { // TODO: remove _buildConfig.split in Astro 4.0
if (isModeDirectory && (_buildConfig.split || functionPerRoute)) {
const entryPointsURL = [..._entryPoints.values()]; const entryPointsURL = [..._entryPoints.values()];
const entryPaths = entryPointsURL.map((entry) => fileURLToPath(entry)); const entryPaths = entryPointsURL.map((entry) => fileURLToPath(entry));
const outputUrl = new URL('$astro', _buildConfig.server); const outputUrl = new URL('$astro', _buildConfig.server);

View file

@ -0,0 +1,15 @@
import { defineConfig } from 'astro/config';
import cloudflare from '@astrojs/cloudflare';
export default defineConfig({
adapter: cloudflare({
mode: 'directory',
functionPerRoute: true
}),
output: 'server',
vite: {
build: {
minify: false,
},
},
});

View file

@ -1,5 +1,5 @@
{ {
"name": "@test/astro-cloudflare-split", "name": "@test/astro-cloudflare-function-per-route",
"version": "0.0.0", "version": "0.0.0",
"private": true, "private": true,
"dependencies": { "dependencies": {

View file

@ -1,25 +1,13 @@
import { loadFixture } from './test-utils.js'; import { loadFixture } from './test-utils.js';
import { expect } from 'chai'; import { expect } from 'chai';
import cloudflare from '../dist/index.js';
/** @type {import('./test-utils').Fixture} */ /** @type {import('./test-utils.js').Fixture} */
describe('Cloudflare SSR split', () => { describe('Cloudflare SSR functionPerRoute', () => {
let fixture; let fixture;
before(async () => { before(async () => {
fixture = await loadFixture({ fixture = await loadFixture({
root: './fixtures/split/', root: './fixtures/function-per-route/',
adapter: cloudflare({ mode: 'directory' }),
output: 'server',
build: {
split: true,
excludeMiddleware: false,
},
vite: {
build: {
minify: false,
},
},
}); });
await fixture.build(); await fixture.build();
}); });

View file

@ -3644,6 +3644,15 @@ importers:
specifier: workspace:* specifier: workspace:*
version: link:../../../../../astro version: link:../../../../../astro
packages/integrations/cloudflare/test/fixtures/function-per-route:
dependencies:
'@astrojs/cloudflare':
specifier: workspace:*
version: link:../../..
astro:
specifier: workspace:*
version: link:../../../../../astro
packages/integrations/cloudflare/test/fixtures/no-output: packages/integrations/cloudflare/test/fixtures/no-output:
dependencies: dependencies:
'@astrojs/cloudflare': '@astrojs/cloudflare':
@ -3680,15 +3689,6 @@ importers:
specifier: workspace:* specifier: workspace:*
version: link:../../../../../astro version: link:../../../../../astro
packages/integrations/cloudflare/test/fixtures/split:
dependencies:
'@astrojs/cloudflare':
specifier: workspace:*
version: link:../../..
astro:
specifier: workspace:*
version: link:../../../../../astro
packages/integrations/cloudflare/test/fixtures/with-solid-js: packages/integrations/cloudflare/test/fixtures/with-solid-js:
dependencies: dependencies:
'@astrojs/cloudflare': '@astrojs/cloudflare':