Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use @vercel/nft to create lambdas #4969

Merged
merged 14 commits into from
May 24, 2022
5 changes: 5 additions & 0 deletions .changeset/lovely-seahorses-sell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/adapter-vercel': patch
---

Use @vercel/nft to include dependencies in lambda without bundling with esbuild, when using v3 build output API
1 change: 1 addition & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
{
"files": [
"**/CHANGELOG.md",
"**/.svelte-kit/**",
"packages/kit/src/packaging/test/fixtures/**/expected/**/*",
"packages/kit/src/packaging/test/watch/expected/**/*",
"packages/kit/src/packaging/test/watch/package/**/*",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"svelte-preprocess": "^4.9.8",
"svelte2tsx": "~0.5.0",
"tiny-glob": "^0.2.9",
"turbo": "^1.2.6",
"turbo": "^1.2.12",
"typescript": "~4.7.2",
"uvu": "^0.5.2"
},
Expand Down
123 changes: 90 additions & 33 deletions packages/adapter-vercel/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { mkdirSync, writeFileSync } from 'fs';
import { dirname, posix } from 'path';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { nodeFileTrace } from '@vercel/nft';
import esbuild from 'esbuild';

// rules for clean URLs and trailing slash handling,
Expand Down Expand Up @@ -80,6 +81,8 @@ const redirects = {
]
};

const files = fileURLToPath(new URL('./files', import.meta.url).href);

/** @type {import('.')} **/
export default function ({ external = [], edge, split } = {}) {
return {
Expand Down Expand Up @@ -113,16 +116,14 @@ async function v1(builder, external) {
builder.rimraf(dir);
builder.rimraf(tmp);

const files = fileURLToPath(new URL('./files', import.meta.url).href);

const dirs = {
static: `${dir}/static`,
lambda: `${dir}/functions/node/render`
};

builder.log.minor('Generating serverless function...');

const relativePath = posix.relative(tmp, builder.getServerDirectory());
const relativePath = path.posix.relative(tmp, builder.getServerDirectory());

builder.copy(`${files}/serverless.js`, `${tmp}/serverless.js`, {
replace: {
Expand All @@ -131,7 +132,7 @@ async function v1(builder, external) {
}
});

writeFileSync(
fs.writeFileSync(
`${tmp}/manifest.js`,
`export const manifest = ${builder.generateManifest({
relativePath
Expand All @@ -148,7 +149,7 @@ async function v1(builder, external) {
format: 'cjs'
});

writeFileSync(`${dirs.lambda}/package.json`, JSON.stringify({ type: 'commonjs' }));
fs.writeFileSync(`${dirs.lambda}/package.json`, JSON.stringify({ type: 'commonjs' }));

builder.log.minor('Copying assets...');

Expand All @@ -173,7 +174,7 @@ async function v1(builder, external) {
status: redirect.status
}));

writeFileSync(
fs.writeFileSync(
`${dir}/config/routes.json`,
JSON.stringify([
...redirects[builder.config.kit.trailingSlash],
Expand Down Expand Up @@ -250,10 +251,9 @@ async function v3(builder, external, edge, split) {
* @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());
const relativePath = path.posix.relative(tmp, builder.getServerDirectory());

builder.copy(`${files}/serverless.js`, `${tmp}/serverless.js`, {
builder.copy(`${files}/serverless.js`, `${tmp}/index.js`, {
replace: {
SERVER: `${relativePath}/index.js`,
MANIFEST: './manifest.js'
Expand All @@ -265,27 +265,12 @@ async function v3(builder, external, edge, split) {
`export const manifest = ${generate_manifest({ relativePath })};\n`
);

await esbuild.build({
entryPoints: [`${tmp}/serverless.js`],
outfile: `${dirs.functions}/${name}.func/index.js`,
target: `node${node_version.full}`,
bundle: true,
platform: 'node',
format: 'cjs',
external
});

write(
`${dirs.functions}/${name}.func/.vc-config.json`,
JSON.stringify({
runtime: `nodejs${node_version.major}.x`,
handler: 'index.js',
launcherType: 'Nodejs'
})
await create_function_bundle(
`${tmp}/index.js`,
`${dirs.functions}/${name}.func`,
`nodejs${node_version.major}.x`
);

write(`${dirs.functions}/${name}.func/package.json`, JSON.stringify({ type: 'commonjs' }));

routes.push({ src: pattern, dest: `/${name}` });
}

Expand All @@ -296,7 +281,7 @@ async function v3(builder, external, edge, split) {
*/
async function generate_edge_function(name, pattern, generate_manifest) {
const tmp = builder.getBuildDirectory(`vercel-tmp/${name}`);
const relativePath = posix.relative(tmp, builder.getServerDirectory());
const relativePath = path.posix.relative(tmp, builder.getServerDirectory());

builder.copy(`${files}/edge.js`, `${tmp}/edge.js`, {
replace: {
Expand Down Expand Up @@ -392,12 +377,12 @@ async function v3(builder, external, edge, split) {
*/
function write(file, data) {
try {
mkdirSync(dirname(file), { recursive: true });
fs.mkdirSync(path.dirname(file), { recursive: true });
} catch {
// do nothing
}

writeFileSync(file, data);
fs.writeFileSync(file, data);
}

function get_node_version() {
Expand All @@ -412,3 +397,75 @@ function get_node_version() {

return { major, full };
}

/**
* @param {string} entry
* @param {string} dir
* @param {string} runtime
*/
async function create_function_bundle(entry, dir, runtime) {
let base = entry;
while (base !== (base = path.dirname(base)));

const traced = await nodeFileTrace([entry], { base });

traced.warnings.forEach((error) => {
// pending https://github.com/vercel/nft/issues/284
if (error.message.startsWith('Failed to resolve dependency node:')) return;
console.error(error);
});

// find common ancestor directory
let common_parts;

for (const file of traced.fileList) {
if (common_parts) {
const parts = file.split(path.sep);

for (let i = 0; i < common_parts.length; i += 1) {
if (parts[i] !== common_parts[i]) {
common_parts = common_parts.slice(0, i);
break;
}
}
} else {
common_parts = path.dirname(file).split(path.sep);
}
}

const ancestor = base + common_parts.join(path.sep);

for (const file of traced.fileList) {
const source = base + file;
const dest = path.join(dir, path.relative(ancestor, source));

const stats = fs.statSync(source);
const is_dir = stats.isDirectory();

const realpath = fs.realpathSync(source);

try {
fs.mkdirSync(path.dirname(dest), { recursive: true });
} catch {
// do nothing
}

if (source !== realpath) {
const realdest = path.join(dir, path.relative(ancestor, realpath));
fs.symlinkSync(path.relative(path.dirname(dest), realdest), dest, is_dir ? 'dir' : 'file');
} else if (!is_dir) {
fs.copyFileSync(source, dest);
}
}

write(
`${dir}/.vc-config.json`,
JSON.stringify({
runtime,
handler: path.relative(base + ancestor, entry),
launcherType: 'Nodejs'
})
);

write(`${dir}/package.json`, JSON.stringify({ type: 'module' }));
}
1 change: 1 addition & 0 deletions packages/adapter-vercel/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"check": "tsc"
},
"dependencies": {
"@vercel/nft": "^0.18.2",
"esbuild": "^0.14.29"
},
"devDependencies": {
Expand Down
3 changes: 2 additions & 1 deletion packages/adapter-vercel/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
"checkJs": true,
"noEmit": true,
"noImplicitAny": true,
"module": "es2020",
"module": "es2022",
"moduleResolution": "node",
"target": "es2022",
"allowSyntheticDefaultImports": true,
"baseUrl": ".",
"paths": {
Expand Down
1 change: 1 addition & 0 deletions packages/kit/test/apps/amp/src/app.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="@sveltejs/kit" />
Loading