diff --git a/.changeset/chatty-pandas-swim.md b/.changeset/chatty-pandas-swim.md new file mode 100644 index 000000000000..5fa9bd5cecf0 --- /dev/null +++ b/.changeset/chatty-pandas-swim.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/adapter-vercel': minor +--- + +feat: allow compatible subset of Node.js built-in modules when targeting edge functions diff --git a/.changeset/two-pianos-obey.md b/.changeset/two-pianos-obey.md new file mode 100644 index 000000000000..1a5f50d810e4 --- /dev/null +++ b/.changeset/two-pianos-obey.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/adapter-netlify': minor +--- + +feat: allow Node.js built-in modules when targeting edge functions diff --git a/packages/adapter-netlify/index.js b/packages/adapter-netlify/index.js index 767644efd892..7bfec980ef9a 100644 --- a/packages/adapter-netlify/index.js +++ b/packages/adapter-netlify/index.js @@ -1,6 +1,7 @@ import { appendFileSync, existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'; import { dirname, join, resolve, posix } from 'node:path'; import { fileURLToPath } from 'node:url'; +import { builtinModules } from 'node:module'; import esbuild from 'esbuild'; import toml from '@iarna/toml'; @@ -165,7 +166,12 @@ async function generate_edge_functions({ builder }) { format: 'esm', platform: 'browser', sourcemap: 'linked', - target: 'es2020' + target: 'es2020', + + // Node built-ins are allowed, but must be prefixed with `node:` + // https://docs.netlify.com/edge-functions/api/#runtime-environment + external: builtinModules.map((id) => `node:${id}`), + alias: Object.fromEntries(builtinModules.map((id) => [id, `node:${id}`])) }); writeFileSync('.netlify/edge-functions/manifest.json', JSON.stringify(edge_manifest)); diff --git a/packages/adapter-vercel/index.js b/packages/adapter-vercel/index.js index 683df1757073..c2cfdbd80093 100644 --- a/packages/adapter-vercel/index.js +++ b/packages/adapter-vercel/index.js @@ -18,6 +18,9 @@ const get_default_runtime = () => { ); }; +// https://vercel.com/docs/functions/edge-functions/edge-runtime#compatible-node.js-modules +const compatible_node_modules = ['async_hooks', 'events', 'buffer', 'assert', 'util']; + /** @type {import('.').default} **/ const plugin = function (defaults = {}) { if ('edge' in defaults) { @@ -109,20 +112,61 @@ const plugin = function (defaults = {}) { `export const manifest = ${builder.generateManifest({ relativePath, routes })};\n` ); - await esbuild.build({ - entryPoints: [`${tmp}/edge.js`], - outfile: `${dirs.functions}/${name}.func/index.js`, - target: 'es2020', // TODO verify what the edge runtime supports - bundle: true, - platform: 'browser', - format: 'esm', - external: config.external, - sourcemap: 'linked', - banner: { js: 'globalThis.global = globalThis;' }, - loader: { - '.wasm': 'copy' + try { + const result = await esbuild.build({ + entryPoints: [`${tmp}/edge.js`], + outfile: `${dirs.functions}/${name}.func/index.js`, + target: 'es2020', // TODO verify what the edge runtime supports + bundle: true, + platform: 'browser', + format: 'esm', + external: [ + ...compatible_node_modules, + ...compatible_node_modules.map((id) => `node:${id}`), + ...(config.external || []) + ], + sourcemap: 'linked', + banner: { js: 'globalThis.global = globalThis;' }, + loader: { + '.wasm': 'copy' + } + }); + + if (result.warnings.length > 0) { + const formatted = await esbuild.formatMessages(result.warnings, { + kind: 'warning', + color: true + }); + + console.error(formatted.join('\n')); } - }); + } catch (error) { + for (const e of error.errors) { + for (const node of e.notes) { + const match = + /The package "(.+)" wasn't found on the file system but is built into node/.exec( + node.text + ); + + if (match) { + node.text = `Cannot use "${match[1]}" when deploying to Vercel Edge Functions.`; + } + } + } + + const formatted = await esbuild.formatMessages(error.errors, { + kind: 'error', + color: true + }); + + console.error(formatted.join('\n')); + + throw new Error( + `Bundling with esbuild failed with ${error.errors.length} ${ + error.errors.length === 1 ? 'error' : 'errors' + }` + ); + } write( `${dirs.functions}/${name}.func/.vc-config.json`,