diff options
Diffstat (limited to 'packages/integrations/vercel/src')
9 files changed, 77 insertions, 177 deletions
diff --git a/packages/integrations/vercel/src/image/shared.ts b/packages/integrations/vercel/src/image/shared.ts index dfae0d06f..21f101912 100644 --- a/packages/integrations/vercel/src/image/shared.ts +++ b/packages/integrations/vercel/src/image/shared.ts @@ -13,7 +13,7 @@ export function isESMImportedImage(src: ImageMetadata | string): src is ImageMet return typeof src === 'object'; } -export type DevImageService = 'sharp' | 'squoosh' | (string & {}); +export type DevImageService = 'sharp' | (string & {}); // https://vercel.com/docs/build-output-api/v3/configuration#images type ImageFormat = 'image/avif' | 'image/webp'; @@ -76,9 +76,6 @@ export function getAstroImageConfig( case 'sharp': devService = '@astrojs/vercel/dev-image-service'; break; - case 'squoosh': - devService = '@astrojs/vercel/squoosh-dev-image-service'; - break; default: if (typeof devImageService === 'string') { devService = devImageService; diff --git a/packages/integrations/vercel/src/image/squoosh-dev-service.ts b/packages/integrations/vercel/src/image/squoosh-dev-service.ts deleted file mode 100644 index d3b05bb11..000000000 --- a/packages/integrations/vercel/src/image/squoosh-dev-service.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { LocalImageService } from 'astro'; -import squooshService from 'astro/assets/services/squoosh'; -import { baseDevService } from './shared-dev-service.js'; - -const service: LocalImageService = { - ...baseDevService, - getHTMLAttributes(options, serviceOptions) { - const { inputtedWidth, ...props } = options; - - // If `validateOptions` returned a different width than the one of the image, use it for attributes - if (inputtedWidth) { - props.width = inputtedWidth; - } - - return squooshService.getHTMLAttributes - ? squooshService.getHTMLAttributes(props, serviceOptions) - : {}; - }, - transform(inputBuffer, transform, serviceOptions) { - // NOTE: Hardcoding webp here isn't accurate to how the Vercel Image Optimization API works, normally what we should - // do is setup a custom endpoint that sniff the user's accept-content header and serve the proper format based on the - // user's Vercel config. However, that's: a lot of work for: not much. The dev service is inaccurate to the prod service - // in many more ways, this is one of the less offending cases and is, imo, okay, erika - 2023-04-27 - transform.format = transform.src.endsWith('svg') ? 'svg' : 'webp'; - - // The base squoosh service works the same way as the Vercel Image Optimization API, so it's a safe fallback in local - return squooshService.transform(inputBuffer, transform, serviceOptions); - }, -}; - -export default service; diff --git a/packages/integrations/vercel/src/lib/prerender.ts b/packages/integrations/vercel/src/lib/prerender.ts deleted file mode 100644 index f69f3b5d4..000000000 --- a/packages/integrations/vercel/src/lib/prerender.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { AstroConfig } from 'astro'; - -export function isServerLikeOutput(config: AstroConfig) { - return config.output === 'server' || config.output === 'hybrid'; -} diff --git a/packages/integrations/vercel/src/lib/redirects.ts b/packages/integrations/vercel/src/lib/redirects.ts index 59a006886..b11a5b2cc 100644 --- a/packages/integrations/vercel/src/lib/redirects.ts +++ b/packages/integrations/vercel/src/lib/redirects.ts @@ -1,6 +1,6 @@ import nodePath from 'node:path'; import { appendForwardSlash, removeLeadingForwardSlash } from '@astrojs/internal-helpers/path'; -import type { AstroConfig, RouteData, RoutePart } from 'astro'; +import type { AstroConfig, IntegrationRouteData, RoutePart } from 'astro'; const pathJoin = nodePath.posix.join; @@ -49,19 +49,19 @@ function getMatchPattern(segments: RoutePart[][]) { return segment[0].spread ? '(?:\\/(.*?))?' : segment - .map((part) => { - if (part) - return part.dynamic - ? '([^/]+?)' - : part.content - .normalize() - .replace(/\?/g, '%3F') - .replace(/#/g, '%23') - .replace(/%5B/g, '[') - .replace(/%5D/g, ']') - .replace(/[*+?^${}()|[\]\\]/g, '\\$&'); - }) - .join(''); + .map((part) => { + if (part) + return part.dynamic + ? '([^/]+?)' + : part.content + .normalize() + .replace(/\?/g, '%3F') + .replace(/#/g, '%23') + .replace(/%5B/g, '[') + .replace(/%5D/g, ']') + .replace(/[*+?^${}()|[\]\\]/g, '\\$&'); + }) + .join(''); }) .join('/'); } @@ -85,7 +85,7 @@ function getReplacePattern(segments: RoutePart[][]) { return result; } -function getRedirectLocation(route: RouteData, config: AstroConfig): string { +function getRedirectLocation(route: IntegrationRouteData, config: AstroConfig): string { if (route.redirectRoute) { const pattern = getReplacePattern(route.redirectRoute.segments); const path = config.trailingSlash === 'always' ? appendForwardSlash(pattern) : pattern; @@ -99,7 +99,7 @@ function getRedirectLocation(route: RouteData, config: AstroConfig): string { } } -function getRedirectStatus(route: RouteData): number { +function getRedirectStatus(route: IntegrationRouteData): number { if (typeof route.redirect === 'object') { return route.redirect.status; } @@ -116,7 +116,7 @@ export function escapeRegex(content: string) { return `^/${getMatchPattern(segments)}$`; } -export function getRedirects(routes: RouteData[], config: AstroConfig): VercelRoute[] { +export function getRedirects(routes: IntegrationRouteData[], config: AstroConfig): VercelRoute[] { const redirects: VercelRoute[] = []; for (const route of routes) { diff --git a/packages/integrations/vercel/src/serverless/adapter.ts b/packages/integrations/vercel/src/serverless/adapter.ts index e50bd4190..f45743b03 100644 --- a/packages/integrations/vercel/src/serverless/adapter.ts +++ b/packages/integrations/vercel/src/serverless/adapter.ts @@ -7,7 +7,7 @@ import type { AstroConfig, AstroIntegration, AstroIntegrationLogger, - RouteData, + IntegrationRouteData, } from 'astro'; import { AstroError } from 'astro/errors'; import glob from 'fast-glob'; @@ -70,12 +70,10 @@ const SUPPORTED_NODE_VERSIONS: Record< function getAdapter({ edgeMiddleware, - functionPerRoute, middlewareSecret, skewProtection, }: { edgeMiddleware: boolean; - functionPerRoute: boolean; middlewareSecret: string; skewProtection: boolean; }): AstroAdapter { @@ -86,19 +84,15 @@ function getAdapter({ args: { middlewareSecret, skewProtection }, adapterFeatures: { edgeMiddleware, - functionPerRoute, + buildOutput: 'server', }, supportedAstroFeatures: { hybridOutput: 'stable', staticOutput: 'stable', serverOutput: 'stable', - assets: { - supportKind: 'stable', - isSharpCompatible: true, - isSquooshCompatible: true, - }, + sharpImageService: 'stable', i18nDomains: 'experimental', - envGetSecret: 'experimental', + envGetSecret: 'stable', }, }; } @@ -134,12 +128,6 @@ export interface VercelServerlessConfig { /** Whether to create the Vercel Edge middleware from an Astro middleware in your code base. */ edgeMiddleware?: boolean; - /** - * Whether to split builds into a separate function for each route. - * @deprecated `functionPerRoute` is deprecated and will be removed in the next major release of the adapter. - */ - functionPerRoute?: boolean; - /** The maximum duration (in seconds) that Serverless Functions can run before timing out. See the [Vercel documentation](https://vercel.com/docs/functions/serverless-functions/runtimes#maxduration) for the default and maximum limit for your account plan. */ maxDuration?: number; @@ -186,7 +174,6 @@ export default function vercelServerless({ imageService, imagesConfig, devImageService = 'sharp', - functionPerRoute = false, edgeMiddleware = false, maxDuration, isr = false, @@ -206,7 +193,7 @@ export default function vercelServerless({ let _config: AstroConfig; let _buildTempFolder: URL; let _serverEntry: string; - let _entryPoints: Map<RouteData, URL>; + let _entryPoints: Map<IntegrationRouteData, URL>; let _middlewareEntryPoint: URL | undefined; // Extra files to be merged with `includeFiles` during build const extraFilesToInclude: URL[] = []; @@ -246,10 +233,10 @@ export default function vercelServerless({ if (vercelConfig.trailingSlash === true && config.trailingSlash === 'always') { logger.warn( '\n' + - `\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` + `\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` ); updateConfig({ trailingSlash: 'ignore', @@ -270,10 +257,7 @@ export default function vercelServerless({ vite: { ...getSpeedInsightsViteConfig(speedInsights?.enabled), ssr: { - external: [ - '@vercel/nft', - ...((await shouldExternalizeAstroEnvSetup()) ? ['astro/env/setup'] : []), - ], + external: ['@vercel/nft'], }, }, ...getAstroImageConfig( @@ -285,39 +269,12 @@ export default function vercelServerless({ ), }); }, - 'astro:config:done': ({ setAdapter, config, logger }) => { - if (functionPerRoute === true) { - logger.warn( - // biome-ignore lint/style/noUnusedTemplateLiteral: <explanation> - `\n` + - `\tVercel's hosting plans might have limits to the number of functions you can create.\n` + - // biome-ignore lint/style/noUnusedTemplateLiteral: <explanation> - `\tMake sure to check your plan carefully to avoid incurring additional costs.\n` + - // biome-ignore lint/style/noUnusedTemplateLiteral: <explanation> - `\tYou can set functionPerRoute: false to prevent surpassing the limit.\n` - ); - - logger.warn( - // biome-ignore lint/style/noUnusedTemplateLiteral: <explanation> - `\n` + - // biome-ignore lint/style/noUnusedTemplateLiteral: <explanation> - `\t\`functionPerRoute\` is deprecated and will be removed in a future version of the adapter.\n` - ); - } - - setAdapter( - getAdapter({ functionPerRoute, edgeMiddleware, middlewareSecret, skewProtection }) - ); + 'astro:config:done': ({ setAdapter, config }) => { + setAdapter(getAdapter({ edgeMiddleware, middlewareSecret, skewProtection })); _config = config; _buildTempFolder = config.build.server; _serverEntry = config.build.serverEntry; - - if (config.output === 'static') { - throw new AstroError( - '`output: "server"` or `output: "hybrid"` is required to use the serverless adapter.' - ); - } }, 'astro:build:ssr': async ({ entryPoints, middlewareEntryPoint }) => { _entryPoints = new Map( @@ -357,7 +314,8 @@ export default function vercelServerless({ // Multiple entrypoint support if (_entryPoints.size) { - const getRouteFuncName = (route: RouteData) => route.component.replace('src/pages/', ''); + const getRouteFuncName = (route: IntegrationRouteData) => + route.component.replace('src/pages/', ''); const getFallbackFuncName = (entryFile: URL) => basename(entryFile.toString()) @@ -426,31 +384,31 @@ export default function vercelServerless({ ...routeDefinitions, ...(fourOhFourRoute ? [ - { - src: '/.*', - dest: fourOhFourRoute.prerender - ? '/404.html' - : _middlewareEntryPoint - ? MIDDLEWARE_PATH - : NODE_PATH, - status: 404, - }, - ] + { + src: '/.*', + dest: fourOhFourRoute.prerender + ? '/404.html' + : _middlewareEntryPoint + ? MIDDLEWARE_PATH + : NODE_PATH, + status: 404, + }, + ] : []), ], ...(imageService || imagesConfig ? { - images: imagesConfig - ? { - ...imagesConfig, - domains: [...imagesConfig.domains, ..._config.image.domains], - remotePatterns: [ - ...(imagesConfig.remotePatterns ?? []), - ..._config.image.remotePatterns, - ], - } - : getDefaultImageConfig(_config.image), - } + images: imagesConfig + ? { + ...imagesConfig, + domains: [...imagesConfig.domains, ..._config.image.domains], + remotePatterns: [ + ...(imagesConfig.remotePatterns ?? []), + ..._config.image.remotePatterns, + ], + } + : getDefaultImageConfig(_config.image), + } : {}), }); @@ -463,16 +421,6 @@ export default function vercelServerless({ type Runtime = `nodejs${string}.x`; -// TODO: remove once we don't use a TLA anymore -async function shouldExternalizeAstroEnvSetup() { - try { - await import('astro/env/setup'); - return false; - } catch { - return true; - } -} - class VercelBuilder { readonly NTF_CACHE = {}; @@ -483,7 +431,7 @@ class VercelBuilder { readonly logger: AstroIntegrationLogger, 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; @@ -561,14 +509,14 @@ function getRuntime(process: NodeJS.Process, logger: AstroIntegrationLogger): Ru const support = SUPPORTED_NODE_VERSIONS[major]; if (support === undefined) { logger.warn( - // biome-ignore lint/style/noUnusedTemplateLiteral: <explanation> // biome-ignore lint/style/useTemplate: <explanation> + // biome-ignore lint/style/noUnusedTemplateLiteral: <explanation> `\n` + - `\tThe local Node.js version (${major}) is not supported by Vercel Serverless Functions.\n` + - // biome-ignore lint/style/noUnusedTemplateLiteral: <explanation> - `\tYour project will use Node.js 18 as the runtime instead.\n` + - // biome-ignore lint/style/noUnusedTemplateLiteral: <explanation> - `\tConsider switching your local version to 18.\n` + `\tThe local Node.js version (${major}) is not supported by Vercel Serverless Functions.\n` + + // biome-ignore lint/style/noUnusedTemplateLiteral: <explanation> + `\tYour project will use Node.js 18 as the runtime instead.\n` + + // biome-ignore lint/style/noUnusedTemplateLiteral: <explanation> + `\tConsider switching your local version to 18.\n` ); return 'nodejs18.x'; } @@ -594,13 +542,13 @@ function getRuntime(process: NodeJS.Process, logger: AstroIntegrationLogger): Ru support.removal ); logger.warn( - // biome-ignore lint/style/noUnusedTemplateLiteral: <explanation> // biome-ignore lint/style/useTemplate: <explanation> + // biome-ignore lint/style/noUnusedTemplateLiteral: <explanation> `\n` + - `\tYour project is being built for Node.js ${major} as the runtime.\n` + - `\tThis version is deprecated by Vercel Serverless Functions, and scheduled to be disabled on ${removeDate}.\n` + - // biome-ignore lint/style/noUnusedTemplateLiteral: <explanation> - `\tConsider upgrading your local version to 18.\n` + `\tYour project is being built for Node.js ${major} as the runtime.\n` + + `\tThis version is deprecated by Vercel Serverless Functions, and scheduled to be disabled on ${removeDate}.\n` + + // biome-ignore lint/style/noUnusedTemplateLiteral: <explanation> + `\tConsider upgrading your local version to 18.\n` ); return `nodejs${major}.x`; } diff --git a/packages/integrations/vercel/src/serverless/entrypoint.ts b/packages/integrations/vercel/src/serverless/entrypoint.ts index 31f78bb8a..01279a2b4 100644 --- a/packages/integrations/vercel/src/serverless/entrypoint.ts +++ b/packages/integrations/vercel/src/serverless/entrypoint.ts @@ -1,6 +1,10 @@ +// Keep at the top +import './polyfill.js'; + import type { IncomingMessage, ServerResponse } from 'node:http'; import type { SSRManifest } from 'astro'; -import { NodeApp, applyPolyfills } from 'astro/app/node'; +import { NodeApp } from 'astro/app/node'; +import { setGetEnv } from 'astro/env/setup'; import { ASTRO_LOCALS_HEADER, ASTRO_MIDDLEWARE_SECRET_HEADER, @@ -8,14 +12,7 @@ import { ASTRO_PATH_PARAM, } from './adapter.js'; -// Run polyfills immediately so any dependent code can use the globals -applyPolyfills(); - -// Won't throw if the virtual module is not available because it's not supported in -// the users's astro version or if astro:env is not enabled in the project -await import('astro/env/setup') - .then((mod) => mod.setGetEnv((key) => process.env[key])) - .catch(() => {}); +setGetEnv((key) => process.env[key]); export const createExports = ( manifest: SSRManifest, diff --git a/packages/integrations/vercel/src/serverless/middleware.ts b/packages/integrations/vercel/src/serverless/middleware.ts index ca84bff33..973df238f 100644 --- a/packages/integrations/vercel/src/serverless/middleware.ts +++ b/packages/integrations/vercel/src/serverless/middleware.ts @@ -98,7 +98,7 @@ export default async function middleware(request, context) { request, params: {} }); - ctx.locals = { vercel: { edge: context }, ...${handlerTemplateCall} }; + Object.assign(ctx.locals, { vercel: { edge: context }, ...${handlerTemplateCall} }); const { origin } = new URL(request.url); const next = async () => { const { vercel, ...locals } = ctx.locals; diff --git a/packages/integrations/vercel/src/serverless/polyfill.ts b/packages/integrations/vercel/src/serverless/polyfill.ts new file mode 100644 index 000000000..dc00f45d7 --- /dev/null +++ b/packages/integrations/vercel/src/serverless/polyfill.ts @@ -0,0 +1,3 @@ +import { applyPolyfills } from 'astro/app/node'; + +applyPolyfills(); diff --git a/packages/integrations/vercel/src/static/adapter.ts b/packages/integrations/vercel/src/static/adapter.ts index 4a5bdf966..458143215 100644 --- a/packages/integrations/vercel/src/static/adapter.ts +++ b/packages/integrations/vercel/src/static/adapter.ts @@ -7,7 +7,6 @@ import { getAstroImageConfig, getDefaultImageConfig, } from '../image/shared.js'; -import { isServerLikeOutput } from '../lib/prerender.js'; import { getRedirects } from '../lib/redirects.js'; import { type VercelSpeedInsightsConfig, @@ -24,19 +23,15 @@ function getAdapter(): AstroAdapter { return { name: PACKAGE_NAME, supportedAstroFeatures: { - assets: { - supportKind: 'stable', - isSquooshCompatible: true, - isSharpCompatible: true, - }, + sharpImageService: 'stable', staticOutput: 'stable', serverOutput: 'unsupported', hybridOutput: 'unsupported', envGetSecret: 'unsupported', }, adapterFeatures: { + buildOutput: 'static', edgeMiddleware: false, - functionPerRoute: false, }, }; } @@ -102,10 +97,6 @@ export default function vercelStatic({ 'astro:config:done': ({ setAdapter, config }) => { setAdapter(getAdapter()); _config = config; - - if (isServerLikeOutput(config)) { - throw new Error(`${PACKAGE_NAME} should be used with output: 'static'`); - } }, 'astro:build:start': async () => { // Ensure to have `.vercel/output` empty. |