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

fix(vercel): output build directory #437

Merged
merged 2 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/slow-mangos-call.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/vercel': patch
---

Fixes a regression where the `@astrojs/vercel` single entry point for the adapter was causing some regressions in users projects.
124 changes: 60 additions & 64 deletions packages/vercel/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { existsSync, readFileSync } from 'node:fs';
import { cpSync, existsSync, mkdirSync, readFileSync } from 'node:fs';
import { basename } from 'node:path';
import { pathToFileURL } from 'node:url';
import { emptyDir, removeDir, writeJson } from '@astrojs/internal-helpers/fs';
Expand All @@ -7,6 +7,7 @@ import type {
AstroConfig,
AstroIntegration,
AstroIntegrationLogger,
HookParameters,
IntegrationRouteData,
} from 'astro';
import glob from 'fast-glob';
Expand Down Expand Up @@ -190,11 +191,12 @@ export default function vercelAdapter({

let buildOutput: 'server' | 'static';

let staticDir: URL | undefined;

return {
name: PACKAGE_NAME,
hooks: {
'astro:config:setup': async ({ command, config, updateConfig, injectScript, logger }) => {
buildOutput = config.output;
if (webAnalytics?.enabled) {
injectScript(
'head-inline',
Expand All @@ -204,6 +206,29 @@ export default function vercelAdapter({
);
}

staticDir = new URL('./.vercel/output/static', config.root);
updateConfig({
build: {
format: 'directory',
redirects: false,
},
vite: {
ssr: {
external: ['@vercel/nft'],
},
},
...getAstroImageConfig(
imageService,
imagesConfig,
command,
devImageService,
config.image
),
});
},
'astro:config:done': ({ setAdapter, config, logger }) => {
buildOutput = config.output;

ematipico marked this conversation as resolved.
Show resolved Hide resolved
if (buildOutput === 'server') {
if (maxDuration && maxDuration > 900) {
logger.warn(
Expand All @@ -214,7 +239,6 @@ export default function vercelAdapter({
`Please make sure that your plan allows for this duration. See https://vercel.com/docs/functions/serverless-functions/runtimes#maxduration for more information.`
);
}

const vercelConfigPath = new URL('vercel.json', config.root);
if (existsSync(vercelConfigPath)) {
try {
Expand All @@ -225,57 +249,13 @@ export default function vercelAdapter({
`\tYour "vercel.json" \`trailingSlash\` configuration (set to \`true\`) will conflict with your Astro \`trailinglSlash\` configuration (set to \`"always"\`).\n` +
// biome-ignore lint/style/noUnusedTemplateLiteral: <explanation>
`\tThis would cause infinite redirects under certain conditions and throw an \`ERR_TOO_MANY_REDIRECTS\` error.\n` +
`\tTo prevent this, your Astro configuration is updated to \`"ignore"\` during builds.\n`
`\tTo prevent this, change your Astro configuration and update \`"trailingSlash"\` to \`"ignore"\`.\n`
);
updateConfig({
trailingSlash: 'ignore',
});
}
} catch (_err) {
logger.warn(`Your "vercel.json" config is not a valid json file.`);
}
}

updateConfig({
outDir: new URL('./.vercel/output/', config.root),
build: {
client: new URL('./.vercel/output/static/', config.root),
server: new URL('./.vercel/output/_functions/', config.root),
redirects: false,
},
vite: {
ssr: {
external: ['@vercel/nft'],
},
},
...getAstroImageConfig(
imageService,
imagesConfig,
command,
devImageService,
config.image
),
});
} else {
const outDir = new URL('./.vercel/output/static/', config.root);
updateConfig({
outDir,
build: {
format: 'directory',
redirects: false,
},
...getAstroImageConfig(
imageService,
imagesConfig,
command,
devImageService,
config.image
),
});
}
},
'astro:config:done': ({ setAdapter, config }) => {
if (buildOutput === 'server') {
setAdapter(getAdapter({ buildOutput, edgeMiddleware, middlewareSecret, skewProtection }));
} else {
setAdapter(
Expand All @@ -292,20 +272,37 @@ export default function vercelAdapter({
_serverEntry = config.build.serverEntry;
},
'astro:build:start': async () => {
if (buildOutput !== 'static') {
// Ensure to have `.vercel/output` empty.
// This is because, when building to static, outDir = .vercel/output/static/,
// so .vercel/output itself won't get cleaned.
await emptyDir(new URL('./.vercel/output/', _config.root));
}
// Ensure to have `.vercel/output` empty.
await emptyDir(new URL('./.vercel/output/', _config.root));
},
'astro:build:ssr': async ({ entryPoints, middlewareEntryPoint }) => {
_entryPoints = new Map(
Array.from(entryPoints).filter(([routeData]) => !routeData.prerender)
);
_middlewareEntryPoint = middlewareEntryPoint;
},
'astro:build:done': async ({ routes, logger }) => {
'astro:build:done': async ({ routes, logger }: HookParameters<'astro:build:done'>) => {
const outDir = new URL('./.vercel/output/', _config.root);
if (staticDir) {
if (existsSync(staticDir)) {
emptyDir(staticDir);
}
mkdirSync(new URL('./.vercel/output/static/', _config.root), { recursive: true });

if (buildOutput === 'static' && staticDir) {
cpSync(_config.outDir, new URL('./.vercel/output/static/', _config.root), {
recursive: true,
});
} else {
cpSync(_config.build.client, new URL('./.vercel/output/static/', _config.root), {
recursive: true,
});
cpSync(_config.build.server, new URL('./.vercel/output/_functions/', _config.root), {
recursive: true,
});
}
}

const routeDefinitions: Array<{
src: string;
dest: string;
Expand Down Expand Up @@ -339,6 +336,7 @@ export default function vercelAdapter({
excludeFiles,
includeFiles,
logger,
outDir,
maxDuration
);

Expand Down Expand Up @@ -399,10 +397,7 @@ export default function vercelAdapter({
}
}
const fourOhFourRoute = routes.find((route) => route.pathname === '/404');
const destination =
buildOutput === 'server'
? new URL('./config.json', _config.outDir)
: new URL('./.vercel/output/config.json', _config.root);
const destination = new URL('./.vercel/output/config.json', _config.root);
const finalRoutes = [
...getRedirects(routes, _config),
{
Expand Down Expand Up @@ -495,16 +490,17 @@ class VercelBuilder {
readonly excludeFiles: URL[],
readonly includeFiles: URL[],
readonly logger: AstroIntegrationLogger,
readonly outDir: URL,
readonly maxDuration?: number,
readonly runtime = getRuntime(process, logger)
) {}

async buildServerlessFolder(entry: URL, functionName: string, root: URL) {
const { config, includeFiles, excludeFiles, logger, NTF_CACHE, runtime, maxDuration } = this;
// .vercel/output/functions/<name>.func/
const functionFolder = new URL(`./functions/${functionName}.func/`, config.outDir);
const packageJson = new URL(`./functions/${functionName}.func/package.json`, config.outDir);
const vcConfig = new URL(`./functions/${functionName}.func/.vc-config.json`, config.outDir);
const functionFolder = new URL(`./functions/${functionName}.func/`, this.outDir);
const packageJson = new URL(`./functions/${functionName}.func/package.json`, this.outDir);
const vcConfig = new URL(`./functions/${functionName}.func/.vc-config.json`, this.outDir);

// Copy necessary files (e.g. node_modules/)
const { handler } = await copyDependenciesToFunction(
Expand Down Expand Up @@ -538,7 +534,7 @@ class VercelBuilder {
await this.buildServerlessFolder(entry, functionName, root);
const prerenderConfig = new URL(
`./functions/${functionName}.prerender-config.json`,
this.config.outDir
this.outDir
);
// https://vercel.com/docs/build-output-api/v3/primitives#prerender-configuration-file
await writeJson(prerenderConfig, {
Expand All @@ -550,7 +546,7 @@ class VercelBuilder {
}

async buildMiddlewareFolder(entry: URL, functionName: string, middlewareSecret: string) {
const functionFolder = new URL(`./functions/${functionName}.func/`, this.config.outDir);
const functionFolder = new URL(`./functions/${functionName}.func/`, this.outDir);

await generateEdgeMiddleware(
entry,
Expand Down
8 changes: 6 additions & 2 deletions packages/vercel/test/serverless-prerender.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,12 @@ describe('Serverless prerender', () => {
const [file] = await fixture.glob(
'../.vercel/output/functions/_render.func/packages/vercel/test/fixtures/serverless-prerender/.vercel/output/_functions/pages/_image.astro.mjs'
);
const contents = await fixture.readFile(file);
assert.ok(!contents.includes('const outDir ='), "outDir is tree-shaken if it's not imported");
try {
await fixture.readFile(file);
assert.fail();
} catch {
assert.ok('Function do be three-shaken');
}
});

// TODO: The path here seems to be inconsistent?
Expand Down
Loading