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:
parent
bbf0b7470b
commit
2540feedb0
20 changed files with 101 additions and 38 deletions
5
.changeset/nasty-garlics-listen.md
Normal file
5
.changeset/nasty-garlics-listen.md
Normal 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.
|
23
.changeset/wise-cameras-agree.md
Normal file
23
.changeset/wise-cameras-agree.md
Normal 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
|
||||||
|
+ })
|
||||||
|
})
|
||||||
|
```
|
|
@ -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) {
|
||||||
|
|
|
@ -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`
|
||||||
|
|
|
@ -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);
|
||||||
|
|
15
packages/integrations/cloudflare/test/fixtures/function-per-route/astro.config.mjs
vendored
Normal file
15
packages/integrations/cloudflare/test/fixtures/function-per-route/astro.config.mjs
vendored
Normal 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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -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": {
|
|
@ -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();
|
||||||
});
|
});
|
|
@ -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':
|
||||||
|
|
Loading…
Reference in a new issue