Skip to content

Commit

Permalink
create vercel edge middleware
Browse files Browse the repository at this point in the history
remove getVercelOutput
  • Loading branch information
lilnasy committed Jan 17, 2024
1 parent 0fa8557 commit 5c7d8ca
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 60 deletions.
2 changes: 0 additions & 2 deletions packages/integrations/vercel/src/lib/fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ export async function getFilesFromFolder(dir: URL) {
return files;
}

export const getVercelOutput = (root: URL) => new URL('./.vercel/output/', root);

/**
* Copies files into a folder keeping the folder structure intact.
* The resulting file tree will start at the common ancestor.
Expand Down
103 changes: 64 additions & 39 deletions packages/integrations/vercel/src/serverless/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import type {
import { AstroError } from 'astro/errors';
import glob from 'fast-glob';
import { basename } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { pathToFileURL } from 'node:url';
import {
getAstroImageConfig,
getDefaultImageConfig,
type DevImageService,
type VercelImageConfig,
} from '../image/shared.js';
import { getVercelOutput, removeDir, writeJson } from '../lib/fs.js';
import { removeDir, writeJson } from '../lib/fs.js';
import { copyDependenciesToFunction } from '../lib/nft.js';
import { getRedirects } from '../lib/redirects.js';
import {
Expand Down Expand Up @@ -111,8 +111,8 @@ export interface VercelServerlessConfig {
export default function vercelServerless({
webAnalytics,
speedInsights,
includeFiles,
excludeFiles = [],
includeFiles: _includeFiles = [],
excludeFiles: _excludeFiles = [],
imageService,
imagesConfig,
devImageService = 'sharp',
Expand All @@ -133,6 +133,7 @@ export default function vercelServerless({
let buildTempFolder: URL;
let serverEntry: string;
let _entryPoints: Map<RouteData, URL>;
let _middlewareEntryPoint: URL | undefined;
// Extra files to be merged with `includeFiles` during build
const extraFilesToInclude: URL[] = [];

Expand Down Expand Up @@ -162,13 +163,12 @@ export default function vercelServerless({
if (command === 'build' && speedInsights?.enabled) {
injectScript('page', 'import "@astrojs/vercel/speed-insights"');
}
const outDir = getVercelOutput(config.root);

updateConfig({
outDir,
outDir: new URL('./.vercel/output/', config.root),
build: {
serverEntry: 'entry.mjs',
client: new URL('./static/', outDir),
server: new URL('./dist/', config.root),
client: new URL('./.vercel/output/static/', config.root),
server: new URL('./.vercel/output/_functions/', config.root),
redirects: false,
},
vite: {
Expand All @@ -195,7 +195,9 @@ export default function vercelServerless({
`\tYou can set functionPerRoute: false to prevent surpassing the limit.\n`
);
}

setAdapter(getAdapter({ functionPerRoute, edgeMiddleware }));

_config = config;
buildTempFolder = config.build.server;
serverEntry = config.build.serverEntry;
Expand All @@ -208,20 +210,7 @@ export default function vercelServerless({
},
'astro:build:ssr': async ({ entryPoints, middlewareEntryPoint }) => {
_entryPoints = entryPoints;
if (middlewareEntryPoint) {
const outPath = fileURLToPath(buildTempFolder);
const vercelEdgeMiddlewareHandlerPath = new URL(
VERCEL_EDGE_MIDDLEWARE_FILE,
_config.srcDir
);
const bundledMiddlewarePath = await generateEdgeMiddleware(
middlewareEntryPoint,
outPath,
vercelEdgeMiddlewareHandlerPath
);
// let's tell the adapter that we need to save this file
extraFilesToInclude.push(bundledMiddlewarePath);
}
_middlewareEntryPoint = middlewareEntryPoint;
},
'astro:build:done': async ({ routes, logger }) => {
// Merge any includes from `vite.assetsInclude
Expand All @@ -240,9 +229,14 @@ export default function vercelServerless({
mergeGlobbedIncludes(_config.vite.assetsInclude);
}

const routeDefinitions: { src: string; dest: string }[] = [];
const filesToInclude = includeFiles?.map((file) => new URL(file, _config.root)) || [];
filesToInclude.push(...extraFilesToInclude);
const routeDefinitions: Array<{
src: string
dest: string
middlewarePath?: string
}> = [];

const includeFiles = _includeFiles.map((file) => new URL(file, _config.root)).concat(extraFilesToInclude);
const excludeFiles = _excludeFiles.map((file) => new URL(file, _config.root));

const runtime = getRuntime(process, logger);

Expand All @@ -267,7 +261,7 @@ export default function vercelServerless({
config: _config,
logger,
NTF_CACHE,
includeFiles: filesToInclude,
includeFiles,
excludeFiles,
maxDuration,
});
Expand All @@ -284,7 +278,7 @@ export default function vercelServerless({
config: _config,
logger,
NTF_CACHE,
includeFiles: filesToInclude,
includeFiles,
excludeFiles,
maxDuration,
});
Expand All @@ -293,9 +287,17 @@ export default function vercelServerless({
routeDefinitions.push({
src: route.pattern.source,
dest: 'render',
middlewarePath: _middlewareEntryPoint ? "_middleware" : undefined
});
}
}
if (_middlewareEntryPoint) {
await createMiddlewareFolder({
functionName: '_middleware',
entry: _middlewareEntryPoint,
config: _config,
});
}
const fourOhFourRoute = routes.find((route) => route.pathname === '/404');
// Output configuration
// https://vercel.com/docs/build-output-api/v3#build-output-configuration
Expand Down Expand Up @@ -345,6 +347,31 @@ export default function vercelServerless({

type Runtime = `nodejs${string}.x`;

interface CreateMiddlewareFolderArgs {
config: AstroConfig
entry: URL
functionName: string
}

async function createMiddlewareFolder({
functionName,
entry,
config,
}: CreateMiddlewareFolderArgs) {
const functionFolder = new URL(`./functions/${functionName}.func/`, config.outDir);

await generateEdgeMiddleware(
entry,
new URL(VERCEL_EDGE_MIDDLEWARE_FILE, config.srcDir),
new URL('./middleware.mjs', functionFolder),
)

await writeJson(new URL(`./.vc-config.json`, functionFolder), {
runtime: 'edge',
entrypoint: 'middleware.mjs',
});
}

interface CreateFunctionFolderArgs {
functionName: string;
runtime: Runtime;
Expand All @@ -353,7 +380,7 @@ interface CreateFunctionFolderArgs {
logger: AstroIntegrationLogger;
NTF_CACHE: any;
includeFiles: URL[];
excludeFiles: string[];
excludeFiles: URL[];
maxDuration: number | undefined;
}

Expand All @@ -379,7 +406,7 @@ async function createFunctionFolder({
entry,
outDir: functionFolder,
includeFiles,
excludeFiles: excludeFiles.map((file) => new URL(file, config.root)),
excludeFiles,
logger,
},
NTF_CACHE
Expand Down Expand Up @@ -430,14 +457,12 @@ function getRuntime(process: NodeJS.Process, logger: AstroIntegrationLogger): Ru
`\tConsider upgrading your local version to 18.\n`
);
return `nodejs${major}.x`;
} else {
logger.warn(
`\n` +
`\tThe local Node.js version (${major}) is not supported by Vercel Serverless Functions.\n` +
`\tYour project will use Node.js 18 as the runtime instead.\n` +
`\tConsider switching your local version to 18.\n`
);
return 'nodejs18.x';
}
return `nodejs${major}.x`;
logger.warn(
`\n` +
`\tThe local Node.js version (${major}) is not supported by Vercel Serverless Functions.\n` +
`\tYour project will use Node.js 18 as the runtime instead.\n` +
`\tConsider switching your local version to 18.\n`
);
return 'nodejs18.x';
}
27 changes: 12 additions & 15 deletions packages/integrations/vercel/src/serverless/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { existsSync } from 'node:fs';
import { join } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { ASTRO_LOCALS_HEADER } from './adapter.js';

Expand All @@ -16,16 +15,12 @@ import { ASTRO_LOCALS_HEADER } from './adapter.js';
*/
export async function generateEdgeMiddleware(
astroMiddlewareEntryPointPath: URL,
outPath: string,
vercelEdgeMiddlewareHandlerPath: URL
vercelEdgeMiddlewareHandlerPath: URL,
outPath: URL,
): Promise<URL> {
const entryPointPathURLAsString = JSON.stringify(
fileURLToPath(astroMiddlewareEntryPointPath).replace(/\\/g, '/')
);

const code = edgeMiddlewareTemplate(entryPointPathURLAsString, vercelEdgeMiddlewareHandlerPath);
const code = edgeMiddlewareTemplate(astroMiddlewareEntryPointPath, vercelEdgeMiddlewareHandlerPath);
// https://vercel.com/docs/concepts/functions/edge-middleware#create-edge-middleware
const bundledFilePath = join(outPath, 'middleware.mjs');
const bundledFilePath = fileURLToPath(outPath);
const esbuild = await import('esbuild');
await esbuild.build({
stdin: {
Expand All @@ -36,7 +31,6 @@ export async function generateEdgeMiddleware(
platform: 'browser',
// https://runtime-keys.proposal.wintercg.org/#edge-light
conditions: ['edge-light', 'worker', 'browser'],
external: ['astro/middleware'],
outfile: bundledFilePath,
allowOverwrite: true,
format: 'esm',
Expand All @@ -46,7 +40,10 @@ export async function generateEdgeMiddleware(
return pathToFileURL(bundledFilePath);
}

function edgeMiddlewareTemplate(middlewarePath: string, vercelEdgeMiddlewareHandlerPath: URL) {
function edgeMiddlewareTemplate(astroMiddlewareEntryPointPath: URL, vercelEdgeMiddlewareHandlerPath: URL) {
const middlewarePath = JSON.stringify(
fileURLToPath(astroMiddlewareEntryPointPath).replace(/\\/g, '/')
);
const filePathEdgeMiddleware = fileURLToPath(vercelEdgeMiddlewareHandlerPath);
let handlerTemplateImport = '';
let handlerTemplateCall = '{}';
Expand All @@ -68,12 +65,12 @@ export default async function middleware(request, context) {
});
ctx.locals = ${handlerTemplateCall};
const next = async () => {
const response = await fetch(url, {
return new Response(null, {
headers: {
${JSON.stringify(ASTRO_LOCALS_HEADER)}: trySerializeLocals(ctx.locals)
'x-middleware-next': '1',
'x-astro-locals': trySerializeLocals(ctx.locals)
}
});
return response;
})
};
return onRequest(ctx, next);
Expand Down
8 changes: 4 additions & 4 deletions packages/integrations/vercel/src/static/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
type DevImageService,
type VercelImageConfig,
} from '../image/shared.js';
import { emptyDir, getVercelOutput, writeJson } from '../lib/fs.js';
import { emptyDir, writeJson } from '../lib/fs.js';
import { isServerLikeOutput } from '../lib/prerender.js';
import { getRedirects } from '../lib/redirects.js';
import {
Expand Down Expand Up @@ -79,7 +79,7 @@ export default function vercelStatic({
if (command === 'build' && speedInsights?.enabled) {
injectScript('page', 'import "@astrojs/vercel/speed-insights"');
}
const outDir = new URL('./static/', getVercelOutput(config.root));
const outDir = new URL('./.vercel/output/static/', config.root);
updateConfig({
outDir,
build: {
Expand Down Expand Up @@ -110,12 +110,12 @@ export default function vercelStatic({
// Ensure to have `.vercel/output` empty.
// This is because, when building to static, outDir = .vercel/output/static/,
// so .vercel/output itself won't get cleaned.
await emptyDir(getVercelOutput(_config.root));
await emptyDir(new URL('./.vercel/output/', _config.root));
},
'astro:build:done': async ({ routes }) => {
// Output configuration
// https://vercel.com/docs/build-output-api/v3#build-output-configuration
await writeJson(new URL(`./config.json`, getVercelOutput(_config.root)), {
await writeJson(new URL('./.vercel/output/config.json', _config.root), {
version: 3,
routes: [
...getRedirects(routes, _config),
Expand Down

0 comments on commit 5c7d8ca

Please sign in to comment.