Remove functionPerRoute option (#11714)

* Remove functionPerRoute option

* Remove more code

* Remove unused test util

* Linting

* Update tests to reflect new structure

* Add a changeset

* Update plugin

* Remove unused import
This commit is contained in:
Matthew Phillips 2024-08-19 10:31:55 -04:00 committed by GitHub
parent 3822e574aa
commit 8a5351737d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 40 additions and 543 deletions

View file

@ -0,0 +1,14 @@
---
'@astrojs/vercel': major
'astro': major
---
Remove support for functionPerRoute
This change removes support for the `functionPerRoute` option both in Astro and `@astrojs/vercel`.
This option made it so that each route got built as separate entrypoints so that they could be loaded as separate functions. The hope was that by doing this it would decrease the size of each function. However in practice routes use most of the same code, and increases in function size limitations made the potential upsides less important.
Additionally there are downsides to functionPerRoute, such as hitting limits on the number of functions per project. The feature also never worked with some Astro features like i18n domains and request rewriting.
Given this, the feature has been removed from Astro.

View file

@ -36,7 +36,7 @@ import { createRequest } from '../request.js';
import { matchRoute } from '../routing/match.js';
import { stringifyParams } from '../routing/params.js';
import { getOutputFilename, isServerLikeOutput } from '../util.js';
import { getOutDirWithinCwd, getOutFile, getOutFolder } from './common.js';
import { getOutFile, getOutFolder } from './common.js';
import { cssOrder, mergeInlineCss } from './internal.js';
import { BuildPipeline } from './pipeline.js';
import type {
@ -47,10 +47,6 @@ import type {
} from './types.js';
import { getTimeStat, shouldAppendForwardSlash } from './util.js';
function createEntryURL(filePath: string, outFolder: URL) {
return new URL('./' + filePath + `?time=${Date.now()}`, outFolder);
}
export async function generatePages(options: StaticBuildOptions, internals: BuildInternals) {
const generatePagesTimer = performance.now();
const ssr = isServerLikeOutput(options.settings.config);
@ -80,10 +76,6 @@ export async function generatePages(options: StaticBuildOptions, internals: Buil
const pipeline = BuildPipeline.create({ internals, manifest, options });
const { config, logger } = pipeline;
const outFolder = ssr
? options.settings.config.build.server
: getOutDirWithinCwd(options.settings.config.outDir);
// HACK! `astro:assets` relies on a global to know if its running in dev, prod, ssr, ssg, full moon
// If we don't delete it here, it's technically not impossible (albeit improbable) for it to leak
if (ssr && !hasPrerenderedPages(internals)) {
@ -107,24 +99,11 @@ export async function generatePages(options: StaticBuildOptions, internals: Buil
}
const ssrEntryPage = await pipeline.retrieveSsrEntry(pageData.route, filePath);
if (options.settings.adapter?.adapterFeatures?.functionPerRoute) {
// forcing to use undefined, so we fail in an expected way if the module is not even there.
// @ts-expect-error When building for `functionPerRoute`, the module exports a `pageModule` function instead
const ssrEntry = ssrEntryPage?.pageModule;
if (ssrEntry) {
await generatePage(pageData, ssrEntry, builtPaths, pipeline);
} else {
const ssrEntryURLPage = createEntryURL(filePath, outFolder);
throw new Error(
`Unable to find the manifest for the module ${ssrEntryURLPage.toString()}. This is unexpected and likely a bug in Astro, please report.`,
);
}
} else {
const ssrEntry = ssrEntryPage as SinglePageBuiltModule;
await generatePage(pageData, ssrEntry, builtPaths, pipeline);
}
}
}
} else {
for (const [pageData, filePath] of pagesToGenerate) {
const entry = await pipeline.retrieveSsrEntry(pageData.route, filePath);

View file

@ -18,7 +18,6 @@ import { isServerLikeOutput } from '../util.js';
import { getOutDirWithinCwd } from './common.js';
import { type BuildInternals, cssOrder, getPageData, mergeInlineCss } from './internal.js';
import { ASTRO_PAGE_MODULE_ID, ASTRO_PAGE_RESOLVED_MODULE_ID } from './plugins/plugin-pages.js';
import { RESOLVED_SPLIT_MODULE_ID } from './plugins/plugin-ssr.js';
import { getPagesFromVirtualModulePageName, getVirtualModulePageName } from './plugins/util.js';
import type { PageBuildData, SinglePageBuiltModule, StaticBuildOptions } from './types.js';
import { i18nHasFallback } from './util.js';
@ -199,15 +198,11 @@ export class BuildPipeline extends Pipeline {
const pages = new Map<PageBuildData, string>();
for (const [virtualModulePageName, filePath] of this.internals.entrySpecifierToBundleMap) {
// virtual pages can be emitted with different prefixes:
// - the classic way are pages emitted with prefix ASTRO_PAGE_RESOLVED_MODULE_ID -> plugin-pages
// - pages emitted using `functionPerRoute`, in this case pages are emitted with prefix RESOLVED_SPLIT_MODULE_ID
// virtual pages are emitted with the 'plugin-pages' prefix
if (
virtualModulePageName.includes(ASTRO_PAGE_RESOLVED_MODULE_ID) ||
virtualModulePageName.includes(RESOLVED_SPLIT_MODULE_ID)
virtualModulePageName.includes(ASTRO_PAGE_RESOLVED_MODULE_ID)
) {
let pageDatas: PageBuildData[] = [];
if (virtualModulePageName.includes(ASTRO_PAGE_RESOLVED_MODULE_ID)) {
pageDatas.push(
...getPagesFromVirtualModulePageName(
this.internals,
@ -215,16 +210,6 @@ export class BuildPipeline extends Pipeline {
virtualModulePageName,
),
);
}
if (virtualModulePageName.includes(RESOLVED_SPLIT_MODULE_ID)) {
pageDatas.push(
...getPagesFromVirtualModulePageName(
this.internals,
RESOLVED_SPLIT_MODULE_ID,
virtualModulePageName,
),
);
}
for (const pageData of pageDatas) {
pages.set(pageData, filePath);
}
@ -306,9 +291,9 @@ export class BuildPipeline extends Pipeline {
}
let entry;
if (routeIsRedirect(route)) {
entry = await this.#getEntryForRedirectRoute(route, this.internals, this.outFolder);
entry = await this.#getEntryForRedirectRoute(route, this.outFolder);
} else if (routeIsFallback(route)) {
entry = await this.#getEntryForFallbackRoute(route, this.internals, this.outFolder);
entry = await this.#getEntryForFallbackRoute(route, this.outFolder);
} else {
const ssrEntryURLPage = createEntryURL(filePath, this.outFolder);
entry = await import(ssrEntryURLPage.toString());
@ -319,7 +304,6 @@ export class BuildPipeline extends Pipeline {
async #getEntryForFallbackRoute(
route: RouteData,
internals: BuildInternals,
outFolder: URL,
): Promise<SinglePageBuiltModule> {
if (route.type !== 'fallback') {
@ -339,7 +323,6 @@ export class BuildPipeline extends Pipeline {
async #getEntryForRedirectRoute(
route: RouteData,
internals: BuildInternals,
outFolder: URL,
): Promise<SinglePageBuiltModule> {
if (route.type !== 'redirect') {

View file

@ -14,7 +14,7 @@ import { pluginPages } from './plugin-pages.js';
import { pluginPrerender } from './plugin-prerender.js';
import { pluginRenderers } from './plugin-renderers.js';
import { pluginScripts } from './plugin-scripts.js';
import { pluginSSR, pluginSSRSplit } from './plugin-ssr.js';
import { pluginSSR } from './plugin-ssr.js';
export function registerAllPlugins({ internals, options, register }: AstroBuildPluginContainer) {
register(pluginComponentEntry(internals));
@ -35,6 +35,5 @@ export function registerAllPlugins({ internals, options, register }: AstroBuildP
register(pluginHoistedScripts(options, internals));
}
register(pluginSSR(options, internals));
register(pluginSSRSplit(options, internals));
register(pluginChunks());
}

View file

@ -54,21 +54,6 @@ function getNonPrerenderOnlyChunks(bundle: Rollup.OutputBundle, internals: Build
continue;
}
}
// Ideally we should record entries when `functionPerRoute` is enabled, but this breaks some tests
// that expect the entrypoint to still exist even if it should be unused.
// TODO: Revisit this so we can delete additional unused chunks
// else if (chunk.facadeModuleId?.startsWith(RESOLVED_SPLIT_MODULE_ID)) {
// const pageDatas = getPagesFromVirtualModulePageName(
// internals,
// RESOLVED_SPLIT_MODULE_ID,
// chunk.facadeModuleId
// );
// const prerender = pageDatas.every((pageData) => pageData.route.prerender);
// if (prerender) {
// prerenderOnlyEntryChunks.add(chunk);
// continue;
// }
// }
nonPrerenderOnlyEntryChunks.add(chunk);
}

View file

@ -1,7 +1,3 @@
import { join } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import type { Plugin as VitePlugin } from 'vite';
import { isFunctionPerRouteEnabled } from '../../../integrations/hooks.js';
import type { AstroSettings } from '../../../types/astro.js';
import type { AstroAdapter } from '../../../types/public/integrations.js';
import { routeIsRedirect } from '../../redirects/index.js';
@ -15,7 +11,8 @@ import { SSR_MANIFEST_VIRTUAL_MODULE_ID } from './plugin-manifest.js';
import { MIDDLEWARE_MODULE_ID } from './plugin-middleware.js';
import { ASTRO_PAGE_MODULE_ID } from './plugin-pages.js';
import { RENDERERS_MODULE_ID } from './plugin-renderers.js';
import { getComponentFromVirtualModulePageName, getVirtualModulePageName } from './util.js';
import { getVirtualModulePageName } from './util.js';
import type { Plugin as VitePlugin } from 'vite';
export const SSR_VIRTUAL_MODULE_ID = '@astrojs-ssr-virtual-entry';
export const RESOLVED_SSR_VIRTUAL_MODULE_ID = '\0' + SSR_VIRTUAL_MODULE_ID;
@ -135,16 +132,12 @@ export function pluginSSR(
internals: BuildInternals,
): AstroBuildPlugin {
const ssr = isServerLikeOutput(options.settings.config);
const functionPerRouteEnabled = isFunctionPerRouteEnabled(options.settings.adapter);
return {
targets: ['server'],
hooks: {
'build:before': () => {
const adapter = options.settings.adapter!;
let ssrPlugin =
ssr && functionPerRouteEnabled === false
? vitePluginSSR(internals, adapter, options)
: undefined;
const ssrPlugin = ssr && vitePluginSSR(internals, adapter, options)
const vitePlugin = [vitePluginAdapter(adapter)];
if (ssrPlugin) {
vitePlugin.unshift(ssrPlugin);
@ -160,10 +153,6 @@ export function pluginSSR(
return;
}
if (functionPerRouteEnabled) {
return;
}
if (!internals.ssrEntryChunk) {
throw new Error(`Did not generate an entry chunk for SSR`);
}
@ -174,111 +163,8 @@ export function pluginSSR(
};
}
export const SPLIT_MODULE_ID = '@astro-page-split:';
export const RESOLVED_SPLIT_MODULE_ID = '\0@astro-page-split:';
function vitePluginSSRSplit(
internals: BuildInternals,
adapter: AstroAdapter,
options: StaticBuildOptions,
): VitePlugin {
return {
name: '@astrojs/vite-plugin-astro-ssr-split',
enforce: 'post',
options(opts) {
const inputs = new Set<string>();
for (const pageData of Object.values(options.allPages)) {
if (routeIsRedirect(pageData.route)) {
continue;
}
inputs.add(getVirtualModulePageName(SPLIT_MODULE_ID, pageData.component));
}
return addRollupInput(opts, Array.from(inputs));
},
resolveId(id) {
if (id.startsWith(SPLIT_MODULE_ID)) {
return '\0' + id;
}
},
async load(id) {
if (id.startsWith(RESOLVED_SPLIT_MODULE_ID)) {
const imports: string[] = [];
const contents: string[] = [];
const exports: string[] = [];
const componentPath = getComponentFromVirtualModulePageName(RESOLVED_SPLIT_MODULE_ID, id);
const virtualModuleName = getVirtualModulePageName(ASTRO_PAGE_MODULE_ID, componentPath);
let module = await this.resolve(virtualModuleName);
if (module) {
// we need to use the non-resolved ID in order to resolve correctly the virtual module
imports.push(`import * as pageModule from "${virtualModuleName}";`);
}
const middleware = await this.resolve(MIDDLEWARE_MODULE_ID);
const ssrCode = generateSSRCode(options.settings, adapter, middleware!.id);
imports.push(...ssrCode.imports);
contents.push(...ssrCode.contents);
exports.push('export { pageModule }');
return [...imports, ...contents, ...exports].join('\n');
}
},
async generateBundle(_opts, bundle) {
// Add assets from this SSR chunk as well.
for (const [, chunk] of Object.entries(bundle)) {
if (chunk.type === 'asset') {
internals.staticFiles.add(chunk.fileName);
}
}
for (const [, chunk] of Object.entries(bundle)) {
if (chunk.type === 'asset') {
continue;
}
for (const moduleKey of Object.keys(chunk.modules)) {
if (moduleKey.startsWith(RESOLVED_SPLIT_MODULE_ID)) {
storeEntryPoint(moduleKey, options, internals, chunk.fileName);
}
}
}
},
};
}
export function pluginSSRSplit(
options: StaticBuildOptions,
internals: BuildInternals,
): AstroBuildPlugin {
const ssr = isServerLikeOutput(options.settings.config);
const functionPerRouteEnabled = isFunctionPerRouteEnabled(options.settings.adapter);
return {
targets: ['server'],
hooks: {
'build:before': () => {
const adapter = options.settings.adapter!;
let ssrPlugin =
ssr && functionPerRouteEnabled
? vitePluginSSRSplit(internals, adapter, options)
: undefined;
const vitePlugin = [vitePluginAdapter(adapter)];
if (ssrPlugin) {
vitePlugin.unshift(ssrPlugin);
}
return {
enforce: 'after-user-plugins',
vitePlugin,
};
},
},
};
}
function generateSSRCode(settings: AstroSettings, adapter: AstroAdapter, middlewareId: string) {
const edgeMiddleware = adapter?.adapterFeatures?.edgeMiddleware ?? false;
const pageMap = isFunctionPerRouteEnabled(adapter) ? 'pageModule' : 'pageMap';
const imports = [
`import { renderers } from '${RENDERERS_MODULE_ID}';`,
@ -294,7 +180,7 @@ function generateSSRCode(settings: AstroSettings, adapter: AstroAdapter, middlew
settings.config.experimental.serverIslands ? '' : `const serverIslandMap = new Map()`,
edgeMiddleware ? `const middleware = (_, next) => next()` : '',
`const _manifest = Object.assign(defaultManifest, {`,
` ${pageMap},`,
` pageMap,`,
` serverIslandMap,`,
` renderers,`,
` middleware`,
@ -325,25 +211,3 @@ if (_start in serverEntrypointModule) {
contents,
};
}
/**
* Because we delete the bundle from rollup at the end of this function,
* we can't use `writeBundle` hook to get the final file name of the entry point written on disk.
* We use this hook instead.
*
* We retrieve all the {@link RouteData} that have the same component as the one we are processing.
*/
function storeEntryPoint(
moduleKey: string,
options: StaticBuildOptions,
internals: BuildInternals,
fileName: string,
) {
const componentPath = getComponentFromVirtualModulePageName(RESOLVED_SPLIT_MODULE_ID, moduleKey);
for (const pageData of Object.values(options.allPages)) {
if (componentPath == pageData.component) {
const publicPath = fileURLToPath(options.settings.config.build.server);
internals.entryPoints.set(pageData.route, pathToFileURL(join(publicPath, fileName)));
}
}
}

View file

@ -1,5 +1,5 @@
import fs from 'node:fs';
import path, { extname } from 'node:path';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { teardown } from '@astrojs/compiler';
import glob from 'fast-glob';
@ -36,7 +36,7 @@ import { copyContentToCache } from './plugins/plugin-content.js';
import { RESOLVED_SSR_MANIFEST_VIRTUAL_MODULE_ID } from './plugins/plugin-manifest.js';
import { ASTRO_PAGE_RESOLVED_MODULE_ID } from './plugins/plugin-pages.js';
import { RESOLVED_RENDERERS_MODULE_ID } from './plugins/plugin-renderers.js';
import { RESOLVED_SPLIT_MODULE_ID, RESOLVED_SSR_VIRTUAL_MODULE_ID } from './plugins/plugin-ssr.js';
import { RESOLVED_SSR_VIRTUAL_MODULE_ID } from './plugins/plugin-ssr.js';
import { ASTRO_PAGE_EXTENSION_POST_PATTERN } from './plugins/util.js';
import type { StaticBuildOptions } from './types.js';
import { encodeName, getTimeStat, viteBuildReturnToRollupOutputs } from './util.js';
@ -247,8 +247,6 @@ async function ssrBuild(
chunkInfo.facadeModuleId,
routes,
);
} else if (chunkInfo.facadeModuleId?.startsWith(RESOLVED_SPLIT_MODULE_ID)) {
return makeSplitEntryPointFileName(chunkInfo.facadeModuleId, routes);
} else if (chunkInfo.facadeModuleId === RESOLVED_SSR_VIRTUAL_MODULE_ID) {
return opts.settings.config.build.serverEntry;
} else if (chunkInfo.facadeModuleId === RESOLVED_RENDERERS_MODULE_ID) {
@ -540,34 +538,3 @@ export function makeAstroPageEntryPointFileName(
.replaceAll(/[[\]]/g, '_')
.replaceAll('...', '---')}.astro.mjs`;
}
/**
* The `facadeModuleId` has a shape like: \0@astro-serverless-page:src/pages/index@_@astro.
*
* 1. We call `makeAstroPageEntryPointFileName` which normalise its name, making it like a file path
* 2. We split the file path using the file system separator and attempt to retrieve the last entry
* 3. The last entry should be the file
* 4. We prepend the file name with `entry.`
* 5. We built the file path again, using the new en3built in the previous step
*
* @param facadeModuleId
* @param opts
*/
export function makeSplitEntryPointFileName(facadeModuleId: string, routes: RouteData[]) {
const filePath = `${makeAstroPageEntryPointFileName(
RESOLVED_SPLIT_MODULE_ID,
facadeModuleId,
routes,
)}`;
const pathComponents = filePath.split(path.sep);
const lastPathComponent = pathComponents.pop();
if (lastPathComponent) {
const extension = extname(lastPathComponent);
if (extension.length > 0) {
const newFileName = `entry.${lastPathComponent}`;
return [...pathComponents, newFileName].join(path.sep);
}
}
return filePath;
}

View file

@ -81,12 +81,6 @@ export function validateSupportedFeatures(
return config?.output === 'server' && !config?.site;
},
);
if (adapterFeatures?.functionPerRoute) {
logger.error(
'config',
'The Astro feature `i18nDomains` is incompatible with the Adapter feature `functionPerRoute`',
);
}
}
validationResult.envGetSecret = validateSupportKind(

View file

@ -13,7 +13,6 @@ import type { AstroSettings } from '../types/astro.js';
import type { AstroConfig } from '../types/public/config.js';
import type { ContentEntryType, DataEntryType } from '../types/public/content.js';
import type {
AstroAdapter,
AstroIntegration,
AstroRenderer,
HookParameters,
@ -619,11 +618,3 @@ export async function runHookRouteSetup({
);
}
}
export function isFunctionPerRouteEnabled(adapter: AstroAdapter | undefined): boolean {
if (adapter?.adapterFeatures?.functionPerRoute === true) {
return true;
} else {
return false;
}
}

View file

@ -66,10 +66,6 @@ export interface AstroAdapterFeatures {
* Creates an edge function that will communiate with the Astro middleware
*/
edgeMiddleware: boolean;
/**
* SSR only. Each route becomes its own function/file.
*/
functionPerRoute: boolean;
}
export interface AstroAdapter {

View file

@ -340,40 +340,3 @@ describe('Middleware with tailwind', () => {
assert.equal(bundledCSS.includes('--tw-content'), true);
});
});
// `loadTestAdapterApp()` does not understand how to load the page with `functionPerRoute`
// since there's no `entry.mjs`. Skip for now.
describe(
'Middleware supports functionPerRoute feature',
{
skip: "`loadTestAdapterApp()` does not understand how to load the page with `functionPerRoute` since there's no `entry.mjs`",
},
() => {
/** @type {import('./test-utils').Fixture} */
let fixture;
before(async () => {
fixture = await loadFixture({
root: './fixtures/middleware space/',
output: 'server',
adapter: testAdapter({
extendAdapter: {
adapterFeatures: {
functionPerRoute: true,
},
},
}),
});
await fixture.build();
});
it('should not render locals data because the page does not export it', async () => {
const app = await fixture.loadTestAdapterApp();
const request = new Request('http://example.com/');
const response = await app.render(request);
const html = await response.text();
const $ = cheerio.load(html);
assert.equal($('p').html(), 'bar');
});
},
);

View file

@ -1,117 +0,0 @@
import assert from 'node:assert/strict';
import { existsSync, readFileSync } from 'node:fs';
import { before, describe, it } from 'node:test';
import { fileURLToPath } from 'node:url';
import * as cheerio from 'cheerio';
import testAdapter from './test-adapter.js';
import { loadFixture } from './test-utils.js';
describe('astro:ssr-manifest, split', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;
let entryPoints;
let currentRoutes;
before(async () => {
fixture = await loadFixture({
root: './fixtures/ssr-split-manifest/',
output: 'server',
adapter: testAdapter({
setEntryPoints(entries) {
if (entries) {
entryPoints = entries;
}
},
setRoutes(routes) {
currentRoutes = routes;
},
extendAdapter: {
adapterFeatures: {
functionPerRoute: true,
},
},
}),
// test suite was authored when inlineStylesheets defaulted to never
build: { inlineStylesheets: 'never' },
});
await fixture.build();
});
it('should be able to render a specific entry point', async () => {
const pagePath = 'src/pages/index.astro';
const app = await fixture.loadEntryPoint(pagePath, currentRoutes);
const request = new Request('http://example.com/');
const response = await app.render(request);
const html = await response.text();
const $ = cheerio.load(html);
assert.match(
$('#assets').text(),
/\["\/_astro\/index\.([\w-]{8})\.css","\/prerender\/index\.html"\]/,
);
});
it('should give access to entry points that exists on file system', async () => {
// number of the pages inside src/
assert.equal(entryPoints.size, 6);
for (const fileUrl of entryPoints.values()) {
let filePath = fileURLToPath(fileUrl);
assert.equal(existsSync(filePath), true);
}
});
it('should correctly emit the the pre render page', async () => {
const indexUrl = new URL(
'./fixtures/ssr-split-manifest/dist/client/prerender/index.html',
import.meta.url,
);
const text = readFileSync(indexUrl, {
encoding: 'utf8',
});
assert.equal(text.includes('<title>Pre render me</title>'), true);
});
it('should emit an entry point to request the pre-rendered page', async () => {
const pagePath = 'src/pages/prerender.astro';
const app = await fixture.loadEntryPoint(pagePath, currentRoutes);
const request = new Request('http://example.com/');
const response = await app.render(request);
const html = await response.text();
assert.equal(html.includes('<title>Pre render me</title>'), true);
});
describe('when function per route is enabled', async () => {
before(async () => {
fixture = await loadFixture({
root: './fixtures/ssr-split-manifest/',
output: 'server',
adapter: testAdapter({
setEntryPoints(entries) {
if (entries) {
entryPoints = entries;
}
},
setRoutes(routes) {
currentRoutes = routes;
},
extendAdapter: {
adapterFeatures: {
functionPerRoute: true,
},
},
}),
// test suite was authored when inlineStylesheets defaulted to never
build: { inlineStylesheets: 'never' },
});
await fixture.build();
});
it('should correctly build, and not create a "uses" entry point', async () => {
const pagePath = 'src/pages/index.astro';
const app = await fixture.loadEntryPoint(pagePath, currentRoutes);
const request = new Request('http://example.com/');
const response = await app.render(request);
const html = await response.text();
assert.equal(html.includes('<title>Testing</title>'), true);
});
});
});

View file

@ -10,9 +10,6 @@ import { check } from '../dist/cli/check/index.js';
import { globalContentLayer } from '../dist/content/content-layer.js';
import { globalContentConfigObserver } from '../dist/content/utils.js';
import build from '../dist/core/build/index.js';
import { RESOLVED_SPLIT_MODULE_ID } from '../dist/core/build/plugins/plugin-ssr.js';
import { getVirtualModulePageName } from '../dist/core/build/plugins/util.js';
import { makeSplitEntryPointFileName } from '../dist/core/build/static-build.js';
import { mergeConfig, resolveConfig } from '../dist/core/config/index.js';
import { dev, preview } from '../dist/core/index.js';
import { nodeLogDestination } from '../dist/core/logger/node.js';
@ -262,15 +259,6 @@ export async function loadFixture(inlineConfig) {
app.manifest = manifest;
return app;
},
loadEntryPoint: async (pagePath, routes, streaming) => {
const virtualModule = getVirtualModulePageName(RESOLVED_SPLIT_MODULE_ID, pagePath);
const filePath = makeSplitEntryPointFileName(virtualModule, routes);
const url = new URL(`./server/${filePath}?id=${fixtureId}`, config.outDir);
const { createApp, manifest } = await import(url);
const app = createApp(streaming);
app.manifest = manifest;
return app;
},
editFile: async (filePath, newContentsOrCallback) => {
const fileUrl = new URL(filePath.replace(/^\//, ''), config.root);
const contents = await fs.promises.readFile(fileUrl, 'utf-8');

View file

@ -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,7 +84,6 @@ function getAdapter({
args: { middlewareSecret, skewProtection },
adapterFeatures: {
edgeMiddleware,
functionPerRoute,
},
supportedAstroFeatures: {
hybridOutput: 'stable',
@ -134,12 +131,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 +177,6 @@ export default function vercelServerless({
imageService,
imagesConfig,
devImageService = 'sharp',
functionPerRoute = false,
edgeMiddleware = false,
maxDuration,
isr = false,
@ -281,23 +271,9 @@ export default function vercelServerless({
),
});
},
'astro:config:done': ({ setAdapter, config, logger }) => {
if (functionPerRoute === true) {
logger.warn(
`\n` +
`\tVercel's hosting plans might have limits to the number of functions you can create.\n` +
`\tMake sure to check your plan carefully to avoid incurring additional costs.\n` +
`\tYou can set functionPerRoute: false to prevent surpassing the limit.\n`,
);
logger.warn(
`\n` +
`\t\`functionPerRoute\` is deprecated and will be removed in a future version of the adapter.\n`,
);
}
'astro:config:done': ({ setAdapter, config }) => {
setAdapter(
getAdapter({ functionPerRoute, edgeMiddleware, middlewareSecret, skewProtection }),
getAdapter({ edgeMiddleware, middlewareSecret, skewProtection }),
);
_config = config;

View file

@ -36,7 +36,6 @@ function getAdapter(): AstroAdapter {
},
adapterFeatures: {
edgeMiddleware: false,
functionPerRoute: false,
},
};
}

View file

@ -2,7 +2,5 @@ import vercel from '@astrojs/vercel/serverless';
import { defineConfig } from 'astro/config';
export default defineConfig({
adapter: vercel({
functionPerRoute: true
})
adapter: vercel({})
});

View file

@ -1,9 +0,0 @@
import vercel from '@astrojs/vercel/serverless';
import { defineConfig } from 'astro/config';
export default defineConfig({
adapter: vercel({
functionPerRoute: true
}),
output: "server"
});

View file

@ -1,9 +0,0 @@
{
"name": "@test/astro-vercel-function-per-route",
"version": "0.0.0",
"private": true,
"dependencies": {
"@astrojs/vercel": "workspace:*",
"astro": "workspace:*"
}
}

View file

@ -1,8 +0,0 @@
<html>
<head>
<title>One</title>
</head>
<body>
<h1>One</h1>
</body>
</html>

View file

@ -1,12 +0,0 @@
---
export const prerender = true;
---
<html>
<head>
<title>Prerendered Page</title>
</head>
<body>
<h1>Prerendered Page</h1>
</body>
</html>

View file

@ -1,8 +0,0 @@
<html>
<head>
<title>Two</title>
</head>
<body>
<h1>Two</h1>
</body>
</html>

View file

@ -5,7 +5,6 @@ export default defineConfig({
adapter: vercel({
// Pass some value to make sure it doesn't error out
includeFiles: ['included.js'],
functionPerRoute: true,
}),
output: 'server'
});

View file

@ -18,10 +18,7 @@ describe('Serverless with dynamic routes', () => {
it('build successful', async () => {
assert.ok(await fixture.readFile('../.vercel/output/static/index.html'));
assert.ok(
await fixture.readFile('../.vercel/output/functions/[id]/index.astro.func/.vc-config.json'),
);
assert.ok(
await fixture.readFile('../.vercel/output/functions/api/[id].js.func/.vc-config.json'),
await fixture.readFile('../.vercel/output/functions/_render.func/.vc-config.json'),
);
});
});

View file

@ -1,32 +0,0 @@
import assert from 'node:assert/strict';
import { before, describe, it } from 'node:test';
import { loadFixture } from './test-utils.js';
describe('build: split', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;
before(async () => {
fixture = await loadFixture({
root: './fixtures/functionPerRoute/',
output: 'server',
});
await fixture.build();
});
it('creates separate functions for non-prerendered pages', async () => {
const files = await fixture.readdir('../.vercel/output/functions/');
assert.equal(files.length, 3);
assert.equal(files.includes('prerender.astro.func'), false);
});
it('creates the route definitions in the config.json', async () => {
const json = await fixture.readFile('../.vercel/output/config.json');
const config = JSON.parse(json);
assert.equal(config.routes.length, 5);
assert.equal(
config.routes.some((route) => route.dest === 'prerender.astro'),
false,
);
});
});