From c533e60537b03b9282c3a09fa25f599e9fca5934 Mon Sep 17 00:00:00 2001 From: JuanM04 Date: Thu, 13 Oct 2022 20:01:42 -0300 Subject: [PATCH 1/4] refactor + include/excludeFiles --- packages/integrations/vercel/README.md | 44 +++++++++++- .../integrations/vercel/src/edge/adapter.ts | 34 +++++++-- packages/integrations/vercel/src/lib/fs.ts | 57 +++++++++++++++ packages/integrations/vercel/src/lib/nft.ts | 69 ++++++------------- .../vercel/src/serverless/adapter.ts | 20 ++++-- 5 files changed, 163 insertions(+), 61 deletions(-) diff --git a/packages/integrations/vercel/README.md b/packages/integrations/vercel/README.md index 532cf314191c..287496e5022e 100644 --- a/packages/integrations/vercel/README.md +++ b/packages/integrations/vercel/README.md @@ -84,7 +84,49 @@ vercel deploy --prebuilt ## Configuration -This adapter does not expose any configuration options. +To configure this adapter, pass an object to the `vercel()` function call in `astro.config.mjs`: + +### includeFiles + +> **Type:** `string[]` +> **Available for:** Edge, Serverless + +You can force files to be bundled with your function in case something's missing. + +```js +import { defineConfig } from 'astro/config'; +import vercel from '@astrojs/vercel/serverless'; + +export default defineConfig({ + output: 'server', + adapter: vercel({ + includeFiles: ['./my-data.json'] + }) +}); +``` + +> **Note** +> When building for the Edge, all the depencies get bundled in a single file to save space. **No extra file will be bundled**. So, if you _need_ some file inside the function, you have to specify it in `includeFiles`. + + +### excludeFiles + +> **Type:** `string[]` +> **Available for:** Serverless + +There might be some files that are being picked up by the bundled that should be there. You can exclude them with this property. + +```js +import { defineConfig } from 'astro/config'; +import vercel from '@astrojs/vercel/serverless'; + +export default defineConfig({ + output: 'server', + adapter: vercel({ + excludeFiles: ['./src/some_big_file.jpg'] + }) +}); +``` ## Troubleshooting diff --git a/packages/integrations/vercel/src/edge/adapter.ts b/packages/integrations/vercel/src/edge/adapter.ts index 018ab703260f..4cc694abddcd 100644 --- a/packages/integrations/vercel/src/edge/adapter.ts +++ b/packages/integrations/vercel/src/edge/adapter.ts @@ -1,6 +1,8 @@ import type { AstroAdapter, AstroConfig, AstroIntegration } from 'astro'; +import { relative as relativePath } from 'node:path'; +import { fileURLToPath } from 'node:url'; -import { getVercelOutput, writeJson } from '../lib/fs.js'; +import { getVercelOutput, removeDir, writeJson, copyFilesToFunction } from '../lib/fs.js'; import { getRedirects } from '../lib/redirects.js'; const PACKAGE_NAME = '@astrojs/vercel/edge'; @@ -13,8 +15,13 @@ function getAdapter(): AstroAdapter { }; } -export default function vercelEdge(): AstroIntegration { +export interface VercelEdgeConfig { + includeFiles?: string[]; +} + +export default function vercelEdge({ includeFiles = [] }: VercelEdgeConfig = {}): AstroIntegration { let _config: AstroConfig; + let buildTempFolder: URL; let functionFolder: URL; let serverEntry: string; let needsBuildConfig = false; @@ -30,21 +37,22 @@ export default function vercelEdge(): AstroIntegration { build: { serverEntry: 'entry.mjs', client: new URL('./static/', outDir), - server: new URL('./functions/render.func/', config.outDir), + server: new URL('./dist/', config.root), }, }); }, 'astro:config:done': ({ setAdapter, config }) => { setAdapter(getAdapter()); _config = config; + buildTempFolder = config.build.server; + functionFolder = new URL('./functions/render.func/', config.outDir); serverEntry = config.build.serverEntry; - functionFolder = config.build.server; }, 'astro:build:start': ({ buildConfig }) => { if (needsBuildConfig) { buildConfig.client = new URL('./static/', _config.outDir); + buildTempFolder = buildConfig.server = new URL('./dist/', _config.root); serverEntry = buildConfig.serverEntry = 'entry.mjs'; - functionFolder = buildConfig.server = new URL('./functions/render.func/', _config.outDir); } }, 'astro:build:setup': ({ vite, target }) => { @@ -69,11 +77,25 @@ export default function vercelEdge(): AstroIntegration { } }, 'astro:build:done': async ({ routes }) => { + const entry = new URL(serverEntry, buildTempFolder); + + // Copy entry and other server files + const commonAncestor = await copyFilesToFunction( + [ + new URL(serverEntry, buildTempFolder), + ...includeFiles.map((file) => new URL(file, _config.root)), + ], + functionFolder + ); + + // Remove temporary folder + await removeDir(buildTempFolder); + // Edge function config // https://vercel.com/docs/build-output-api/v3#vercel-primitives/edge-functions/configuration await writeJson(new URL(`./.vc-config.json`, functionFolder), { runtime: 'edge', - entrypoint: serverEntry, + entrypoint: relativePath(commonAncestor, fileURLToPath(entry)), }); // Output configuration diff --git a/packages/integrations/vercel/src/lib/fs.ts b/packages/integrations/vercel/src/lib/fs.ts index 64c4c69ba67d..47b218ce50a5 100644 --- a/packages/integrations/vercel/src/lib/fs.ts +++ b/packages/integrations/vercel/src/lib/fs.ts @@ -1,5 +1,7 @@ import type { PathLike } from 'node:fs'; import * as fs from 'node:fs/promises'; +import nodePath from 'node:path'; +import { fileURLToPath } from 'node:url'; export async function writeJson(path: PathLike, data: T) { await fs.writeFile(path, JSON.stringify(data), { encoding: 'utf-8' }); @@ -15,3 +17,58 @@ export async function emptyDir(dir: PathLike): Promise { } 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. + * + * @param {URL[]} files A list of files to copy (absolute path). + * @param {URL} outDir Destination folder where to copy the files to (absolute path). + * @param {URL[]} [exclude] A list of files to exclude (absolute path). + * @returns {Promise} The common ancestor of the copied files. + */ +export async function copyFilesToFunction( + files: URL[], + outDir: URL, + exclude: URL[] = [] +): Promise { + const excludeList = exclude.map(fileURLToPath); + const fileList = files.map(fileURLToPath).filter((f) => !excludeList.includes(f)); + + if (files.length === 0) throw new Error('[@astrojs/vercel] No files found to copy'); + + let commonAncestor = nodePath.dirname(fileList[0]); + for (const file of fileList.slice(1)) { + while (!file.startsWith(commonAncestor)) { + commonAncestor = nodePath.dirname(commonAncestor); + } + } + + for (const origin of fileList) { + const dest = new URL(nodePath.relative(commonAncestor, origin), outDir); + + const realpath = await fs.realpath(origin); + const isSymlink = realpath !== origin; + const isDir = (await fs.stat(origin)).isDirectory(); + + // Create directories recursively + if (isDir && !isSymlink) { + await fs.mkdir(new URL('..', dest), { recursive: true }); + } else { + await fs.mkdir(new URL('.', dest), { recursive: true }); + } + + if (isSymlink) { + const realdest = fileURLToPath(new URL(nodePath.relative(commonAncestor, realpath), outDir)); + await fs.symlink( + nodePath.relative(fileURLToPath(new URL('.', dest)), realdest), + dest, + isDir ? 'dir' : 'file' + ); + } else if (!isDir) { + await fs.copyFile(origin, dest); + } + } + + return commonAncestor; +} diff --git a/packages/integrations/vercel/src/lib/nft.ts b/packages/integrations/vercel/src/lib/nft.ts index ba3677583778..83209bf824af 100644 --- a/packages/integrations/vercel/src/lib/nft.ts +++ b/packages/integrations/vercel/src/lib/nft.ts @@ -1,12 +1,20 @@ import { nodeFileTrace } from '@vercel/nft'; -import * as fs from 'node:fs/promises'; -import nodePath from 'node:path'; +import { relative as relativePath } from 'node:path'; import { fileURLToPath } from 'node:url'; -export async function copyDependenciesToFunction( - entry: URL, - outDir: URL -): Promise<{ handler: string }> { +import { copyFilesToFunction } from './fs'; + +export async function copyDependenciesToFunction({ + entry, + outDir, + includeFiles, + excludeFiles, +}: { + entry: URL; + outDir: URL; + includeFiles: URL[]; + excludeFiles: URL[]; +}): Promise<{ handler: string }> { const entryPath = fileURLToPath(entry); // Get root of folder of the system (like C:\ on Windows or / on Linux) @@ -19,8 +27,6 @@ export async function copyDependenciesToFunction( base: fileURLToPath(base), }); - if (result.fileList.size === 0) throw new Error('[@astrojs/vercel] No files found'); - for (const error of result.warnings) { if (error.message.startsWith('Failed to resolve dependency')) { const [, module, file] = /Cannot find module '(.+?)' loaded from (.+)/.exec(error.message)!; @@ -42,49 +48,14 @@ export async function copyDependenciesToFunction( } } - const fileList = [...result.fileList]; - - let commonAncestor = nodePath.dirname(fileList[0]); - for (const file of fileList.slice(1)) { - while (!file.startsWith(commonAncestor)) { - commonAncestor = nodePath.dirname(commonAncestor); - } - } - - for (const file of fileList) { - const origin = new URL(file, base); - const dest = new URL(nodePath.relative(commonAncestor, file), outDir); - - const realpath = await fs.realpath(origin); - const isSymlink = realpath !== fileURLToPath(origin); - const isDir = (await fs.stat(origin)).isDirectory(); - - // Create directories recursively - if (isDir && !isSymlink) { - await fs.mkdir(new URL('..', dest), { recursive: true }); - } else { - await fs.mkdir(new URL('.', dest), { recursive: true }); - } - - if (isSymlink) { - const realdest = fileURLToPath( - new URL( - nodePath.relative(nodePath.join(fileURLToPath(base), commonAncestor), realpath), - outDir - ) - ); - await fs.symlink( - nodePath.relative(fileURLToPath(new URL('.', dest)), realdest), - dest, - isDir ? 'dir' : 'file' - ); - } else if (!isDir) { - await fs.copyFile(origin, dest); - } - } + const commonAncestor = await copyFilesToFunction( + [...result.fileList].map((file) => new URL(file, base)).concat(includeFiles), + outDir, + excludeFiles + ); return { // serverEntry location inside the outDir - handler: nodePath.relative(nodePath.join(fileURLToPath(base), commonAncestor), entryPath), + handler: relativePath(commonAncestor, entryPath), }; } diff --git a/packages/integrations/vercel/src/serverless/adapter.ts b/packages/integrations/vercel/src/serverless/adapter.ts index 5b65c331efe7..3c7ba15defda 100644 --- a/packages/integrations/vercel/src/serverless/adapter.ts +++ b/packages/integrations/vercel/src/serverless/adapter.ts @@ -14,7 +14,15 @@ function getAdapter(): AstroAdapter { }; } -export default function vercelEdge(): AstroIntegration { +export interface VercelServerlessConfig { + includeFiles?: string[]; + excludeFiles?: string[]; +} + +export default function vercelServerless({ + includeFiles, + excludeFiles, +}: VercelServerlessConfig = {}): AstroIntegration { let _config: AstroConfig; let buildTempFolder: URL; let functionFolder: URL; @@ -59,10 +67,12 @@ export default function vercelEdge(): AstroIntegration { }, 'astro:build:done': async ({ routes }) => { // Copy necessary files (e.g. node_modules/) - const { handler } = await copyDependenciesToFunction( - new URL(serverEntry, buildTempFolder), - functionFolder - ); + const { handler } = await copyDependenciesToFunction({ + entry: new URL(serverEntry, buildTempFolder), + outDir: functionFolder, + includeFiles: includeFiles?.map((file) => new URL(file, _config.root)) || [], + excludeFiles: excludeFiles?.map((file) => new URL(file, _config.root)) || [], + }); // Remove temporary folder await removeDir(buildTempFolder); From ea0572a4d76c93efdb32d94ef9589f1ba849468d Mon Sep 17 00:00:00 2001 From: JuanM04 Date: Thu, 13 Oct 2022 20:21:42 -0300 Subject: [PATCH 2/4] Changeset --- .changeset/violet-buckets-call.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/violet-buckets-call.md diff --git a/.changeset/violet-buckets-call.md b/.changeset/violet-buckets-call.md new file mode 100644 index 000000000000..37688d16b02b --- /dev/null +++ b/.changeset/violet-buckets-call.md @@ -0,0 +1,5 @@ +--- +'@astrojs/vercel': minor +--- + +Added `includeFiles` and `excludeFiles` options From 65c720fa152481be230fa7752378bd4ab92e867c Mon Sep 17 00:00:00 2001 From: JuanM04 Date: Fri, 14 Oct 2022 08:45:52 -0300 Subject: [PATCH 3/4] Updated README.md Co-authored-by: Sarah Rainsberger --- packages/integrations/vercel/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/integrations/vercel/README.md b/packages/integrations/vercel/README.md index 287496e5022e..ca9610a61e5d 100644 --- a/packages/integrations/vercel/README.md +++ b/packages/integrations/vercel/README.md @@ -91,7 +91,7 @@ To configure this adapter, pass an object to the `vercel()` function call in `as > **Type:** `string[]` > **Available for:** Edge, Serverless -You can force files to be bundled with your function in case something's missing. +Use this property to force files to be bundled with your function. This is helpful when you notice missing files. ```js import { defineConfig } from 'astro/config'; @@ -114,7 +114,7 @@ export default defineConfig({ > **Type:** `string[]` > **Available for:** Serverless -There might be some files that are being picked up by the bundled that should be there. You can exclude them with this property. +Use this property to exclude any files from the bundling process that would otherwise be included. ```js import { defineConfig } from 'astro/config'; From 890f9ec0174f19949b4ca5432c33278ee2864034 Mon Sep 17 00:00:00 2001 From: JuanM04 Date: Fri, 14 Oct 2022 11:23:55 -0300 Subject: [PATCH 4/4] Missing .js --- packages/integrations/vercel/src/lib/nft.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/integrations/vercel/src/lib/nft.ts b/packages/integrations/vercel/src/lib/nft.ts index 83209bf824af..6a9ac116eeeb 100644 --- a/packages/integrations/vercel/src/lib/nft.ts +++ b/packages/integrations/vercel/src/lib/nft.ts @@ -2,7 +2,7 @@ import { nodeFileTrace } from '@vercel/nft'; import { relative as relativePath } from 'node:path'; import { fileURLToPath } from 'node:url'; -import { copyFilesToFunction } from './fs'; +import { copyFilesToFunction } from './fs.js'; export async function copyDependenciesToFunction({ entry,