feat: adapter features, deprecate astro configs (#7839)

This commit is contained in:
Emanuele Stoppa 2023-07-28 10:11:13 +01:00
parent 7ee1861557
commit c17e265c0d
20 changed files with 214 additions and 31 deletions

View file

@ -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
+ })
})
```

View file

@ -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
+ })
})
```

View file

@ -1410,6 +1410,17 @@ export interface DataEntryType {
export type GetDataEntryInfoReturnType = { data: Record<string, unknown>; rawData?: string }; export type GetDataEntryInfoReturnType = { data: Record<string, unknown>; 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 { export interface AstroSettings {
config: AstroConfig; config: AstroConfig;
adapter: AstroAdapter | undefined; adapter: AstroAdapter | undefined;
@ -1674,6 +1685,7 @@ export interface AstroAdapter {
previewEntrypoint?: string; previewEntrypoint?: string;
exports?: string[]; exports?: string[];
args?: any; args?: any;
adapterFeatures?: AstroAdapterFeatures;
} }
type Body = string; type Body = string;

View file

@ -74,7 +74,11 @@ function vitePluginPages(opts: StaticBuildOptions, internals: BuildInternals): V
exports.push(`export { renderers };`); exports.push(`export { renderers };`);
// The middleware should not be imported by the pages // 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); const middlewareModule = await this.resolve(MIDDLEWARE_MODULE_ID);
if (middlewareModule) { if (middlewareModule) {
imports.push(`import { onRequest } from "${middlewareModule.id}";`); imports.push(`import { onRequest } from "${middlewareModule.id}";`);

View file

@ -3,7 +3,7 @@ import { join } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url'; import { fileURLToPath, pathToFileURL } from 'node:url';
import type { Plugin as VitePlugin } from 'vite'; import type { Plugin as VitePlugin } from 'vite';
import type { AstroAdapter, AstroConfig } from '../../../@types/astro'; 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 { isServerLikeOutput } from '../../../prerender/utils.js';
import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from '../../../vite-plugin-scripts/index.js'; import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from '../../../vite-plugin-scripts/index.js';
import type { SerializedRouteInfo, SerializedSSRManifest } from '../../app/types'; import type { SerializedRouteInfo, SerializedSSRManifest } from '../../app/types';
@ -103,12 +103,16 @@ export function pluginSSR(
internals: BuildInternals internals: BuildInternals
): AstroBuildPlugin { ): AstroBuildPlugin {
const ssr = isServerLikeOutput(options.settings.config); const ssr = isServerLikeOutput(options.settings.config);
const functionPerRouteEnabled = isFunctionPerRouteEnabled(options.settings.adapter);
return { return {
build: 'ssr', build: 'ssr',
hooks: { hooks: {
'build:before': () => { 'build:before': () => {
let vitePlugin = 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) ? vitePluginSSR(internals, options.settings.adapter!, options)
: undefined; : undefined;
@ -122,7 +126,7 @@ export function pluginSSR(
return; return;
} }
if (options.settings.config.build.split) { if (options.settings.config.build.split || functionPerRouteEnabled) {
return; return;
} }
@ -155,11 +159,12 @@ function vitePluginSSRSplit(
adapter: AstroAdapter, adapter: AstroAdapter,
options: StaticBuildOptions options: StaticBuildOptions
): VitePlugin { ): VitePlugin {
const functionPerRouteEnabled = isFunctionPerRouteEnabled(options.settings.adapter);
return { return {
name: '@astrojs/vite-plugin-astro-ssr-split', name: '@astrojs/vite-plugin-astro-ssr-split',
enforce: 'post', enforce: 'post',
options(opts) { options(opts) {
if (options.settings.config.build.split) { if (options.settings.config.build.split || functionPerRouteEnabled) {
const inputs = new Set<string>(); const inputs = new Set<string>();
for (const path of Object.keys(options.allPages)) { for (const path of Object.keys(options.allPages)) {
@ -229,12 +234,14 @@ export function pluginSSRSplit(
internals: BuildInternals internals: BuildInternals
): AstroBuildPlugin { ): AstroBuildPlugin {
const ssr = isServerLikeOutput(options.settings.config); const ssr = isServerLikeOutput(options.settings.config);
const functionPerRouteEnabled = isFunctionPerRouteEnabled(options.settings.adapter);
return { return {
build: 'ssr', build: 'ssr',
hooks: { hooks: {
'build:before': () => { 'build:before': () => {
let vitePlugin = let vitePlugin =
ssr && options.settings.config.build.split ssr && (options.settings.config.build.split || functionPerRouteEnabled)
? vitePluginSSRSplit(internals, options.settings.adapter!, options) ? vitePluginSSRSplit(internals, options.settings.adapter!, options)
: undefined; : undefined;
@ -247,7 +254,7 @@ export function pluginSSRSplit(
if (!ssr) { if (!ssr) {
return; return;
} }
if (!options.settings.config.build.split) { if (!options.settings.config.build.split && !functionPerRouteEnabled) {
return; return;
} }
@ -276,7 +283,7 @@ function generateSSRCode(config: AstroConfig, adapter: AstroAdapter) {
const imports: string[] = []; const imports: string[] = [];
const contents: string[] = []; const contents: string[] = [];
let pageMap; let pageMap;
if (config.build.split) { if (config.build.split || isFunctionPerRouteEnabled(adapter)) {
pageMap = 'pageModule'; pageMap = 'pageModule';
} else { } else {
pageMap = 'pageMap'; pageMap = 'pageMap';
@ -337,7 +344,10 @@ export async function createManifest(
buildOpts: StaticBuildOptions, buildOpts: StaticBuildOptions,
internals: BuildInternals internals: BuildInternals
): Promise<SerializedSSRManifest> { ): Promise<SerializedSSRManifest> {
if (buildOpts.settings.config.build.split) { if (
buildOpts.settings.config.build.split ||
isFunctionPerRouteEnabled(buildOpts.settings.adapter)
) {
if (internals.ssrSplitEntryChunks.size === 0) { if (internals.ssrSplitEntryChunks.size === 0) {
throw new Error(`Did not generate an entry chunk for SSR in serverless mode`); throw new Error(`Did not generate an entry chunk for SSR in serverless mode`);
} }

View file

@ -124,7 +124,15 @@ export const AstroConfigSchema = z.object({
.optional() .optional()
.default(ASTRO_CONFIG_DEFAULTS.build.inlineStylesheets), .default(ASTRO_CONFIG_DEFAULTS.build.inlineStylesheets),
/**
* @deprecated
* Use the adapter feature instead
*/
split: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.build.split), split: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.build.split),
/**
* @deprecated
* Use the adapter feature instead
*/
excludeMiddleware: z excludeMiddleware: z
.boolean() .boolean()
.optional() .optional()

View file

@ -4,6 +4,7 @@ import type { AddressInfo } from 'node:net';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import type { InlineConfig, ViteDevServer } from 'vite'; import type { InlineConfig, ViteDevServer } from 'vite';
import type { import type {
AstroAdapter,
AstroConfig, AstroConfig,
AstroIntegration, AstroIntegration,
AstroRenderer, 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;
}
}

View file

@ -8,12 +8,16 @@ import { createRedirects } from './shared.js';
export const NETLIFY_EDGE_MIDDLEWARE_FILE = 'netlify-edge-middleware'; export const NETLIFY_EDGE_MIDDLEWARE_FILE = 'netlify-edge-middleware';
export const ASTRO_LOCALS_HEADER = 'x-astro-locals'; export const ASTRO_LOCALS_HEADER = 'x-astro-locals';
export function getAdapter(args: Args = {}): AstroAdapter { export function getAdapter({ functionPerRoute, edgeMiddleware, ...args }: Args): AstroAdapter {
return { return {
name: '@astrojs/netlify/functions', name: '@astrojs/netlify/functions',
serverEntrypoint: '@astrojs/netlify/netlify-functions.js', serverEntrypoint: '@astrojs/netlify/netlify-functions.js',
exports: ['handler'], exports: ['handler'],
args, args,
adapterFeatures: {
functionPerRoute,
edgeMiddleware,
},
}; };
} }
@ -21,12 +25,16 @@ interface NetlifyFunctionsOptions {
dist?: URL; dist?: URL;
builders?: boolean; builders?: boolean;
binaryMediaTypes?: string[]; binaryMediaTypes?: string[];
edgeMiddleware?: boolean;
functionPerRoute?: boolean;
} }
function netlifyFunctions({ function netlifyFunctions({
dist, dist,
builders, builders,
binaryMediaTypes, binaryMediaTypes,
functionPerRoute = false,
edgeMiddleware = false,
}: NetlifyFunctionsOptions = {}): AstroIntegration { }: NetlifyFunctionsOptions = {}): AstroIntegration {
let _config: AstroConfig; let _config: AstroConfig;
let _entryPoints: Map<RouteData, URL>; let _entryPoints: Map<RouteData, URL>;
@ -53,7 +61,7 @@ function netlifyFunctions({
_entryPoints = entryPoints; _entryPoints = entryPoints;
}, },
'astro:config:done': ({ config, setAdapter }) => { 'astro:config:done': ({ config, setAdapter }) => {
setAdapter(getAdapter({ binaryMediaTypes, builders })); setAdapter(getAdapter({ binaryMediaTypes, builders, functionPerRoute, edgeMiddleware }));
_config = config; _config = config;
ssrEntryFile = config.build.serverEntry.replace(/\.m?js/, ''); ssrEntryFile = config.build.serverEntry.replace(/\.m?js/, '');

View file

@ -9,6 +9,8 @@ applyPolyfills();
export interface Args { export interface Args {
builders?: boolean; builders?: boolean;
binaryMediaTypes?: string[]; binaryMediaTypes?: string[];
edgeMiddleware: boolean;
functionPerRoute: boolean;
} }
function parseContentType(header?: string) { function parseContentType(header?: string) {

View file

@ -10,6 +10,7 @@ describe('Middleware', () => {
output: 'server', output: 'server',
adapter: netlifyAdapter({ adapter: netlifyAdapter({
dist: new URL('./fixtures/middleware-with-handler-file/dist/', import.meta.url), dist: new URL('./fixtures/middleware-with-handler-file/dist/', import.meta.url),
edgeMiddleware: true,
}), }),
site: `http://example.com`, site: `http://example.com`,
integrations: [testIntegration()], integrations: [testIntegration()],

View file

@ -13,6 +13,7 @@ describe('Split support', () => {
output: 'server', output: 'server',
adapter: netlifyAdapter({ adapter: netlifyAdapter({
dist: new URL('./fixtures/split-support/dist/', import.meta.url), dist: new URL('./fixtures/split-support/dist/', import.meta.url),
functionPerRoute: true,
}), }),
site: `http://example.com`, site: `http://example.com`,
integrations: [ integrations: [
@ -22,9 +23,6 @@ describe('Split support', () => {
}, },
}), }),
], ],
build: {
split: true,
},
}); });
await fixture.build(); await fixture.build();
}); });

View file

@ -29,11 +29,21 @@ const SUPPORTED_NODE_VERSIONS: Record<
18: { status: 'current' }, 18: { status: 'current' },
}; };
function getAdapter(): AstroAdapter { function getAdapter({
edgeMiddleware,
functionPerRoute,
}: {
edgeMiddleware: boolean;
functionPerRoute: boolean;
}): AstroAdapter {
return { return {
name: PACKAGE_NAME, name: PACKAGE_NAME,
serverEntrypoint: `${PACKAGE_NAME}/entrypoint`, serverEntrypoint: `${PACKAGE_NAME}/entrypoint`,
exports: ['default'], exports: ['default'],
adapterFeatures: {
edgeMiddleware,
functionPerRoute,
},
}; };
} }
@ -43,6 +53,8 @@ export interface VercelServerlessConfig {
analytics?: boolean; analytics?: boolean;
imageService?: boolean; imageService?: boolean;
imagesConfig?: VercelImageConfig; imagesConfig?: VercelImageConfig;
edgeMiddleware?: boolean;
functionPerRoute?: boolean;
} }
export default function vercelServerless({ export default function vercelServerless({
@ -51,6 +63,8 @@ export default function vercelServerless({
analytics, analytics,
imageService, imageService,
imagesConfig, imagesConfig,
functionPerRoute = false,
edgeMiddleware = false,
}: VercelServerlessConfig = {}): AstroIntegration { }: VercelServerlessConfig = {}): AstroIntegration {
let _config: AstroConfig; let _config: AstroConfig;
let buildTempFolder: URL; let buildTempFolder: URL;
@ -112,7 +126,7 @@ export default function vercelServerless({
}, },
'astro:config:done': ({ setAdapter, config }) => { 'astro:config:done': ({ setAdapter, config }) => {
throwIfAssetsNotEnabled(config, imageService); throwIfAssetsNotEnabled(config, imageService);
setAdapter(getAdapter()); setAdapter(getAdapter({ functionPerRoute, edgeMiddleware }));
_config = config; _config = config;
buildTempFolder = config.build.server; buildTempFolder = config.build.server;
serverEntry = config.build.serverEntry; serverEntry = config.build.serverEntry;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,9 +2,8 @@ import {defineConfig} from "astro/config";
import vercel from "@astrojs/vercel/serverless"; import vercel from "@astrojs/vercel/serverless";
export default defineConfig({ export default defineConfig({
adapter: vercel(), adapter: vercel({
build: { edgeMiddleware: true
excludeMiddleware: true }),
},
output: 'server' output: 'server'
}); });

View file

@ -2,9 +2,8 @@ import {defineConfig} from "astro/config";
import vercel from "@astrojs/vercel/serverless"; import vercel from "@astrojs/vercel/serverless";
export default defineConfig({ export default defineConfig({
adapter: vercel(), adapter: vercel({
build: { edgeMiddleware: true
excludeMiddleware: true }),
},
output: 'server' output: 'server'
}); });

View file

@ -7,11 +7,8 @@ describe('build: split', () => {
before(async () => { before(async () => {
fixture = await loadFixture({ fixture = await loadFixture({
root: './fixtures/basic/', root: './fixtures/functionPerRoute/',
output: 'server', output: 'server',
build: {
split: true,
},
}); });
await fixture.build(); await fixture.build();
}); });