Skip to content

Commit

Permalink
feature(@astrojs/cloudflare): port functionPerRoute (#8078)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
4 people committed Aug 17, 2023
1 parent bbf0b74 commit 2540fee
Show file tree
Hide file tree
Showing 20 changed files with 97 additions and 34 deletions.
5 changes: 5 additions & 0 deletions .changeset/nasty-garlics-listen.md
Original file line number Diff line number Diff line change
@@ -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 changes: 23 additions & 0 deletions .changeset/wise-cameras-agree.md
Original file line number Diff line number Diff line change
@@ -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
+ })
})
```
6 changes: 5 additions & 1 deletion packages/astro/src/core/build/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,11 @@ export async function generatePages(opts: StaticBuildOptions, internals: BuildIn
if (pageData.route.prerender) {
const ssrEntryURLPage = createEntryURL(filePath, outFolder);
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.
const ssrEntry = ssrEntryPage?.manifest?.pageModule;
if (ssrEntry) {
Expand Down
30 changes: 22 additions & 8 deletions packages/integrations/cloudflare/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,23 +44,37 @@ export default defineConfig({

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.

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.
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.

```ts
// directory mode
// astro.config.mjs
export default defineConfig({
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

In order for preview to work you must install `wrangler`
Expand Down
22 changes: 18 additions & 4 deletions packages/integrations/cloudflare/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import glob from 'tiny-glob';

type Options = {
mode: 'directory' | 'advanced';
functionPerRoute?: boolean;
};

interface BuildConfig {
Expand All @@ -18,12 +19,22 @@ interface BuildConfig {
split?: boolean;
}

export function getAdapter(isModeDirectory: boolean): AstroAdapter {
export function getAdapter({
isModeDirectory,
functionPerRoute,
}: {
isModeDirectory: boolean;
functionPerRoute: boolean;
}): AstroAdapter {
return isModeDirectory
? {
name: '@astrojs/cloudflare',
serverEntrypoint: '@astrojs/cloudflare/server.directory.js',
exports: ['onRequest', 'manifest'],
adapterFeatures: {
functionPerRoute,
edgeMiddleware: false,
},
supportedAstroFeatures: {
hybridOutput: 'stable',
staticOutput: 'unsupported',
Expand Down Expand Up @@ -67,9 +78,11 @@ const potentialFunctionRouteTypes = ['endpoint', 'page'];
export default function createIntegration(args?: Options): AstroIntegration {
let _config: AstroConfig;
let _buildConfig: BuildConfig;
const isModeDirectory = args?.mode === 'directory';
let _entryPoints = new Map<RouteData, URL>();

const isModeDirectory = args?.mode === 'directory';
const functionPerRoute = args?.functionPerRoute ?? false;

return {
name: '@astrojs/cloudflare',
hooks: {
Expand All @@ -84,7 +97,7 @@ export default function createIntegration(args?: Options): AstroIntegration {
});
},
'astro:config:done': ({ setAdapter, config }) => {
setAdapter(getAdapter(isModeDirectory));
setAdapter(getAdapter({ isModeDirectory, functionPerRoute }));
_config = config;
_buildConfig = config.build;

Expand Down Expand Up @@ -136,7 +149,8 @@ export default function createIntegration(args?: Options): AstroIntegration {
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 entryPaths = entryPointsURL.map((entry) => fileURLToPath(entry));
const outputUrl = new URL('$astro', _buildConfig.server);
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
},
},
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@test/astro-cloudflare-split",
"name": "@test/astro-cloudflare-function-per-route",
"version": "0.0.0",
"private": true,
"dependencies": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,13 @@
import { loadFixture } from './test-utils.js';
import { expect } from 'chai';
import cloudflare from '../dist/index.js';

/** @type {import('./test-utils').Fixture} */
describe('Cloudflare SSR split', () => {
/** @type {import('./test-utils.js').Fixture} */
describe('Cloudflare SSR functionPerRoute', () => {
let fixture;

before(async () => {
fixture = await loadFixture({
root: './fixtures/split/',
adapter: cloudflare({ mode: 'directory' }),
output: 'server',
build: {
split: true,
excludeMiddleware: false,
},
vite: {
build: {
minify: false,
},
},
root: './fixtures/function-per-route/',
});
await fixture.build();
});
Expand Down
10 changes: 5 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 2540fee

Please sign in to comment.