summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Alexander Niebuhr <alexander@nbhr.io> 2023-06-30 11:09:21 +0200
committerGravatar GitHub <noreply@github.com> 2023-06-30 10:09:21 +0100
commit1a59185ddd393bf8894ec0c981b26d6fecdb3c67 (patch)
tree388bbb6d419f40c5527bd7469937a735a1f5296e
parent7ae6e892921fd47c630b289a28d8100414f861e2 (diff)
downloadastro-1a59185ddd393bf8894ec0c981b26d6fecdb3c67.tar.gz
astro-1a59185ddd393bf8894ec0c981b26d6fecdb3c67.tar.zst
astro-1a59185ddd393bf8894ec0c981b26d6fecdb3c67.zip
feature(astrojs/cloudflare): add support for `splitted` SSR bundles (#7464)
* initial commit * try to fix windows * output files directly into the correct folder * allow for rest parameters * use fixed hook * improve tests * apply doc's team suggestions for README Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * try to fix prerendering * apply doc's team suggestion for changeset Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * bump to minor * readme update * resolve review comments * optimize memory allocation * resolve review comments * add removed link, to make sure old docs keep same * resolve comment Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> --------- Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> Co-authored-by: Chris Swithinbank <swithinbank@gmail.com>
Diffstat (limited to '')
-rw-r--r--.changeset/healthy-books-study.md7
-rw-r--r--packages/integrations/cloudflare/README.md6
-rw-r--r--packages/integrations/cloudflare/package.json3
-rw-r--r--packages/integrations/cloudflare/src/index.ts145
-rw-r--r--packages/integrations/cloudflare/src/server.directory.ts2
-rw-r--r--packages/integrations/cloudflare/test/directory-split.test.js44
-rw-r--r--packages/integrations/cloudflare/test/fixtures/split/package.json9
-rw-r--r--packages/integrations/cloudflare/test/fixtures/split/src/middleware.ts10
-rw-r--r--packages/integrations/cloudflare/test/fixtures/split/src/pages/[language]/files/[...path].astro37
-rw-r--r--packages/integrations/cloudflare/test/fixtures/split/src/pages/[person]/[car].astro14
-rw-r--r--packages/integrations/cloudflare/test/fixtures/split/src/pages/blog/[post].astro14
-rw-r--r--packages/integrations/cloudflare/test/fixtures/split/src/pages/blog/cool.astro11
-rw-r--r--packages/integrations/cloudflare/test/fixtures/split/src/pages/files/[...path].astro37
-rw-r--r--packages/integrations/cloudflare/test/fixtures/split/src/pages/index.astro22
-rw-r--r--packages/integrations/cloudflare/test/fixtures/split/src/pages/prerender.astro14
-rw-r--r--pnpm-lock.yaml9
16 files changed, 338 insertions, 46 deletions
diff --git a/.changeset/healthy-books-study.md b/.changeset/healthy-books-study.md
new file mode 100644
index 000000000..e4b51b041
--- /dev/null
+++ b/.changeset/healthy-books-study.md
@@ -0,0 +1,7 @@
+---
+'@astrojs/cloudflare': minor
+---
+
+Split Support in Cloudflare
+
+Adds support for configuring `build.split` when using the Cloudflare adapter
diff --git a/packages/integrations/cloudflare/README.md b/packages/integrations/cloudflare/README.md
index 6f69e3bdd..a6638f36d 100644
--- a/packages/integrations/cloudflare/README.md
+++ b/packages/integrations/cloudflare/README.md
@@ -48,7 +48,11 @@ Cloudflare Pages has 2 different modes for deploying functions, `advanced` mode
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.
-In directory mode the adapter will compile the client side part of your app the same way, but moves the worker script into a `functions` folder in the project root. 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. Cloudflare documentation contains more information about [writing custom functions](https://developers.cloudflare.com/pages/platform/functions/).
+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.
```ts
// directory mode
diff --git a/packages/integrations/cloudflare/package.json b/packages/integrations/cloudflare/package.json
index fe64ee166..f4ad834a0 100644
--- a/packages/integrations/cloudflare/package.json
+++ b/packages/integrations/cloudflare/package.json
@@ -35,7 +35,8 @@
"build": "astro-scripts build \"src/**/*.ts\" && tsc",
"build:ci": "astro-scripts build \"src/**/*.ts\"",
"dev": "astro-scripts dev \"src/**/*.ts\"",
- "test": "mocha --exit --timeout 30000 test/"
+ "test": "mocha --exit --timeout 30000 test/",
+ "test:match": "mocha --exit --timeout 30000 -g"
},
"dependencies": {
"@astrojs/underscore-redirects": "^0.1.0",
diff --git a/packages/integrations/cloudflare/src/index.ts b/packages/integrations/cloudflare/src/index.ts
index 252dd778a..f4ee26fea 100644
--- a/packages/integrations/cloudflare/src/index.ts
+++ b/packages/integrations/cloudflare/src/index.ts
@@ -1,8 +1,9 @@
import { createRedirectsFromAstroRoutes } from '@astrojs/underscore-redirects';
-import type { AstroAdapter, AstroConfig, AstroIntegration } from 'astro';
+import type { AstroAdapter, AstroConfig, AstroIntegration, RouteData } from 'astro';
import esbuild from 'esbuild';
import * as fs from 'fs';
import * as os from 'os';
+import { dirname } from 'path';
import glob from 'tiny-glob';
import { fileURLToPath, pathToFileURL } from 'url';
@@ -14,20 +15,21 @@ interface BuildConfig {
server: URL;
client: URL;
serverEntry: string;
+ split?: boolean;
}
export function getAdapter(isModeDirectory: boolean): AstroAdapter {
return isModeDirectory
? {
- name: '@astrojs/cloudflare',
- serverEntrypoint: '@astrojs/cloudflare/server.directory.js',
- exports: ['onRequest'],
- }
+ name: '@astrojs/cloudflare',
+ serverEntrypoint: '@astrojs/cloudflare/server.directory.js',
+ exports: ['onRequest', 'manifest'],
+ }
: {
- name: '@astrojs/cloudflare',
- serverEntrypoint: '@astrojs/cloudflare/server.advanced.js',
- exports: ['default'],
- };
+ name: '@astrojs/cloudflare',
+ serverEntrypoint: '@astrojs/cloudflare/server.advanced.js',
+ exports: ['default'],
+ };
}
const SHIM = `globalThis.process = {
@@ -41,6 +43,7 @@ export default function createIntegration(args?: Options): AstroIntegration {
let _config: AstroConfig;
let _buildConfig: BuildConfig;
const isModeDirectory = args?.mode === 'directory';
+ let _entryPoints = new Map<RouteData, URL>();
return {
name: '@astrojs/cloudflare',
@@ -90,35 +93,99 @@ export default function createIntegration(args?: Options): AstroIntegration {
vite.ssr.target = 'webworker';
}
},
+ 'astro:build:ssr': ({ manifest, entryPoints }) => {
+ _entryPoints = entryPoints;
+ },
'astro:build:done': async ({ pages, routes, dir }) => {
- const entryPath = fileURLToPath(new URL(_buildConfig.serverEntry, _buildConfig.server));
- const entryUrl = new URL(_buildConfig.serverEntry, _config.outDir);
- const buildPath = fileURLToPath(entryUrl);
- // A URL for the final build path after renaming
- const finalBuildUrl = pathToFileURL(buildPath.replace(/\.mjs$/, '.js'));
-
- await esbuild.build({
- target: 'es2020',
- platform: 'browser',
- conditions: ['workerd', 'worker', 'browser'],
- entryPoints: [entryPath],
- outfile: buildPath,
- allowOverwrite: true,
- format: 'esm',
- bundle: true,
- minify: _config.vite?.build?.minify !== false,
- banner: {
- js: SHIM,
- },
- logOverride: {
- 'ignored-bare-import': 'silent',
- },
- });
+ const functionsUrl = new URL('functions/', _config.root);
+
+ if (isModeDirectory) {
+ await fs.promises.mkdir(functionsUrl, { recursive: true });
+ }
+
+ if (isModeDirectory && _buildConfig.split) {
+ const entryPointsRouteData = [..._entryPoints.keys()]
+ const entryPointsURL = [..._entryPoints.values()]
+ const entryPaths = entryPointsURL.map((entry) => fileURLToPath(entry));
+ const outputDir = fileURLToPath(new URL('.astro', _buildConfig.server));
+
+ // NOTE: AFAIK, esbuild keeps the order of the entryPoints array
+ const { outputFiles } = await esbuild.build({
+ target: 'es2020',
+ platform: 'browser',
+ conditions: ['workerd', 'worker', 'browser'],
+ entryPoints: entryPaths,
+ outdir: outputDir,
+ allowOverwrite: true,
+ format: 'esm',
+ bundle: true,
+ minify: _config.vite?.build?.minify !== false,
+ banner: {
+ js: SHIM,
+ },
+ logOverride: {
+ 'ignored-bare-import': 'silent',
+ },
+ write: false,
+ });
- // Rename to worker.js
- await fs.promises.rename(buildPath, finalBuildUrl);
+ // loop through all bundled files and write them to the functions folder
+ for (const [index, outputFile] of outputFiles.entries()) {
+ // we need to make sure the filename in the functions folder
+ // matches to cloudflares routing capabilities (see their docs)
+ // IN: src/pages/[language]/files/[...path].astro
+ // OUT: [language]/files/[[path]].js
+ const fileName = entryPointsRouteData[index].component
+ .replace('src/pages/', '')
+ .replace('.astro', '.js')
+ .replace(/(\[\.\.\.)(\w+)(\])/g, (_match, _p1, p2, _p3) => {
+ return `[[${p2}]]`;
+ });
+
+ const fileUrl = new URL(fileName, functionsUrl)
+ const newFileDir = dirname(fileURLToPath(fileUrl));
+ if (!fs.existsSync(newFileDir)) {
+ fs.mkdirSync(newFileDir, { recursive: true });
+ }
+ await fs.promises.writeFile(fileUrl, outputFile.contents);
+ }
- // throw the server folder in the bin
+ } else {
+ const entryPath = fileURLToPath(new URL(_buildConfig.serverEntry, _buildConfig.server));
+ const entryUrl = new URL(_buildConfig.serverEntry, _config.outDir);
+ const buildPath = fileURLToPath(entryUrl);
+ // A URL for the final build path after renaming
+ const finalBuildUrl = pathToFileURL(buildPath.replace(/\.mjs$/, '.js'));
+
+ await esbuild.build({
+ target: 'es2020',
+ platform: 'browser',
+ conditions: ['workerd', 'worker', 'browser'],
+ entryPoints: [entryPath],
+ outfile: buildPath,
+ allowOverwrite: true,
+ format: 'esm',
+ bundle: true,
+ minify: _config.vite?.build?.minify !== false,
+ banner: {
+ js: SHIM,
+ },
+ logOverride: {
+ 'ignored-bare-import': 'silent',
+ },
+ });
+
+ // Rename to worker.js
+ await fs.promises.rename(buildPath, finalBuildUrl);
+
+ if (isModeDirectory) {
+ const directoryUrl = new URL('[[path]].js', functionsUrl);
+ await fs.promises.rename(finalBuildUrl, directoryUrl);
+ }
+
+ }
+
+ // // // throw the server folder in the bin
const serverUrl = new URL(_buildConfig.server);
await fs.promises.rm(serverUrl, { recursive: true, force: true });
@@ -225,14 +292,6 @@ export default function createIntegration(args?: Options): AstroIntegration {
)
);
}
-
- if (isModeDirectory) {
- const functionsUrl = new URL('functions/', _config.root);
- await fs.promises.mkdir(functionsUrl, { recursive: true });
-
- const directoryUrl = new URL('[[path]].js', functionsUrl);
- await fs.promises.rename(finalBuildUrl, directoryUrl);
- }
},
},
};
diff --git a/packages/integrations/cloudflare/src/server.directory.ts b/packages/integrations/cloudflare/src/server.directory.ts
index da2360557..f9f71a33b 100644
--- a/packages/integrations/cloudflare/src/server.directory.ts
+++ b/packages/integrations/cloudflare/src/server.directory.ts
@@ -61,5 +61,5 @@ export function createExports(manifest: SSRManifest) {
});
};
- return { onRequest };
+ return { onRequest, manifest };
}
diff --git a/packages/integrations/cloudflare/test/directory-split.test.js b/packages/integrations/cloudflare/test/directory-split.test.js
new file mode 100644
index 000000000..8bb6cd872
--- /dev/null
+++ b/packages/integrations/cloudflare/test/directory-split.test.js
@@ -0,0 +1,44 @@
+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', () => {
+ 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,
+ },
+ },
+ });
+ await fixture.build();
+ });
+
+ after(() => {
+ fixture.clean();
+ });
+
+ it('generates functions folders inside the project root, and checks that each page is emitted by astro', async () => {
+ expect(await fixture.pathExists('../functions')).to.be.true;
+ expect(await fixture.pathExists('../functions/index.js')).to.be.true;
+ expect(await fixture.pathExists('../functions/blog/cool.js')).to.be.true;
+ expect(await fixture.pathExists('../functions/blog/[post].js')).to.be.true;
+ expect(await fixture.pathExists('../functions/[person]/[car].js')).to.be.true;
+ expect(await fixture.pathExists('../functions/files/[[path]].js')).to.be.true;
+ expect(await fixture.pathExists('../functions/[language]/files/[[path]].js')).to.be.true;
+ });
+
+ it('generates pre-rendered files', async () => {
+ expect(await fixture.pathExists('./prerender/index.html')).to.be.true;
+ });
+});
diff --git a/packages/integrations/cloudflare/test/fixtures/split/package.json b/packages/integrations/cloudflare/test/fixtures/split/package.json
new file mode 100644
index 000000000..fd7dcc253
--- /dev/null
+++ b/packages/integrations/cloudflare/test/fixtures/split/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "@test/astro-cloudflare-split",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "@astrojs/cloudflare": "workspace:*",
+ "astro": "workspace:*"
+ }
+}
diff --git a/packages/integrations/cloudflare/test/fixtures/split/src/middleware.ts b/packages/integrations/cloudflare/test/fixtures/split/src/middleware.ts
new file mode 100644
index 000000000..a6ce640cb
--- /dev/null
+++ b/packages/integrations/cloudflare/test/fixtures/split/src/middleware.ts
@@ -0,0 +1,10 @@
+import { defineMiddleware } from "astro/middleware";
+
+export const onRequest = defineMiddleware(({ locals, request }, next) => {
+ // intercept response data from a request
+ // optionally, transform the response by modifying `locals`
+ locals.title = "New title"
+
+ // return a Response or the result of calling `next()`
+ return next()
+});
diff --git a/packages/integrations/cloudflare/test/fixtures/split/src/pages/[language]/files/[...path].astro b/packages/integrations/cloudflare/test/fixtures/split/src/pages/[language]/files/[...path].astro
new file mode 100644
index 000000000..84ad53228
--- /dev/null
+++ b/packages/integrations/cloudflare/test/fixtures/split/src/pages/[language]/files/[...path].astro
@@ -0,0 +1,37 @@
+---
+const files = [
+ {
+ slug: undefined,
+ title: 'Root level',
+ },
+ {
+ slug: 'test.png',
+ title: "One level"
+ },
+ {
+ slug: 'assets/test.png',
+ title: "Two levels"
+ },
+ {
+ slug: 'assets/images/test.png',
+ title: 'Three levels',
+ }
+];
+
+const { path } = Astro.params;
+const page = files.find((page) => page.slug === path);
+const { title } = page;
+
+---
+<html>
+ <body>
+ <h1>Files / Rest Parameters / {title}</h1>
+ <p>DEBUG: {path} </p>
+ <p><a href="/">index</a></p>
+ </body>
+ <style>
+ h1 {
+ background-color: yellow;
+ }
+ </style>
+</html>
diff --git a/packages/integrations/cloudflare/test/fixtures/split/src/pages/[person]/[car].astro b/packages/integrations/cloudflare/test/fixtures/split/src/pages/[person]/[car].astro
new file mode 100644
index 000000000..f4fda9dc5
--- /dev/null
+++ b/packages/integrations/cloudflare/test/fixtures/split/src/pages/[person]/[car].astro
@@ -0,0 +1,14 @@
+---
+const { person, car } = Astro.params;
+---
+<html>
+ <body>
+ <h1> {person} / {car}</h1>
+ <p><a href="/">index</a></p>
+ </body>
+ <style>
+ h1 {
+ background-color: blue;
+ }
+ </style>
+</html>
diff --git a/packages/integrations/cloudflare/test/fixtures/split/src/pages/blog/[post].astro b/packages/integrations/cloudflare/test/fixtures/split/src/pages/blog/[post].astro
new file mode 100644
index 000000000..7b0e1e5b8
--- /dev/null
+++ b/packages/integrations/cloudflare/test/fixtures/split/src/pages/blog/[post].astro
@@ -0,0 +1,14 @@
+---
+const { post } = Astro.params;
+---
+<html>
+ <body>
+ <h1>Blog / {post}</h1>
+ <p><a href="/">index</a></p>
+ </body>
+ <style>
+ h1 {
+ background-color: pink;
+ }
+ </style>
+</html>
diff --git a/packages/integrations/cloudflare/test/fixtures/split/src/pages/blog/cool.astro b/packages/integrations/cloudflare/test/fixtures/split/src/pages/blog/cool.astro
new file mode 100644
index 000000000..7127282a4
--- /dev/null
+++ b/packages/integrations/cloudflare/test/fixtures/split/src/pages/blog/cool.astro
@@ -0,0 +1,11 @@
+<html>
+ <body>
+ <h1>Blog / Cool</h1>
+ <p><a href="/">index</a></p>
+ </body>
+ <style>
+ h1 {
+ background-color: orange;
+ }
+ </style>
+</html>
diff --git a/packages/integrations/cloudflare/test/fixtures/split/src/pages/files/[...path].astro b/packages/integrations/cloudflare/test/fixtures/split/src/pages/files/[...path].astro
new file mode 100644
index 000000000..84ad53228
--- /dev/null
+++ b/packages/integrations/cloudflare/test/fixtures/split/src/pages/files/[...path].astro
@@ -0,0 +1,37 @@
+---
+const files = [
+ {
+ slug: undefined,
+ title: 'Root level',
+ },
+ {
+ slug: 'test.png',
+ title: "One level"
+ },
+ {
+ slug: 'assets/test.png',
+ title: "Two levels"
+ },
+ {
+ slug: 'assets/images/test.png',
+ title: 'Three levels',
+ }
+];
+
+const { path } = Astro.params;
+const page = files.find((page) => page.slug === path);
+const { title } = page;
+
+---
+<html>
+ <body>
+ <h1>Files / Rest Parameters / {title}</h1>
+ <p>DEBUG: {path} </p>
+ <p><a href="/">index</a></p>
+ </body>
+ <style>
+ h1 {
+ background-color: yellow;
+ }
+ </style>
+</html>
diff --git a/packages/integrations/cloudflare/test/fixtures/split/src/pages/index.astro b/packages/integrations/cloudflare/test/fixtures/split/src/pages/index.astro
new file mode 100644
index 000000000..a7f564046
--- /dev/null
+++ b/packages/integrations/cloudflare/test/fixtures/split/src/pages/index.astro
@@ -0,0 +1,22 @@
+---
+const data = Astro.locals;
+---
+<html>
+ <body>
+ <h1>Index</h1>
+ <p>Middleware ({data.title})</p>
+ <p><a href="/prerender/">prerender</a></p>
+ <p><a href="/blog/cool/">sub-route</a></p>
+ <p><a href="/blog/dynamic-post/">dynamic route in static sub-route</a></p>
+ <p><a href="/mustermann/bmw/">dynamic route in dynamic sub-route</a></p>
+ <p><a href="/files/">rest parameters root level</a></p>
+ <p><a href="/files/test.png/">rest parameters one level</a></p>
+ <p><a href="/files/assets/test.png/">rest parameters two level</a></p>
+ <p><a href="/files/assets/images/test.png/">rest parameters three level</a></p>
+ </body>
+ <style>
+ h1 {
+ background-color: red;
+ }
+ </style>
+</html>
diff --git a/packages/integrations/cloudflare/test/fixtures/split/src/pages/prerender.astro b/packages/integrations/cloudflare/test/fixtures/split/src/pages/prerender.astro
new file mode 100644
index 000000000..bdda9b12c
--- /dev/null
+++ b/packages/integrations/cloudflare/test/fixtures/split/src/pages/prerender.astro
@@ -0,0 +1,14 @@
+---
+export const prerender = true;
+---
+<html>
+ <body>
+ <h1>Prerender</h1>
+ <p><a href="/">index</a></p>
+ </body>
+ <style>
+ h1 {
+ background-color: yellow;
+ }
+ </style>
+</html>
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c5649d326..ee0f3c091 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -3675,6 +3675,15 @@ importers:
specifier: workspace:*
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:
dependencies:
'@astrojs/cloudflare':