0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2024-12-30 22:03:56 -05:00

Split support in the Vercel Serverless adapter (#7514)

* start of vercel split support

* Split Mode with the Vercel Adapter

* Write routes into the config.json

* Add a changeset

* Add docs

* Better changeset
This commit is contained in:
Matthew Phillips 2023-06-29 16:18:28 -04:00 committed by GitHub
parent 5df4853bd1
commit 154af8f5ea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 160 additions and 28 deletions

View file

@ -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
}
});
```

View file

@ -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 [Vercels middleware documentation](https://vercel.com/docs/concepts/functions/edge-middleware).

View file

@ -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) {

View file

@ -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)) || [],
});
const routeDefinitions: { src: string; dest: string }[] = [];
// 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',
});
// 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);
},
},
};

View file

@ -0,0 +1,6 @@
import { defineConfig } from 'astro/config';
import vercel from '@astrojs/vercel/serverless';
export default defineConfig({
adapter: vercel()
});

View file

@ -0,0 +1,9 @@
{
"name": "@test/astro-vercel-basic",
"version": "0.0.0",
"private": true,
"dependencies": {
"@astrojs/vercel": "workspace:*",
"astro": "workspace:*"
}
}

View file

@ -0,0 +1,8 @@
<html>
<head>
<title>One</title>
</head>
<body>
<h1>One</h1>
</body>
</html>

View file

@ -0,0 +1,8 @@
<html>
<head>
<title>Two</title>
</head>
<body>
<h1>Two</h1>
</body>
</html>

View file

@ -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);
})
});

View file

@ -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':