From 84032dee4b5473fb06bcc17ddeaeb945d1a82b0d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 11 Apr 2022 12:32:57 -0400 Subject: [PATCH 01/13] start working on v3 filesystem API --- packages/adapter-vercel/index.js | 55 +++++++++++++++++++------------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/packages/adapter-vercel/index.js b/packages/adapter-vercel/index.js index 7ed513d8eb9e..6d4b0b59c678 100644 --- a/packages/adapter-vercel/index.js +++ b/packages/adapter-vercel/index.js @@ -3,7 +3,7 @@ import { posix } from 'path'; import { fileURLToPath } from 'url'; import esbuild from 'esbuild'; -const dir = '.vercel_build_output'; +const dir = '.vercel/output'; // rules for clean URLs and trailing slash handling, // generated with @vercel/routing-utils @@ -97,7 +97,7 @@ export default function ({ external = [] } = {}) { const dirs = { static: `${dir}/static`, - lambda: `${dir}/functions/node/render` + lambda: `${dir}/functions/render.func` }; builder.log.minor('Generating serverless function...'); @@ -124,9 +124,18 @@ export default function ({ external = [] } = {}) { target: 'node14', bundle: true, platform: 'node', + format: 'esm', external }); + writeFileSync( + `${dirs.lambda}/.vc-config.json`, + JSON.stringify({ + runtime: 'nodejs14.x', + handler: 'handle' + }) + ); + writeFileSync(`${dirs.lambda}/package.json`, JSON.stringify({ type: 'commonjs' })); builder.log.minor('Copying assets...'); @@ -137,8 +146,6 @@ export default function ({ external = [] } = {}) { builder.log.minor('Writing routes...'); - builder.mkdirp(`${dir}/config`); - const prerendered_pages = Array.from(builder.prerendered.pages, ([src, page]) => ({ src, dest: page.file @@ -156,25 +163,29 @@ export default function ({ external = [] } = {}) { ); writeFileSync( - `${dir}/config/routes.json`, - JSON.stringify([ - ...redirects[builder.config.kit.trailingSlash], - ...prerendered_pages, - ...prerendered_redirects, - { - src: `/${builder.config.kit.appDir}/.+`, - headers: { - 'cache-control': 'public, immutable, max-age=31536000' + `${dir}/config.json`, + JSON.stringify({ + version: 3, + target: 'production', + routes: [ + ...redirects[builder.config.kit.trailingSlash], + ...prerendered_pages, + ...prerendered_redirects, + { + src: `/${builder.config.kit.appDir}/.+`, + headers: { + 'cache-control': 'public, immutable, max-age=31536000' + } + }, + { + handle: 'filesystem' + }, + { + src: '/.*', + dest: 'render' } - }, - { - handle: 'filesystem' - }, - { - src: '/.*', - dest: '.vercel/functions/render' - } - ]) + ] + }) ); } }; From f5a0b181f1eded1e63cc37bfa036f36487622c6d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 19 Apr 2022 14:18:10 -0400 Subject: [PATCH 02/13] fix --- packages/adapter-vercel/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/adapter-vercel/index.js b/packages/adapter-vercel/index.js index 6d4b0b59c678..2a40519296c7 100644 --- a/packages/adapter-vercel/index.js +++ b/packages/adapter-vercel/index.js @@ -124,7 +124,7 @@ export default function ({ external = [] } = {}) { target: 'node14', bundle: true, platform: 'node', - format: 'esm', + format: 'cjs', external }); @@ -132,7 +132,8 @@ export default function ({ external = [] } = {}) { `${dirs.lambda}/.vc-config.json`, JSON.stringify({ runtime: 'nodejs14.x', - handler: 'handle' + handler: 'index.js', + launcherType: 'Nodejs' }) ); From 12f0c854fa0003ad6074ba21b85aef4f64302664 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 19 Apr 2022 14:25:05 -0400 Subject: [PATCH 03/13] support v1 and v3 --- packages/adapter-vercel/index.js | 295 +++++++++++++++++++++---------- 1 file changed, 199 insertions(+), 96 deletions(-) diff --git a/packages/adapter-vercel/index.js b/packages/adapter-vercel/index.js index 2a40519296c7..8c6eae5a458c 100644 --- a/packages/adapter-vercel/index.js +++ b/packages/adapter-vercel/index.js @@ -3,8 +3,6 @@ import { posix } from 'path'; import { fileURLToPath } from 'url'; import esbuild from 'esbuild'; -const dir = '.vercel/output'; - // rules for clean URLs and trailing slash handling, // generated with @vercel/routing-utils const redirects = { @@ -88,106 +86,211 @@ export default function ({ external = [] } = {}) { name: '@sveltejs/adapter-vercel', async adapt(builder) { - const tmp = builder.getBuildDirectory('vercel-tmp'); + if (process.env.ENABLE_VC_BUILD) { + await v3(builder, external); + } else { + await v1(builder, external); + } + } + }; +} - builder.rimraf(dir); - builder.rimraf(tmp); +/** + * @param {import('@sveltejs/kit').Builder} builder + * @param {string[]} external + */ +async function v1(builder, external) { + const dir = '.vercel_build_output'; - const files = fileURLToPath(new URL('./files', import.meta.url).href); + const tmp = builder.getBuildDirectory('vercel-tmp'); - const dirs = { - static: `${dir}/static`, - lambda: `${dir}/functions/render.func` - }; + builder.rimraf(dir); + builder.rimraf(tmp); - builder.log.minor('Generating serverless function...'); + const files = fileURLToPath(new URL('./files', import.meta.url).href); - const relativePath = posix.relative(tmp, builder.getServerDirectory()); + const dirs = { + static: `${dir}/static`, + lambda: `${dir}/functions/node/render` + }; - builder.copy(files, tmp, { - replace: { - SERVER: `${relativePath}/index.js`, - MANIFEST: './manifest.js' - } - }); - - writeFileSync( - `${tmp}/manifest.js`, - `export const manifest = ${builder.generateManifest({ - relativePath - })};\n` - ); - - await esbuild.build({ - entryPoints: [`${tmp}/entry.js`], - outfile: `${dirs.lambda}/index.js`, - target: 'node14', - bundle: true, - platform: 'node', - format: 'cjs', - external - }); - - writeFileSync( - `${dirs.lambda}/.vc-config.json`, - JSON.stringify({ - runtime: 'nodejs14.x', - handler: 'index.js', - launcherType: 'Nodejs' - }) - ); - - writeFileSync(`${dirs.lambda}/package.json`, JSON.stringify({ type: 'commonjs' })); - - builder.log.minor('Copying assets...'); - - builder.writeStatic(dirs.static); - builder.writeClient(dirs.static); - builder.writePrerendered(dirs.static); - - builder.log.minor('Writing routes...'); - - const prerendered_pages = Array.from(builder.prerendered.pages, ([src, page]) => ({ - src, - dest: page.file - })); - - const prerendered_redirects = Array.from( - builder.prerendered.redirects, - ([src, redirect]) => ({ - src, - headers: { - Location: redirect.location - }, - status: redirect.status - }) - ); - - writeFileSync( - `${dir}/config.json`, - JSON.stringify({ - version: 3, - target: 'production', - routes: [ - ...redirects[builder.config.kit.trailingSlash], - ...prerendered_pages, - ...prerendered_redirects, - { - src: `/${builder.config.kit.appDir}/.+`, - headers: { - 'cache-control': 'public, immutable, max-age=31536000' - } - }, - { - handle: 'filesystem' - }, - { - src: '/.*', - dest: 'render' - } - ] - }) - ); + builder.log.minor('Generating serverless function...'); + + const relativePath = posix.relative(tmp, builder.getServerDirectory()); + + builder.copy(files, tmp, { + replace: { + SERVER: `${relativePath}/index.js`, + MANIFEST: './manifest.js' } + }); + + writeFileSync( + `${tmp}/manifest.js`, + `export const manifest = ${builder.generateManifest({ + relativePath + })};\n` + ); + + await esbuild.build({ + entryPoints: [`${tmp}/entry.js`], + outfile: `${dirs.lambda}/index.js`, + target: 'node14', + bundle: true, + platform: 'node', + external + }); + + writeFileSync(`${dirs.lambda}/package.json`, JSON.stringify({ type: 'commonjs' })); + + builder.log.minor('Copying assets...'); + + builder.writeStatic(dirs.static); + builder.writeClient(dirs.static); + builder.writePrerendered(dirs.static); + + builder.log.minor('Writing routes...'); + + builder.mkdirp(`${dir}/config`); + + const prerendered_pages = Array.from(builder.prerendered.pages, ([src, page]) => ({ + src, + dest: page.file + })); + + const prerendered_redirects = Array.from(builder.prerendered.redirects, ([src, redirect]) => ({ + src, + headers: { + Location: redirect.location + }, + status: redirect.status + })); + + writeFileSync( + `${dir}/config/routes.json`, + JSON.stringify([ + ...redirects[builder.config.kit.trailingSlash], + ...prerendered_pages, + ...prerendered_redirects, + { + src: `/${builder.config.kit.appDir}/.+`, + headers: { + 'cache-control': 'public, immutable, max-age=31536000' + } + }, + { + handle: 'filesystem' + }, + { + src: '/.*', + dest: '.vercel/functions/render' + } + ]) + ); +} + +/** + * @param {import('@sveltejs/kit').Builder} builder + * @param {string[]} external + */ +async function v3(builder, external) { + const dir = '.vercel/output'; + + const tmp = builder.getBuildDirectory('vercel-tmp'); + + builder.rimraf(dir); + builder.rimraf(tmp); + + const files = fileURLToPath(new URL('./files', import.meta.url).href); + + const dirs = { + static: `${dir}/static`, + lambda: `${dir}/functions/render.func` }; + + builder.log.minor('Generating serverless function...'); + + const relativePath = posix.relative(tmp, builder.getServerDirectory()); + + builder.copy(files, tmp, { + replace: { + SERVER: `${relativePath}/index.js`, + MANIFEST: './manifest.js' + } + }); + + writeFileSync( + `${tmp}/manifest.js`, + `export const manifest = ${builder.generateManifest({ + relativePath + })};\n` + ); + + await esbuild.build({ + entryPoints: [`${tmp}/entry.js`], + outfile: `${dirs.lambda}/index.js`, + target: 'node14', + bundle: true, + platform: 'node', + format: 'cjs', + external + }); + + writeFileSync( + `${dirs.lambda}/.vc-config.json`, + JSON.stringify({ + runtime: 'nodejs14.x', + handler: 'index.js', + launcherType: 'Nodejs' + }) + ); + + writeFileSync(`${dirs.lambda}/package.json`, JSON.stringify({ type: 'commonjs' })); + + builder.log.minor('Copying assets...'); + + builder.writeStatic(dirs.static); + builder.writeClient(dirs.static); + builder.writePrerendered(dirs.static); + + builder.log.minor('Writing routes...'); + + const prerendered_pages = Array.from(builder.prerendered.pages, ([src, page]) => ({ + src, + dest: page.file + })); + + const prerendered_redirects = Array.from(builder.prerendered.redirects, ([src, redirect]) => ({ + src, + headers: { + Location: redirect.location + }, + status: redirect.status + })); + + writeFileSync( + `${dir}/config.json`, + JSON.stringify({ + version: 3, + target: 'production', + routes: [ + ...redirects[builder.config.kit.trailingSlash], + ...prerendered_pages, + ...prerendered_redirects, + { + src: `/${builder.config.kit.appDir}/.+`, + headers: { + 'cache-control': 'public, immutable, max-age=31536000' + } + }, + { + handle: 'filesystem' + }, + { + src: '/.*', + dest: 'render' + } + ] + }) + ); } From 18661ce6c5cbcc480c6a3026e1911857462aa3be Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 19 Apr 2022 15:37:11 -0400 Subject: [PATCH 04/13] implement route splitting --- packages/adapter-vercel/index.d.ts | 2 + packages/adapter-vercel/index.js | 177 +++++++++++++++++-------- packages/kit/src/core/adapt/builder.js | 5 +- packages/kit/types/index.d.ts | 2 +- packages/kit/types/private.d.ts | 3 +- 5 files changed, 132 insertions(+), 57 deletions(-) diff --git a/packages/adapter-vercel/index.d.ts b/packages/adapter-vercel/index.d.ts index 25e220d37ec8..1566ac142b82 100644 --- a/packages/adapter-vercel/index.d.ts +++ b/packages/adapter-vercel/index.d.ts @@ -1,7 +1,9 @@ import { Adapter } from '@sveltejs/kit'; type Options = { + edge?: boolean; external?: string[]; + split?: boolean; }; declare function plugin(options?: Options): Adapter; diff --git a/packages/adapter-vercel/index.js b/packages/adapter-vercel/index.js index 8c6eae5a458c..50ad217a2a1b 100644 --- a/packages/adapter-vercel/index.js +++ b/packages/adapter-vercel/index.js @@ -1,5 +1,5 @@ -import { writeFileSync } from 'fs'; -import { posix } from 'path'; +import { fstat, mkdirSync, writeFileSync } from 'fs'; +import { dirname, posix } from 'path'; import { fileURLToPath } from 'url'; import esbuild from 'esbuild'; @@ -81,14 +81,18 @@ const redirects = { }; /** @type {import('.')} **/ -export default function ({ external = [] } = {}) { +export default function ({ external = [], edge, split } = {}) { return { name: '@sveltejs/adapter-vercel', async adapt(builder) { if (process.env.ENABLE_VC_BUILD) { - await v3(builder, external); + await v3(builder, external, edge, split); } else { + if (edge || split) { + throw new Error('edge and split options can only be used with ENABLE_VC_BUILD'); + } + await v1(builder, external); } } @@ -192,8 +196,10 @@ async function v1(builder, external) { /** * @param {import('@sveltejs/kit').Builder} builder * @param {string[]} external + * @param {boolean} edge + * @param {boolean} split */ -async function v3(builder, external) { +async function v3(builder, external, edge, split) { const dir = '.vercel/output'; const tmp = builder.getBuildDirectory('vercel-tmp'); @@ -205,47 +211,111 @@ async function v3(builder, external) { const dirs = { static: `${dir}/static`, - lambda: `${dir}/functions/render.func` + functions: `${dir}/functions` }; - builder.log.minor('Generating serverless function...'); + // TODO use `overrides` rather than `routes` + const prerendered_pages = Array.from(builder.prerendered.pages, ([src, page]) => ({ + src, + dest: page.file + })); - const relativePath = posix.relative(tmp, builder.getServerDirectory()); + const prerendered_redirects = Array.from(builder.prerendered.redirects, ([src, redirect]) => ({ + src, + headers: { + Location: redirect.location + }, + status: redirect.status + })); - builder.copy(files, tmp, { - replace: { - SERVER: `${relativePath}/index.js`, - MANIFEST: './manifest.js' + const routes = [ + ...redirects[builder.config.kit.trailingSlash], + ...prerendered_pages, + ...prerendered_redirects, + { + src: `/${builder.config.kit.appDir}/.+`, + headers: { + 'cache-control': 'public, immutable, max-age=31536000' + } + }, + { + handle: 'filesystem' + }, + { + src: '/.*', + dest: 'render' } - }); + ]; - writeFileSync( - `${tmp}/manifest.js`, - `export const manifest = ${builder.generateManifest({ - relativePath - })};\n` - ); - - await esbuild.build({ - entryPoints: [`${tmp}/entry.js`], - outfile: `${dirs.lambda}/index.js`, - target: 'node14', - bundle: true, - platform: 'node', - format: 'cjs', - external - }); - - writeFileSync( - `${dirs.lambda}/.vc-config.json`, - JSON.stringify({ - runtime: 'nodejs14.x', - handler: 'index.js', - launcherType: 'Nodejs' - }) - ); + builder.log.minor('Generating serverless function...'); - writeFileSync(`${dirs.lambda}/package.json`, JSON.stringify({ type: 'commonjs' })); + /** + * @param {string} name + * @param {string} pattern + * @param {(options: { relativePath: string }) => string} generate_manifest + */ + async function generate_serverless_function(name, pattern, generate_manifest) { + const tmp = builder.getBuildDirectory(`vercel-tmp/${name}`); + const relativePath = posix.relative(tmp, builder.getServerDirectory()); + + builder.copy(files, tmp, { + replace: { + SERVER: `${relativePath}/index.js`, + MANIFEST: './manifest.js' + } + }); + + write( + `${tmp}/manifest.js`, + `export const manifest = ${generate_manifest({ relativePath })};\n` + ); + + await esbuild.build({ + entryPoints: [`${tmp}/entry.js`], + outfile: `${dirs.functions}/${name}.func/index.js`, + target: 'node14', + bundle: true, + platform: 'node', + format: 'cjs', + external + }); + + write( + `${dirs.functions}/${name}.func/.vc-config.json`, + JSON.stringify({ + runtime: 'nodejs14.x', + handler: 'index.js', + launcherType: 'Nodejs' + }) + ); + + write(`${dirs.functions}/${name}.func/package.json`, JSON.stringify({ type: 'commonjs' })); + + routes.push({ src: pattern, dest: name }); + } + + /** + * @param {string} name + * @param {string} pattern + */ + async function generate_edge_function(name, pattern) {} + + const generate_function = edge ? generate_edge_function : generate_serverless_function; + + if (split) { + await builder.createEntries((route) => { + return { + id: route.pattern.toString(), // TODO is `id` necessary? + filter: (other) => route.pattern.toString() === other.pattern.toString(), + complete: async (entry) => { + const src = `/${route.pattern}`; + await generate_function(route.id, src, entry.generateManifest); + } + }; + }); + } else { + generate_function('render', '/.*', builder.generateManifest); + } builder.log.minor('Copying assets...'); @@ -255,20 +325,7 @@ async function v3(builder, external) { builder.log.minor('Writing routes...'); - const prerendered_pages = Array.from(builder.prerendered.pages, ([src, page]) => ({ - src, - dest: page.file - })); - - const prerendered_redirects = Array.from(builder.prerendered.redirects, ([src, redirect]) => ({ - src, - headers: { - Location: redirect.location - }, - status: redirect.status - })); - - writeFileSync( + write( `${dir}/config.json`, JSON.stringify({ version: 3, @@ -294,3 +351,17 @@ async function v3(builder, external) { }) ); } + +/** + * @param {string} file + * @param {string} data + */ +function write(file, data) { + try { + mkdirSync(dirname(file), { recursive: true }); + } catch { + // do nothing + } + + writeFileSync(file, data); +} diff --git a/packages/kit/src/core/adapt/builder.js b/packages/kit/src/core/adapt/builder.js index 7c589dff643a..057d06dd80f2 100644 --- a/packages/kit/src/core/adapt/builder.js +++ b/packages/kit/src/core/adapt/builder.js @@ -33,11 +33,12 @@ export function create_builder({ config, build_data, prerendered, log }) { config, prerendered, - createEntries(fn) { + async createEntries(fn) { const { routes } = build_data.manifest_data; /** @type {import('types').RouteDefinition[]} */ const facades = routes.map((route) => ({ + id: route.id, type: route.type, segments: route.id.split('/').map((segment) => ({ dynamic: segment.includes('['), @@ -81,7 +82,7 @@ export function create_builder({ config, build_data, prerendered, log }) { }); if (filtered.size > 0) { - complete({ + await complete({ generateManifest: ({ relativePath, format }) => generate_manifest({ build_data, diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 088596ed4354..5a13705ef5f2 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -41,7 +41,7 @@ export interface Builder { * Create entry points that map to individual functions * @param fn A function that groups a set of routes into an entry point */ - createEntries(fn: (route: RouteDefinition) => AdapterEntry): void; + createEntries(fn: (route: RouteDefinition) => AdapterEntry): Promise; generateManifest: (opts: { relativePath: string; format?: 'esm' | 'cjs' }) => string; diff --git a/packages/kit/types/private.d.ts b/packages/kit/types/private.d.ts index f2a74d7b118a..9cd151ad5bb5 100644 --- a/packages/kit/types/private.d.ts +++ b/packages/kit/types/private.d.ts @@ -23,7 +23,7 @@ export interface AdapterEntry { */ complete: (entry: { generateManifest: (opts: { relativePath: string; format?: 'esm' | 'cjs' }) => string; - }) => void; + }) => Promise; } // Based on https://github.com/josh-hemphill/csp-typed-directives/blob/latest/src/csp.types.ts @@ -250,6 +250,7 @@ export interface ResolveOptions { export type ResponseHeaders = Record; export interface RouteDefinition { + id: string; type: 'page' | 'endpoint'; pattern: RegExp; segments: RouteSegment[]; From 5942d60dcd4fb252fcf4c1351d3f700c292a23bf Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 19 Apr 2022 16:09:12 -0400 Subject: [PATCH 05/13] WIP edge support --- packages/adapter-vercel/files/edge.js | 16 +++++++ .../files/{entry.js => serverless.js} | 4 +- packages/adapter-vercel/files/shims.js | 2 - packages/adapter-vercel/index.js | 43 +++++++++++++++++-- 4 files changed, 59 insertions(+), 6 deletions(-) create mode 100644 packages/adapter-vercel/files/edge.js rename packages/adapter-vercel/files/{entry.js => serverless.js} (89%) delete mode 100644 packages/adapter-vercel/files/shims.js diff --git a/packages/adapter-vercel/files/edge.js b/packages/adapter-vercel/files/edge.js new file mode 100644 index 000000000000..87420defc566 --- /dev/null +++ b/packages/adapter-vercel/files/edge.js @@ -0,0 +1,16 @@ +import { Server } from 'SERVER'; +import { manifest } from 'MANIFEST'; + +const server = new Server(manifest); + +/** + * @param {Request} request + * @param {any} event + */ +export default (request, event) => { + return server.respond(request, { + getClientAddress() { + return request.headers.get('x-forwarded-for'); + } + }); +}; diff --git a/packages/adapter-vercel/files/entry.js b/packages/adapter-vercel/files/serverless.js similarity index 89% rename from packages/adapter-vercel/files/entry.js rename to packages/adapter-vercel/files/serverless.js index dcbd3ebcb6bb..9e7285f26e9a 100644 --- a/packages/adapter-vercel/files/entry.js +++ b/packages/adapter-vercel/files/serverless.js @@ -1,8 +1,10 @@ -import './shims'; +import { installFetch } from '@sveltejs/kit/install-fetch'; import { getRequest, setResponse } from '@sveltejs/kit/node'; import { Server } from 'SERVER'; import { manifest } from 'MANIFEST'; +installFetch(); + const server = new Server(manifest); /** diff --git a/packages/adapter-vercel/files/shims.js b/packages/adapter-vercel/files/shims.js deleted file mode 100644 index 8d2fe00acd85..000000000000 --- a/packages/adapter-vercel/files/shims.js +++ /dev/null @@ -1,2 +0,0 @@ -import { installFetch } from '@sveltejs/kit/install-fetch'; -installFetch(); diff --git a/packages/adapter-vercel/index.js b/packages/adapter-vercel/index.js index 50ad217a2a1b..d5a4adf8eaa2 100644 --- a/packages/adapter-vercel/index.js +++ b/packages/adapter-vercel/index.js @@ -258,7 +258,7 @@ async function v3(builder, external, edge, split) { const tmp = builder.getBuildDirectory(`vercel-tmp/${name}`); const relativePath = posix.relative(tmp, builder.getServerDirectory()); - builder.copy(files, tmp, { + builder.copy(`${files}/serverless.js`, `${tmp}/serverless.js`, { replace: { SERVER: `${relativePath}/index.js`, MANIFEST: './manifest.js' @@ -271,7 +271,7 @@ async function v3(builder, external, edge, split) { ); await esbuild.build({ - entryPoints: [`${tmp}/entry.js`], + entryPoints: [`${tmp}/serverless.js`], outfile: `${dirs.functions}/${name}.func/index.js`, target: 'node14', bundle: true, @@ -297,8 +297,45 @@ async function v3(builder, external, edge, split) { /** * @param {string} name * @param {string} pattern + * @param {(options: { relativePath: string }) => string} generate_manifest */ - async function generate_edge_function(name, pattern) {} + async function generate_edge_function(name, pattern, generate_manifest) { + const tmp = builder.getBuildDirectory(`vercel-tmp/${name}`); + const relativePath = posix.relative(tmp, builder.getServerDirectory()); + + builder.copy(`${files}/edge.js`, `${tmp}/edge.js`, { + replace: { + SERVER: `${relativePath}/index.js`, + MANIFEST: './manifest.js' + } + }); + + write( + `${tmp}/manifest.js`, + `export const manifest = ${generate_manifest({ relativePath })};\n` + ); + + await esbuild.build({ + entryPoints: [`${tmp}/edge.js`], + outfile: `${dirs.functions}/${name}.func/index.js`, + target: 'node14', + bundle: true, + platform: 'node', + format: 'esm', + external + }); + + write( + `${dirs.functions}/${name}.func/.vc-config.json`, + JSON.stringify({ + runtime: 'edge', + handler: 'index.js' + // TODO expose envVarsInUse + }) + ); + + routes.push({ src: pattern, dest: name }); + } const generate_function = edge ? generate_edge_function : generate_serverless_function; From 3d8371bb196dddec2816ecfab675321a47fe72b1 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 19 Apr 2022 16:09:29 -0400 Subject: [PATCH 06/13] DYAC --- packages/adapter-vercel/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/adapter-vercel/index.js b/packages/adapter-vercel/index.js index d5a4adf8eaa2..f63c01a18297 100644 --- a/packages/adapter-vercel/index.js +++ b/packages/adapter-vercel/index.js @@ -1,4 +1,4 @@ -import { fstat, mkdirSync, writeFileSync } from 'fs'; +import { mkdirSync, writeFileSync } from 'fs'; import { dirname, posix } from 'path'; import { fileURLToPath } from 'url'; import esbuild from 'esbuild'; From cfc7319691290a994ecd320ab95be58ce4873b9b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 19 Apr 2022 16:21:26 -0400 Subject: [PATCH 07/13] lint --- packages/adapter-vercel/files/edge.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/adapter-vercel/files/edge.js b/packages/adapter-vercel/files/edge.js index 87420defc566..cc8cb64370b1 100644 --- a/packages/adapter-vercel/files/edge.js +++ b/packages/adapter-vercel/files/edge.js @@ -5,9 +5,8 @@ const server = new Server(manifest); /** * @param {Request} request - * @param {any} event */ -export default (request, event) => { +export default (request) => { return server.respond(request, { getClientAddress() { return request.headers.get('x-forwarded-for'); From 4880c9c3318ba8ba3ae73368234ebb8d195d5a22 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 19 Apr 2022 18:08:50 -0400 Subject: [PATCH 08/13] fix config and .vc-config.json --- packages/adapter-vercel/index.js | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/packages/adapter-vercel/index.js b/packages/adapter-vercel/index.js index f63c01a18297..ec3c13618d11 100644 --- a/packages/adapter-vercel/index.js +++ b/packages/adapter-vercel/index.js @@ -329,7 +329,7 @@ async function v3(builder, external, edge, split) { `${dirs.functions}/${name}.func/.vc-config.json`, JSON.stringify({ runtime: 'edge', - handler: 'index.js' + entrypoint: 'index.js' // TODO expose envVarsInUse }) ); @@ -367,24 +367,7 @@ async function v3(builder, external, edge, split) { JSON.stringify({ version: 3, target: 'production', - routes: [ - ...redirects[builder.config.kit.trailingSlash], - ...prerendered_pages, - ...prerendered_redirects, - { - src: `/${builder.config.kit.appDir}/.+`, - headers: { - 'cache-control': 'public, immutable, max-age=31536000' - } - }, - { - handle: 'filesystem' - }, - { - src: '/.*', - dest: 'render' - } - ] + routes }) ); } From 4a29ef62d9d9716e9da72c68cc24f48c5fdceade Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 20 Apr 2022 10:14:16 -0400 Subject: [PATCH 09/13] remove catch-all route --- packages/adapter-vercel/index.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/adapter-vercel/index.js b/packages/adapter-vercel/index.js index ec3c13618d11..ca4a74bb2ed4 100644 --- a/packages/adapter-vercel/index.js +++ b/packages/adapter-vercel/index.js @@ -240,10 +240,6 @@ async function v3(builder, external, edge, split) { }, { handle: 'filesystem' - }, - { - src: '/.*', - dest: 'render' } ]; From 24d7f8ac54a97a1425192ad11ae1a0788ed47a49 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 20 Apr 2022 12:21:11 -0400 Subject: [PATCH 10/13] fixes --- packages/adapter-vercel/index.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/adapter-vercel/index.js b/packages/adapter-vercel/index.js index ca4a74bb2ed4..3405d8cc07ab 100644 --- a/packages/adapter-vercel/index.js +++ b/packages/adapter-vercel/index.js @@ -228,6 +228,7 @@ async function v3(builder, external, edge, split) { status: redirect.status })); + /** @type {any[]} */ const routes = [ ...redirects[builder.config.kit.trailingSlash], ...prerendered_pages, @@ -287,7 +288,7 @@ async function v3(builder, external, edge, split) { write(`${dirs.functions}/${name}.func/package.json`, JSON.stringify({ type: 'commonjs' })); - routes.push({ src: pattern, dest: name }); + routes.push({ src: pattern, dest: `/${name}` }); } /** @@ -330,7 +331,7 @@ async function v3(builder, external, edge, split) { }) ); - routes.push({ src: pattern, dest: name }); + routes.push({ src: pattern, middlewarePath: name }); } const generate_function = edge ? generate_edge_function : generate_serverless_function; @@ -341,13 +342,17 @@ async function v3(builder, external, edge, split) { id: route.pattern.toString(), // TODO is `id` necessary? filter: (other) => route.pattern.toString() === other.pattern.toString(), complete: async (entry) => { - const src = `/${route.pattern}`; + const src = `${route.pattern + .toString() + .slice(1, -2) // remove leading / and trailing $/ + .replace(/\\\//g, '/')}(?:/__data.json)?$`; // TODO adding /__data.json is a temporary workaround — those endpoints should be treated as distinct routes + await generate_function(route.id, src, entry.generateManifest); } }; }); } else { - generate_function('render', '/.*', builder.generateManifest); + await generate_function('render', '/.*', builder.generateManifest); } builder.log.minor('Copying assets...'); From 6b861e73a850390ba1cb19678b47a9204e2b89e4 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 20 Apr 2022 13:58:41 -0400 Subject: [PATCH 11/13] use overrides for prerendered pages --- packages/adapter-vercel/index.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/adapter-vercel/index.js b/packages/adapter-vercel/index.js index 3405d8cc07ab..781586e79f6a 100644 --- a/packages/adapter-vercel/index.js +++ b/packages/adapter-vercel/index.js @@ -214,12 +214,6 @@ async function v3(builder, external, edge, split) { functions: `${dir}/functions` }; - // TODO use `overrides` rather than `routes` - const prerendered_pages = Array.from(builder.prerendered.pages, ([src, page]) => ({ - src, - dest: page.file - })); - const prerendered_redirects = Array.from(builder.prerendered.redirects, ([src, redirect]) => ({ src, headers: { @@ -231,7 +225,6 @@ async function v3(builder, external, edge, split) { /** @type {any[]} */ const routes = [ ...redirects[builder.config.kit.trailingSlash], - ...prerendered_pages, ...prerendered_redirects, { src: `/${builder.config.kit.appDir}/.+`, @@ -363,12 +356,19 @@ async function v3(builder, external, edge, split) { builder.log.minor('Writing routes...'); + /** @type {Record} */ + const overrides = {}; + builder.prerendered.pages.forEach((page, src) => { + overrides[page.file] = { path: src.slice(1) }; + }); + write( `${dir}/config.json`, JSON.stringify({ version: 3, target: 'production', - routes + routes, + overrides }) ); } From ce331e7bafa50c27aff2158b6511a4c9a6aa2108 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 20 Apr 2022 14:44:35 -0400 Subject: [PATCH 12/13] update README --- packages/adapter-vercel/README.md | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/packages/adapter-vercel/README.md b/packages/adapter-vercel/README.md index 9717b67f17e7..3eb2a525ef7f 100644 --- a/packages/adapter-vercel/README.md +++ b/packages/adapter-vercel/README.md @@ -6,6 +6,8 @@ If you're using [adapter-auto](../adapter-auto), you don't need to install this ## Usage +> The `edge` and `split` options depend on the Vercel Build Output API which is currently in beta. For now, you must opt in by visiting `https://vercel.com/svelte/[YOUR_PROJECT]/settings/environment-variables` and adding `ENABLE_VC_BUILD` with the value `1`. + Add `"@sveltejs/adapter-vercel": "next"` to the `devDependencies` in your `package.json` and run `npm install`. Then in your `svelte.config.js`: @@ -15,18 +17,25 @@ import vercel from '@sveltejs/adapter-vercel'; export default { kit: { - ... - adapter: vercel(options) + // default options are shown + adapter: vercel({ + // if true, will deploy the app using edge functions + // (https://vercel.com/docs/concepts/functions/edge-functions) + // rather than serverless functions + edge: false, + + // an array of dependencies that esbuild should treat + // as external when bundling functions + external: [], + + // if true, will split your app into multiple functions + // instead of creating a single one for the entire app + split: false + }) } }; ``` -## Options - -You can pass an `options` argument, if necessary, with the following: - -- `external` — an array of dependencies that [esbuild](https://esbuild.github.io/api/#external) should treat as external - ## Changelog [The Changelog for this package is available on GitHub](https://github.com/sveltejs/kit/blob/master/packages/adapter-vercel/CHANGELOG.md). From f08755c48111b78824e1edeeb132e17dfe5dd93e Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 20 Apr 2022 14:47:43 -0400 Subject: [PATCH 13/13] changesets --- .changeset/giant-ants-hang.md | 5 +++++ .changeset/smooth-dodos-clap.md | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .changeset/giant-ants-hang.md create mode 100644 .changeset/smooth-dodos-clap.md diff --git a/.changeset/giant-ants-hang.md b/.changeset/giant-ants-hang.md new file mode 100644 index 000000000000..2ed64e81bfcf --- /dev/null +++ b/.changeset/giant-ants-hang.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/adapter-vercel': patch +--- + +Support build output API, with edge functions and code-splitting diff --git a/.changeset/smooth-dodos-clap.md b/.changeset/smooth-dodos-clap.md new file mode 100644 index 000000000000..d45d135122b5 --- /dev/null +++ b/.changeset/smooth-dodos-clap.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +builder.createEntries returns a promise that awaits complete() callbacks