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:
parent
5df4853bd1
commit
154af8f5ea
10 changed files with 160 additions and 28 deletions
21
.changeset/tricky-snails-poke.md
Normal file
21
.changeset/tricky-snails-poke.md
Normal 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
|
||||
}
|
||||
});
|
||||
```
|
|
@ -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).
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
6
packages/integrations/vercel/test/fixtures/basic/astro.config.mjs
vendored
Normal file
6
packages/integrations/vercel/test/fixtures/basic/astro.config.mjs
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
import vercel from '@astrojs/vercel/serverless';
|
||||
|
||||
export default defineConfig({
|
||||
adapter: vercel()
|
||||
});
|
9
packages/integrations/vercel/test/fixtures/basic/package.json
vendored
Normal file
9
packages/integrations/vercel/test/fixtures/basic/package.json
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"name": "@test/astro-vercel-basic",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@astrojs/vercel": "workspace:*",
|
||||
"astro": "workspace:*"
|
||||
}
|
||||
}
|
8
packages/integrations/vercel/test/fixtures/basic/src/pages/one.astro
vendored
Normal file
8
packages/integrations/vercel/test/fixtures/basic/src/pages/one.astro
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>One</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>One</h1>
|
||||
</body>
|
||||
</html>
|
8
packages/integrations/vercel/test/fixtures/basic/src/pages/two.astro
vendored
Normal file
8
packages/integrations/vercel/test/fixtures/basic/src/pages/two.astro
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Two</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Two</h1>
|
||||
</body>
|
||||
</html>
|
29
packages/integrations/vercel/test/split.test.js
Normal file
29
packages/integrations/vercel/test/split.test.js
Normal 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);
|
||||
})
|
||||
});
|
|
@ -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':
|
||||
|
|
Loading…
Reference in a new issue