summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Juan Martín Seery <me@juanm04.com> 2022-10-14 17:19:35 -0300
committerGravatar GitHub <noreply@github.com> 2022-10-14 17:19:35 -0300
commitcd25abae594f9c42d3766753dfeee4f476311f1e (patch)
treec25d1d7120ba55a39ffc40e83fb952492c686da8
parentf8198d2502bbf7f7daf5854e7e12317e39a66fcc (diff)
downloadastro-cd25abae594f9c42d3766753dfeee4f476311f1e.tar.gz
astro-cd25abae594f9c42d3766753dfeee4f476311f1e.tar.zst
astro-cd25abae594f9c42d3766753dfeee4f476311f1e.zip
feat(vercel): `includeFiles` and `excludeFiles` (#5085)
* refactor + include/excludeFiles * Changeset * Updated README.md Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Missing .js Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
-rw-r--r--.changeset/violet-buckets-call.md5
-rw-r--r--packages/integrations/vercel/README.md44
-rw-r--r--packages/integrations/vercel/src/edge/adapter.ts33
-rw-r--r--packages/integrations/vercel/src/lib/fs.ts57
-rw-r--r--packages/integrations/vercel/src/lib/nft.ts69
-rw-r--r--packages/integrations/vercel/src/serverless/adapter.ts20
6 files changed, 168 insertions, 60 deletions
diff --git a/.changeset/violet-buckets-call.md b/.changeset/violet-buckets-call.md
new file mode 100644
index 000000000..37688d16b
--- /dev/null
+++ b/.changeset/violet-buckets-call.md
@@ -0,0 +1,5 @@
+---
+'@astrojs/vercel': minor
+---
+
+Added `includeFiles` and `excludeFiles` options
diff --git a/packages/integrations/vercel/README.md b/packages/integrations/vercel/README.md
index 532cf3141..ca9610a61 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
+
+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';
+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
+
+Use this property to exclude any files from the bundling process that would otherwise be included.
+
+```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 c842cb38f..73cc89483 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,13 +37,15 @@ 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;
@@ -50,8 +59,8 @@ export default function vercelEdge(): AstroIntegration {
'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 }) => {
@@ -79,11 +88,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 64c4c69ba..47b218ce5 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<T>(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<void> {
}
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<string>} The common ancestor of the copied files.
+ */
+export async function copyFilesToFunction(
+ files: URL[],
+ outDir: URL,
+ exclude: URL[] = []
+): Promise<string> {
+ 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 ba3677583..6a9ac116e 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.js';
+
+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 5b65c331e..3c7ba15de 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);