summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/tricky-snails-poke.md21
-rw-r--r--packages/integrations/vercel/README.md18
-rw-r--r--packages/integrations/vercel/src/lib/fs.ts2
-rw-r--r--packages/integrations/vercel/src/serverless/adapter.ts78
-rw-r--r--packages/integrations/vercel/test/fixtures/basic/astro.config.mjs6
-rw-r--r--packages/integrations/vercel/test/fixtures/basic/package.json9
-rw-r--r--packages/integrations/vercel/test/fixtures/basic/src/pages/one.astro8
-rw-r--r--packages/integrations/vercel/test/fixtures/basic/src/pages/two.astro8
-rw-r--r--packages/integrations/vercel/test/split.test.js29
-rw-r--r--pnpm-lock.yaml9
10 files changed, 160 insertions, 28 deletions
diff --git a/.changeset/tricky-snails-poke.md b/.changeset/tricky-snails-poke.md
new file mode 100644
index 000000000..815942d1a
--- /dev/null
+++ b/.changeset/tricky-snails-poke.md
@@ -0,0 +1,21 @@
+---
+'@astrojs/vercel': minor
+---
+
+Split support in Vercel Serverless
+
+The Vercel adapter builds to a single function by default. Astro 2.7 added support for splitting your build into separate entry points per page. If you use this configuration the Vercel adapter will generate a separate function for each page. This can help reduce the size of each function so they are only bundling code used on that page.
+
+```js
+// astro.config.mjs
+import { defineConfig } from 'astro/config';
+import vercel from '@astrojs/vercel/serverless';
+
+export default defineConfig({
+ output: 'server',
+ adapter: vercel(),
+ build: {
+ split: true
+ }
+});
+```
diff --git a/packages/integrations/vercel/README.md b/packages/integrations/vercel/README.md
index fd8549839..c32a5f57c 100644
--- a/packages/integrations/vercel/README.md
+++ b/packages/integrations/vercel/README.md
@@ -215,6 +215,24 @@ export default defineConfig({
});
```
+### Per-page functions
+
+The Vercel adapter builds to a single function by default. Astro 2.7 added support for splitting your build into separate entry points per page. If you use this configuration the Vercel adapter will generate a separate function for each page. This can help reduce the size of each function so they are only bundling code used on that page.
+
+```js
+// astro.config.mjs
+import { defineConfig } from 'astro/config';
+import vercel from '@astrojs/vercel/serverless';
+
+export default defineConfig({
+ output: 'server',
+ adapter: vercel(),
+ build: {
+ split: true
+ }
+});
+```
+
### Vercel Middleware
You can use Vercel middleware to intercept a request and redirect before sending a response. Vercel middleware can run for Edge, SSR, and Static deployments. You don't need to install `@vercel/edge` to write middleware, but you do need to install it to use features such as geolocation. For more information see [Vercel’s middleware documentation](https://vercel.com/docs/concepts/functions/edge-middleware).
diff --git a/packages/integrations/vercel/src/lib/fs.ts b/packages/integrations/vercel/src/lib/fs.ts
index 875a0ae9c..18fbe85d2 100644
--- a/packages/integrations/vercel/src/lib/fs.ts
+++ b/packages/integrations/vercel/src/lib/fs.ts
@@ -4,7 +4,7 @@ import nodePath from 'node:path';
import { fileURLToPath } from 'node:url';
export async function writeJson<T>(path: PathLike, data: T) {
- await fs.writeFile(path, JSON.stringify(data), { encoding: 'utf-8' });
+ await fs.writeFile(path, JSON.stringify(data, null, '\t'), { encoding: 'utf-8' });
}
export async function removeDir(dir: PathLike) {
diff --git a/packages/integrations/vercel/src/serverless/adapter.ts b/packages/integrations/vercel/src/serverless/adapter.ts
index 8a1870770..52fb9b34b 100644
--- a/packages/integrations/vercel/src/serverless/adapter.ts
+++ b/packages/integrations/vercel/src/serverless/adapter.ts
@@ -1,4 +1,4 @@
-import type { AstroAdapter, AstroConfig, AstroIntegration } from 'astro';
+import type { AstroAdapter, AstroConfig, AstroIntegration, RouteData } from 'astro';
import glob from 'fast-glob';
import { pathToFileURL } from 'url';
@@ -12,6 +12,7 @@ import { exposeEnv } from '../lib/env.js';
import { getVercelOutput, removeDir, writeJson } from '../lib/fs.js';
import { copyDependenciesToFunction } from '../lib/nft.js';
import { getRedirects } from '../lib/redirects.js';
+import { basename } from 'node:path';
const PACKAGE_NAME = '@astrojs/vercel/serverless';
@@ -40,8 +41,34 @@ export default function vercelServerless({
}: VercelServerlessConfig = {}): AstroIntegration {
let _config: AstroConfig;
let buildTempFolder: URL;
- let functionFolder: URL;
let serverEntry: string;
+ let _entryPoints: Map<RouteData, URL>;
+
+ async function createFunctionFolder(funcName: string, entry: URL, inc: URL[]) {
+ const functionFolder = new URL(`./functions/${funcName}.func/`, _config.outDir);
+
+ // Copy necessary files (e.g. node_modules/)
+ const { handler } = await copyDependenciesToFunction({
+ entry,
+ outDir: functionFolder,
+ includeFiles: inc,
+ excludeFiles: excludeFiles?.map((file) => new URL(file, _config.root)) || [],
+ });
+
+ // Enable ESM
+ // https://aws.amazon.com/blogs/compute/using-node-js-es-modules-and-top-level-await-in-aws-lambda/
+ await writeJson(new URL(`./package.json`, functionFolder), {
+ type: 'module',
+ });
+
+ // Serverless function config
+ // https://vercel.com/docs/build-output-api/v3#vercel-primitives/serverless-functions/configuration
+ await writeJson(new URL(`./.vc-config.json`, functionFolder), {
+ runtime: getRuntime(),
+ handler,
+ launcherType: 'Nodejs',
+ });
+ }
return {
name: PACKAGE_NAME,
@@ -70,7 +97,6 @@ export default function vercelServerless({
setAdapter(getAdapter());
_config = config;
buildTempFolder = config.build.server;
- functionFolder = new URL('./functions/render.func/', config.outDir);
serverEntry = config.build.serverEntry;
if (config.output === 'static') {
@@ -80,6 +106,9 @@ export default function vercelServerless({
`);
}
},
+ 'astro:build:ssr': async ({ entryPoints }) => {
+ _entryPoints = entryPoints;
+ },
'astro:build:done': async ({ routes }) => {
// Merge any includes from `vite.assetsInclude
const inc = includeFiles?.map((file) => new URL(file, _config.root)) || [];
@@ -98,30 +127,22 @@ export default function vercelServerless({
mergeGlobbedIncludes(_config.vite.assetsInclude);
}
- // Copy necessary files (e.g. node_modules/)
- const { handler } = await copyDependenciesToFunction({
- entry: new URL(serverEntry, buildTempFolder),
- outDir: functionFolder,
- includeFiles: inc,
- excludeFiles: excludeFiles?.map((file) => new URL(file, _config.root)) || [],
- });
-
- // Remove temporary folder
- await removeDir(buildTempFolder);
-
- // Enable ESM
- // https://aws.amazon.com/blogs/compute/using-node-js-es-modules-and-top-level-await-in-aws-lambda/
- await writeJson(new URL(`./package.json`, functionFolder), {
- type: 'module',
- });
+ const routeDefinitions: { src: string; dest: string }[] = [];
- // Serverless function config
- // https://vercel.com/docs/build-output-api/v3#vercel-primitives/serverless-functions/configuration
- await writeJson(new URL(`./.vc-config.json`, functionFolder), {
- runtime: getRuntime(),
- handler,
- launcherType: 'Nodejs',
- });
+ // Multiple entrypoint support
+ if(_entryPoints.size) {
+ for(const [route, entryFile] of _entryPoints) {
+ const func = basename(entryFile.toString()).replace(/\.mjs$/, '');
+ await createFunctionFolder(func, entryFile, inc);
+ routeDefinitions.push({
+ src: route.pattern.source,
+ dest: func
+ });
+ }
+ } else {
+ await createFunctionFolder('render', new URL(serverEntry, buildTempFolder), inc);
+ routeDefinitions.push({ src: '/.*', dest: 'render' });
+ }
// Output configuration
// https://vercel.com/docs/build-output-api/v3#build-output-configuration
@@ -130,12 +151,15 @@ export default function vercelServerless({
routes: [
...getRedirects(routes, _config),
{ handle: 'filesystem' },
- { src: '/.*', dest: 'render' },
+ ...routeDefinitions
],
...(imageService || imagesConfig
? { images: imagesConfig ? imagesConfig : defaultImageConfig }
: {}),
});
+
+ // Remove temporary folder
+ await removeDir(buildTempFolder);
},
},
};
diff --git a/packages/integrations/vercel/test/fixtures/basic/astro.config.mjs b/packages/integrations/vercel/test/fixtures/basic/astro.config.mjs
new file mode 100644
index 000000000..664b64d56
--- /dev/null
+++ b/packages/integrations/vercel/test/fixtures/basic/astro.config.mjs
@@ -0,0 +1,6 @@
+import { defineConfig } from 'astro/config';
+import vercel from '@astrojs/vercel/serverless';
+
+export default defineConfig({
+ adapter: vercel()
+});
diff --git a/packages/integrations/vercel/test/fixtures/basic/package.json b/packages/integrations/vercel/test/fixtures/basic/package.json
new file mode 100644
index 000000000..89fb910ff
--- /dev/null
+++ b/packages/integrations/vercel/test/fixtures/basic/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "@test/astro-vercel-basic",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "@astrojs/vercel": "workspace:*",
+ "astro": "workspace:*"
+ }
+}
diff --git a/packages/integrations/vercel/test/fixtures/basic/src/pages/one.astro b/packages/integrations/vercel/test/fixtures/basic/src/pages/one.astro
new file mode 100644
index 000000000..0c7fb90a7
--- /dev/null
+++ b/packages/integrations/vercel/test/fixtures/basic/src/pages/one.astro
@@ -0,0 +1,8 @@
+<html>
+ <head>
+ <title>One</title>
+ </head>
+ <body>
+ <h1>One</h1>
+ </body>
+</html>
diff --git a/packages/integrations/vercel/test/fixtures/basic/src/pages/two.astro b/packages/integrations/vercel/test/fixtures/basic/src/pages/two.astro
new file mode 100644
index 000000000..e7ba9910e
--- /dev/null
+++ b/packages/integrations/vercel/test/fixtures/basic/src/pages/two.astro
@@ -0,0 +1,8 @@
+<html>
+ <head>
+ <title>Two</title>
+ </head>
+ <body>
+ <h1>Two</h1>
+ </body>
+</html>
diff --git a/packages/integrations/vercel/test/split.test.js b/packages/integrations/vercel/test/split.test.js
new file mode 100644
index 000000000..b89a428be
--- /dev/null
+++ b/packages/integrations/vercel/test/split.test.js
@@ -0,0 +1,29 @@
+import { loadFixture } from './test-utils.js';
+import { expect } from 'chai';
+
+describe('build: split', () => {
+ /** @type {import('./test-utils').Fixture} */
+ let fixture;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/basic/',
+ output: 'server',
+ build: {
+ split: true,
+ }
+ });
+ await fixture.build();
+ });
+
+ it('creates separate functions for each page', async () => {
+ const files = await fixture.readdir('../.vercel/output/functions/')
+ expect(files.length).to.equal(2);
+ });
+
+ it('creates the route definitions in the config.json', async () => {
+ const json = await fixture.readFile('../.vercel/output/config.json');
+ const config = JSON.parse(json);
+ expect(config.routes).to.have.a.lengthOf(3);
+ })
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 76aae821f..c5649d326 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -4902,6 +4902,15 @@ importers:
specifier: ^9.2.2
version: 9.2.2
+ packages/integrations/vercel/test/fixtures/basic:
+ dependencies:
+ '@astrojs/vercel':
+ specifier: workspace:*
+ version: link:../../..
+ astro:
+ specifier: workspace:*
+ version: link:../../../../../astro
+
packages/integrations/vercel/test/fixtures/image:
dependencies:
'@astrojs/vercel':