diff --git a/.changeset/fair-emus-divide.md b/.changeset/fair-emus-divide.md new file mode 100644 index 000000000..e8d40120b --- /dev/null +++ b/.changeset/fair-emus-divide.md @@ -0,0 +1,39 @@ +--- +'astro': major +'@astrojs/netlify': minor +--- + +The configuration `build.split` and `build.excludeMiddleware` are deprecated. + +Configuration that were inside the astro configuration, are now moved inside the adapter: + +```diff +import {defineConfig} from "astro/config"; +import netlify from "@astrojs/netlify/functions"; + +export default defineConfig({ +- build: { +- excludeMiddleware: true +- }, +- adapter: netlify() ++ adapter: netlify({ ++ edgeMiddleware: true ++ }) +}) +``` + +```diff +import {defineConfig} from "astro/config"; +import netlify from "@astrojs/netlify/functions"; + +export default defineConfig({ +- build: { +- split: true +- }, +- adapter: netlify() ++ adapter: netlify({ ++ functionPerRoute: true ++ }) +}) +``` + diff --git a/.changeset/tricky-candles-suffer.md b/.changeset/tricky-candles-suffer.md new file mode 100644 index 000000000..e1f04f193 --- /dev/null +++ b/.changeset/tricky-candles-suffer.md @@ -0,0 +1,39 @@ +--- +'astro': major +'@astrojs/vercel': minor +--- + +The configuration `build.split` and `build.excludeMiddleware` are deprecated. + +Configuration that were inside the astro configuration, are now moved inside the adapter: + +```diff +import {defineConfig} from "astro/config"; +import vercel from "@astrojs/vercel/serverless"; + +export default defineConfig({ +- build: { +- excludeMiddleware: true +- }, +- adapter: vercel() ++ adapter: vercel({ ++ edgeMiddleware: true ++ }) +}) +``` + +```diff +import {defineConfig} from "astro/config"; +import vercel from "@astrojs/vercel/serverless"; + +export default defineConfig({ +- build: { +- split: true +- }, +- adapter: vercel() ++ adapter: vercel({ ++ functionPerRoute: true ++ }) +}) +``` + diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index 14b0a7489..cfa354d98 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -1411,6 +1411,17 @@ export interface DataEntryType { export type GetDataEntryInfoReturnType = { data: Record; rawData?: string }; +export interface AstroAdapterFeatures { + /** + * Creates and edge function that will communiate with the Astro middleware + */ + edgeMiddleware: boolean; + /** + * SSR only. Each route becomes its own function/file. + */ + functionPerRoute: boolean; +} + export interface AstroSettings { config: AstroConfig; adapter: AstroAdapter | undefined; @@ -1675,6 +1686,7 @@ export interface AstroAdapter { previewEntrypoint?: string; exports?: string[]; args?: any; + adapterFeatures?: AstroAdapterFeatures; } type Body = string; diff --git a/packages/astro/src/core/build/plugins/plugin-pages.ts b/packages/astro/src/core/build/plugins/plugin-pages.ts index 2ee438a6a..966426439 100644 --- a/packages/astro/src/core/build/plugins/plugin-pages.ts +++ b/packages/astro/src/core/build/plugins/plugin-pages.ts @@ -74,7 +74,11 @@ function vitePluginPages(opts: StaticBuildOptions, internals: BuildInternals): V exports.push(`export { renderers };`); // The middleware should not be imported by the pages - if (!opts.settings.config.build.excludeMiddleware) { + if ( + // TODO: remover in Astro 4.0 + !opts.settings.config.build.excludeMiddleware || + opts.settings.adapter?.adapterFeatures?.edgeMiddleware === true + ) { const middlewareModule = await this.resolve(MIDDLEWARE_MODULE_ID); if (middlewareModule) { imports.push(`import { onRequest } from "${middlewareModule.id}";`); diff --git a/packages/astro/src/core/build/plugins/plugin-ssr.ts b/packages/astro/src/core/build/plugins/plugin-ssr.ts index 514fe2409..b1fc0e4c2 100644 --- a/packages/astro/src/core/build/plugins/plugin-ssr.ts +++ b/packages/astro/src/core/build/plugins/plugin-ssr.ts @@ -3,7 +3,7 @@ import { join } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import type { Plugin as VitePlugin } from 'vite'; import type { AstroAdapter, AstroConfig } from '../../../@types/astro'; -import { runHookBuildSsr } from '../../../integrations/index.js'; +import { isFunctionPerRouteEnabled, runHookBuildSsr } from '../../../integrations/index.js'; import { isServerLikeOutput } from '../../../prerender/utils.js'; import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from '../../../vite-plugin-scripts/index.js'; import type { SerializedRouteInfo, SerializedSSRManifest } from '../../app/types'; @@ -103,12 +103,16 @@ export function pluginSSR( internals: BuildInternals ): AstroBuildPlugin { const ssr = isServerLikeOutput(options.settings.config); + const functionPerRouteEnabled = isFunctionPerRouteEnabled(options.settings.adapter); return { build: 'ssr', hooks: { 'build:before': () => { let vitePlugin = - ssr && !options.settings.config.build.split + ssr && + // TODO: Remove in Astro 4.0 + options.settings.config.build.split === false && + functionPerRouteEnabled === false ? vitePluginSSR(internals, options.settings.adapter!, options) : undefined; @@ -122,7 +126,7 @@ export function pluginSSR( return; } - if (options.settings.config.build.split) { + if (options.settings.config.build.split || functionPerRouteEnabled) { return; } @@ -155,11 +159,12 @@ function vitePluginSSRSplit( adapter: AstroAdapter, options: StaticBuildOptions ): VitePlugin { + const functionPerRouteEnabled = isFunctionPerRouteEnabled(options.settings.adapter); return { name: '@astrojs/vite-plugin-astro-ssr-split', enforce: 'post', options(opts) { - if (options.settings.config.build.split) { + if (options.settings.config.build.split || functionPerRouteEnabled) { const inputs = new Set(); for (const path of Object.keys(options.allPages)) { @@ -229,12 +234,14 @@ export function pluginSSRSplit( internals: BuildInternals ): AstroBuildPlugin { const ssr = isServerLikeOutput(options.settings.config); + const functionPerRouteEnabled = isFunctionPerRouteEnabled(options.settings.adapter); + return { build: 'ssr', hooks: { 'build:before': () => { let vitePlugin = - ssr && options.settings.config.build.split + ssr && (options.settings.config.build.split || functionPerRouteEnabled) ? vitePluginSSRSplit(internals, options.settings.adapter!, options) : undefined; @@ -247,7 +254,7 @@ export function pluginSSRSplit( if (!ssr) { return; } - if (!options.settings.config.build.split) { + if (!options.settings.config.build.split && !functionPerRouteEnabled) { return; } @@ -276,7 +283,7 @@ function generateSSRCode(config: AstroConfig, adapter: AstroAdapter) { const imports: string[] = []; const contents: string[] = []; let pageMap; - if (config.build.split) { + if (config.build.split || isFunctionPerRouteEnabled(adapter)) { pageMap = 'pageModule'; } else { pageMap = 'pageMap'; @@ -337,7 +344,10 @@ export async function createManifest( buildOpts: StaticBuildOptions, internals: BuildInternals ): Promise { - if (buildOpts.settings.config.build.split) { + if ( + buildOpts.settings.config.build.split || + isFunctionPerRouteEnabled(buildOpts.settings.adapter) + ) { if (internals.ssrSplitEntryChunks.size === 0) { throw new Error(`Did not generate an entry chunk for SSR in serverless mode`); } diff --git a/packages/astro/src/core/config/schema.ts b/packages/astro/src/core/config/schema.ts index 34c1e5549..c57f8b95a 100644 --- a/packages/astro/src/core/config/schema.ts +++ b/packages/astro/src/core/config/schema.ts @@ -124,7 +124,15 @@ export const AstroConfigSchema = z.object({ .optional() .default(ASTRO_CONFIG_DEFAULTS.build.inlineStylesheets), + /** + * @deprecated + * Use the adapter feature instead + */ split: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.build.split), + /** + * @deprecated + * Use the adapter feature instead + */ excludeMiddleware: z .boolean() .optional() diff --git a/packages/astro/src/integrations/index.ts b/packages/astro/src/integrations/index.ts index d54ac3803..ab9898523 100644 --- a/packages/astro/src/integrations/index.ts +++ b/packages/astro/src/integrations/index.ts @@ -4,6 +4,7 @@ import type { AddressInfo } from 'node:net'; import { fileURLToPath } from 'node:url'; import type { InlineConfig, ViteDevServer } from 'vite'; import type { + AstroAdapter, AstroConfig, AstroIntegration, AstroRenderer, @@ -410,3 +411,19 @@ export async function runHookBuildDone({ config, pages, routes, logging }: RunHo } } } + +export function isFunctionPerRouteEnabled(adapter: AstroAdapter | undefined): boolean { + if (adapter?.adapterFeatures?.functionPerRoute === true) { + return true; + } else { + return false; + } +} + +export function isEdgeMiddlewareEnabled(adapter: AstroAdapter | undefined): boolean { + if (adapter?.adapterFeatures?.edgeMiddleware === true) { + return true; + } else { + return false; + } +} diff --git a/packages/integrations/netlify/src/integration-functions.ts b/packages/integrations/netlify/src/integration-functions.ts index a950167e5..3b10e096c 100644 --- a/packages/integrations/netlify/src/integration-functions.ts +++ b/packages/integrations/netlify/src/integration-functions.ts @@ -8,12 +8,16 @@ import { createRedirects } from './shared.js'; export const NETLIFY_EDGE_MIDDLEWARE_FILE = 'netlify-edge-middleware'; export const ASTRO_LOCALS_HEADER = 'x-astro-locals'; -export function getAdapter(args: Args = {}): AstroAdapter { +export function getAdapter({ functionPerRoute, edgeMiddleware, ...args }: Args): AstroAdapter { return { name: '@astrojs/netlify/functions', serverEntrypoint: '@astrojs/netlify/netlify-functions.js', exports: ['handler'], args, + adapterFeatures: { + functionPerRoute, + edgeMiddleware, + }, }; } @@ -21,12 +25,16 @@ interface NetlifyFunctionsOptions { dist?: URL; builders?: boolean; binaryMediaTypes?: string[]; + edgeMiddleware?: boolean; + functionPerRoute?: boolean; } function netlifyFunctions({ dist, builders, binaryMediaTypes, + functionPerRoute = false, + edgeMiddleware = false, }: NetlifyFunctionsOptions = {}): AstroIntegration { let _config: AstroConfig; let _entryPoints: Map; @@ -53,7 +61,7 @@ function netlifyFunctions({ _entryPoints = entryPoints; }, 'astro:config:done': ({ config, setAdapter }) => { - setAdapter(getAdapter({ binaryMediaTypes, builders })); + setAdapter(getAdapter({ binaryMediaTypes, builders, functionPerRoute, edgeMiddleware })); _config = config; ssrEntryFile = config.build.serverEntry.replace(/\.m?js/, ''); diff --git a/packages/integrations/netlify/src/netlify-functions.ts b/packages/integrations/netlify/src/netlify-functions.ts index 91c4f1ef7..3da0718b0 100644 --- a/packages/integrations/netlify/src/netlify-functions.ts +++ b/packages/integrations/netlify/src/netlify-functions.ts @@ -9,6 +9,8 @@ applyPolyfills(); export interface Args { builders?: boolean; binaryMediaTypes?: string[]; + edgeMiddleware: boolean; + functionPerRoute: boolean; } function parseContentType(header?: string) { diff --git a/packages/integrations/netlify/test/functions/edge-middleware.test.js b/packages/integrations/netlify/test/functions/edge-middleware.test.js index 219fd1ced..a83720a4d 100644 --- a/packages/integrations/netlify/test/functions/edge-middleware.test.js +++ b/packages/integrations/netlify/test/functions/edge-middleware.test.js @@ -10,6 +10,7 @@ describe('Middleware', () => { output: 'server', adapter: netlifyAdapter({ dist: new URL('./fixtures/middleware-with-handler-file/dist/', import.meta.url), + edgeMiddleware: true, }), site: `http://example.com`, integrations: [testIntegration()], diff --git a/packages/integrations/netlify/test/functions/split-support.test.js b/packages/integrations/netlify/test/functions/split-support.test.js index 217b3c0d3..fde8b5eb1 100644 --- a/packages/integrations/netlify/test/functions/split-support.test.js +++ b/packages/integrations/netlify/test/functions/split-support.test.js @@ -13,6 +13,7 @@ describe('Split support', () => { output: 'server', adapter: netlifyAdapter({ dist: new URL('./fixtures/split-support/dist/', import.meta.url), + functionPerRoute: true, }), site: `http://example.com`, integrations: [ @@ -22,9 +23,6 @@ describe('Split support', () => { }, }), ], - build: { - split: true, - }, }); await fixture.build(); }); diff --git a/packages/integrations/vercel/src/serverless/adapter.ts b/packages/integrations/vercel/src/serverless/adapter.ts index 1a2f9d82a..4fd7e22a4 100644 --- a/packages/integrations/vercel/src/serverless/adapter.ts +++ b/packages/integrations/vercel/src/serverless/adapter.ts @@ -29,11 +29,21 @@ const SUPPORTED_NODE_VERSIONS: Record< 18: { status: 'current' }, }; -function getAdapter(): AstroAdapter { +function getAdapter({ + edgeMiddleware, + functionPerRoute, +}: { + edgeMiddleware: boolean; + functionPerRoute: boolean; +}): AstroAdapter { return { name: PACKAGE_NAME, serverEntrypoint: `${PACKAGE_NAME}/entrypoint`, exports: ['default'], + adapterFeatures: { + edgeMiddleware, + functionPerRoute, + }, }; } @@ -43,6 +53,8 @@ export interface VercelServerlessConfig { analytics?: boolean; imageService?: boolean; imagesConfig?: VercelImageConfig; + edgeMiddleware?: boolean; + functionPerRoute?: boolean; } export default function vercelServerless({ @@ -51,6 +63,8 @@ export default function vercelServerless({ analytics, imageService, imagesConfig, + functionPerRoute = false, + edgeMiddleware = false, }: VercelServerlessConfig = {}): AstroIntegration { let _config: AstroConfig; let buildTempFolder: URL; @@ -112,7 +126,7 @@ export default function vercelServerless({ }, 'astro:config:done': ({ setAdapter, config }) => { throwIfAssetsNotEnabled(config, imageService); - setAdapter(getAdapter()); + setAdapter(getAdapter({ functionPerRoute, edgeMiddleware })); _config = config; buildTempFolder = config.build.server; serverEntry = config.build.serverEntry; diff --git a/packages/integrations/vercel/test/fixtures/basic/astro.config.mjs b/packages/integrations/vercel/test/fixtures/basic/astro.config.mjs index 664b64d56..6e2e304d9 100644 --- a/packages/integrations/vercel/test/fixtures/basic/astro.config.mjs +++ b/packages/integrations/vercel/test/fixtures/basic/astro.config.mjs @@ -2,5 +2,7 @@ import { defineConfig } from 'astro/config'; import vercel from '@astrojs/vercel/serverless'; export default defineConfig({ - adapter: vercel() + adapter: vercel({ + functionPerRoute: true + }) }); diff --git a/packages/integrations/vercel/test/fixtures/functionPerRoute/astro.config.mjs b/packages/integrations/vercel/test/fixtures/functionPerRoute/astro.config.mjs new file mode 100644 index 000000000..fefc69a83 --- /dev/null +++ b/packages/integrations/vercel/test/fixtures/functionPerRoute/astro.config.mjs @@ -0,0 +1,9 @@ +import { defineConfig } from 'astro/config'; +import vercel from '@astrojs/vercel/serverless'; + +export default defineConfig({ + adapter: vercel({ + functionPerRoute: true + }), + output: "server" +}); diff --git a/packages/integrations/vercel/test/fixtures/functionPerRoute/package.json b/packages/integrations/vercel/test/fixtures/functionPerRoute/package.json new file mode 100644 index 000000000..e33f178bb --- /dev/null +++ b/packages/integrations/vercel/test/fixtures/functionPerRoute/package.json @@ -0,0 +1,9 @@ +{ + "name": "@test/astro-vercel-function-per-route", + "version": "0.0.0", + "private": true, + "dependencies": { + "@astrojs/vercel": "workspace:*", + "astro": "workspace:*" + } +} diff --git a/packages/integrations/vercel/test/fixtures/functionPerRoute/src/pages/one.astro b/packages/integrations/vercel/test/fixtures/functionPerRoute/src/pages/one.astro new file mode 100644 index 000000000..0c7fb90a7 --- /dev/null +++ b/packages/integrations/vercel/test/fixtures/functionPerRoute/src/pages/one.astro @@ -0,0 +1,8 @@ + + + One + + +

One

+ + diff --git a/packages/integrations/vercel/test/fixtures/functionPerRoute/src/pages/two.astro b/packages/integrations/vercel/test/fixtures/functionPerRoute/src/pages/two.astro new file mode 100644 index 000000000..e7ba9910e --- /dev/null +++ b/packages/integrations/vercel/test/fixtures/functionPerRoute/src/pages/two.astro @@ -0,0 +1,8 @@ + + + Two + + +

Two

+ + diff --git a/packages/integrations/vercel/test/fixtures/middleware-with-edge-file/astro.config.mjs b/packages/integrations/vercel/test/fixtures/middleware-with-edge-file/astro.config.mjs index 321a8bde3..33910b50a 100644 --- a/packages/integrations/vercel/test/fixtures/middleware-with-edge-file/astro.config.mjs +++ b/packages/integrations/vercel/test/fixtures/middleware-with-edge-file/astro.config.mjs @@ -2,9 +2,8 @@ import {defineConfig} from "astro/config"; import vercel from "@astrojs/vercel/serverless"; export default defineConfig({ - adapter: vercel(), - build: { - excludeMiddleware: true - }, + adapter: vercel({ + edgeMiddleware: true + }), output: 'server' -}); \ No newline at end of file +}); diff --git a/packages/integrations/vercel/test/fixtures/middleware-without-edge-file/astro.config.mjs b/packages/integrations/vercel/test/fixtures/middleware-without-edge-file/astro.config.mjs index 321a8bde3..33910b50a 100644 --- a/packages/integrations/vercel/test/fixtures/middleware-without-edge-file/astro.config.mjs +++ b/packages/integrations/vercel/test/fixtures/middleware-without-edge-file/astro.config.mjs @@ -2,9 +2,8 @@ import {defineConfig} from "astro/config"; import vercel from "@astrojs/vercel/serverless"; export default defineConfig({ - adapter: vercel(), - build: { - excludeMiddleware: true - }, + adapter: vercel({ + edgeMiddleware: true + }), output: 'server' -}); \ No newline at end of file +}); diff --git a/packages/integrations/vercel/test/split.test.js b/packages/integrations/vercel/test/split.test.js index 9044954f2..85affb3ff 100644 --- a/packages/integrations/vercel/test/split.test.js +++ b/packages/integrations/vercel/test/split.test.js @@ -7,11 +7,8 @@ describe('build: split', () => { before(async () => { fixture = await loadFixture({ - root: './fixtures/basic/', + root: './fixtures/functionPerRoute/', output: 'server', - build: { - split: true, - }, }); await fixture.build(); });