From 6ce4c3ddc33aa1904763a037bb03e3ab4e18cc7a Mon Sep 17 00:00:00 2001 From: bluwy Date: Tue, 6 Aug 2024 21:04:37 +0800 Subject: [PATCH 01/10] wip --- packages/astro/src/@types/astro.ts | 4 + packages/astro/src/core/create-vite.ts | 2 +- packages/astro/src/integrations/hooks.ts | 3 + packages/astro/src/vite-plugin-env/index.ts | 17 ++++- .../astro/src/vite-plugin-scanner/scan.ts | 13 ++++ .../integrations/node/test/prerender.test.js | 75 +++++++++++++++++++ 6 files changed, 112 insertions(+), 2 deletions(-) diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index 32d1cd30b025..68c864dd9eb4 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -2220,6 +2220,8 @@ export interface ResolvedInjectedRoute extends InjectedRoute { resolvedEntryPoint?: URL; } +export type RouteOptionsHandler = (route: { prerender?: boolean }) => void; + /** * Resolved Astro Config * Config with user settings along with all defaults filled in. @@ -2351,6 +2353,7 @@ export interface AstroSettings { preferences: AstroPreferences; injectedRoutes: InjectedRoute[]; resolvedInjectedRoutes: ResolvedInjectedRoute[]; + routeOptionsHandlers: RouteOptionsHandler[]; pageExtensions: string[]; contentEntryTypes: ContentEntryType[]; dataEntryTypes: DataEntryType[]; @@ -3063,6 +3066,7 @@ declare global { addWatchFile: (path: URL | string) => void; injectScript: (stage: InjectedScriptStage, content: string) => void; injectRoute: (injectRoute: InjectedRoute) => void; + handleRouteOptions: (handler: RouteOptionsHandler) => void; addClientDirective: (directive: ClientDirectiveConfig) => void; /** * @deprecated Use `addDevToolbarApp` instead. diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts index f0986e82ca7b..742126f327f2 100644 --- a/packages/astro/src/core/create-vite.ts +++ b/packages/astro/src/core/create-vite.ts @@ -132,7 +132,7 @@ export async function createVite( // The server plugin is for dev only and having it run during the build causes // the build to run very slow as the filewatcher is triggered often. mode !== 'build' && vitePluginAstroServer({ settings, logger, fs }), - envVitePlugin({ settings }), + envVitePlugin({ settings, logger }), astroEnv({ settings, mode, fs, sync }), markdownVitePlugin({ settings, logger }), htmlVitePlugin(), diff --git a/packages/astro/src/integrations/hooks.ts b/packages/astro/src/integrations/hooks.ts index 33a7c7c0c5ac..60705c1f64a2 100644 --- a/packages/astro/src/integrations/hooks.ts +++ b/packages/astro/src/integrations/hooks.ts @@ -182,6 +182,9 @@ export async function runHookConfigSetup({ } updatedSettings.injectedRoutes.push(injectRoute); }, + handleRouteOptions: (handler) => { + updatedSettings.routeOptionsHandlers.push(handler); + }, addWatchFile: (path) => { updatedSettings.watchFiles.push(path instanceof URL ? fileURLToPath(path) : path); }, diff --git a/packages/astro/src/vite-plugin-env/index.ts b/packages/astro/src/vite-plugin-env/index.ts index 00df8f4d36af..3222afbb2b92 100644 --- a/packages/astro/src/vite-plugin-env/index.ts +++ b/packages/astro/src/vite-plugin-env/index.ts @@ -1,12 +1,15 @@ import { fileURLToPath } from 'node:url'; import { transform } from 'esbuild'; +import { bold } from 'kleur/colors'; import MagicString from 'magic-string'; import type * as vite from 'vite'; import { loadEnv } from 'vite'; import type { AstroConfig, AstroSettings } from '../@types/astro.js'; +import type { Logger } from '../core/logger/core.js'; interface EnvPluginOptions { settings: AstroSettings; + logger: Logger; } // Match `import.meta.env` directly without trailing property access @@ -116,7 +119,7 @@ async function replaceDefine( }; } -export default function envVitePlugin({ settings }: EnvPluginOptions): vite.Plugin { +export default function envVitePlugin({ settings, logger }: EnvPluginOptions): vite.Plugin { let privateEnv: Record; let defaultDefines: Record; let isDev: boolean; @@ -170,13 +173,25 @@ export default function envVitePlugin({ settings }: EnvPluginOptions): vite.Plug s.prepend(devImportMetaEnvPrepend); // EDGE CASE: We need to do a static replacement for `export const prerender` for `vite-plugin-scanner` + // TODO: Remove in Astro 5 + let exportConstPrerenderStr: string | undefined; s.replace(exportConstPrerenderRe, (m, key) => { + exportConstPrerenderStr = m; if (privateEnv[key] != null) { return `export const prerender = ${privateEnv[key]}`; } else { return m; } }); + if (exportConstPrerenderStr) { + logger.warn( + 'router', + `Exporting dynamic values from prerender is deprecated. Please use an integration with the "astro:config:setup" hook ` + + `and update the route's prerender option with the \`handleRouteOptions()\` API. This allows for better treeshaking ` + + `and bundling configuration in the future. See for a migration example. \n` + + `Found \`${bold(exportConstPrerenderStr)}\` in ${bold(id)}.` + ); + } return { code: s.toString(), diff --git a/packages/astro/src/vite-plugin-scanner/scan.ts b/packages/astro/src/vite-plugin-scanner/scan.ts index 4e0e5fbfe027..46c8593cff6f 100644 --- a/packages/astro/src/vite-plugin-scanner/scan.ts +++ b/packages/astro/src/vite-plugin-scanner/scan.ts @@ -3,6 +3,7 @@ import type { PageOptions } from '../vite-plugin-astro/types.js'; import * as eslexer from 'es-module-lexer'; import { AstroError, AstroErrorData } from '../core/errors/index.js'; +import { rootRelativePath } from '../core/viteUtils.js'; const BOOLEAN_EXPORTS = new Set(['prerender']); @@ -36,6 +37,7 @@ function isFalsy(value: string) { let didInit = false; +// NOTE: `settings` is only undefined in tests export async function scan( code: string, id: string, @@ -82,5 +84,16 @@ export async function scan( } } + if (settings) { + + const route = { + component: rootRelativePath(settings.config.root, new URL(id, 'file://'), true), + prerender: pageOptions.prerender }; + for (const handler of settings.routeOptionsHandlers) { + handler(route); + } + pageOptions.prerender = route.prerender; + } + return pageOptions; } diff --git a/packages/integrations/node/test/prerender.test.js b/packages/integrations/node/test/prerender.test.js index d856d9d3e752..0a0b49705bb0 100644 --- a/packages/integrations/node/test/prerender.test.js +++ b/packages/integrations/node/test/prerender.test.js @@ -152,6 +152,81 @@ describe('Prerendering', () => { }); }); + describe('Via integration', () => { + before(async () => { + process.env.PRERENDER = false; + fixture = await loadFixture({ + root: './fixtures/prerender/', + output: 'server', + outDir: './dist/hybrid-via-integration', + build: { + client: './dist/hybrid-via-integration/client', + server: './dist/hybrid-via-integration/server', + }, + adapter: nodejs({ mode: 'standalone' }), + integrations: [ + { + name: 'test', + hooks: { + 'astro:config:setup': ({ handleRouteOptions }) => { + handleRouteOptions((route) => { + console.log + }) + } + } + } + ] + }); + await fixture.build(); + const { startServer } = await fixture.loadAdapterEntryModule(); + let res = startServer(); + server = res.server; + await waitServerListen(server.server); + }); + + after(async () => { + await server.stop(); + await fixture.clean(); + delete process.env.PRERENDER; + }); + + it('Can render SSR route', async () => { + const res = await fetch(`http://${server.host}:${server.port}/one`); + const html = await res.text(); + const $ = cheerio.load(html); + + assert.equal(res.status, 200); + assert.equal($('h1').text(), 'One'); + }); + + it('Can render prerendered route', async () => { + const res = await fetch(`http://${server.host}:${server.port}/two`); + const html = await res.text(); + const $ = cheerio.load(html); + + assert.equal(res.status, 200); + assert.equal($('h1').text(), 'Two'); + }); + + it('Can render prerendered route with redirect and query params', async () => { + const res = await fetch(`http://${server.host}:${server.port}/two?foo=bar`); + const html = await res.text(); + const $ = cheerio.load(html); + + assert.equal(res.status, 200); + assert.equal($('h1').text(), 'Two'); + }); + + it('Can render prerendered route with query params', async () => { + const res = await fetch(`http://${server.host}:${server.port}/two/?foo=bar`); + const html = await res.text(); + const $ = cheerio.load(html); + + assert.equal(res.status, 200); + assert.equal($('h1').text(), 'Two'); + }); + }); + describe('Dev', () => { let devServer; From 1dd0b8d422d983b256506c8d1ad475634837833f Mon Sep 17 00:00:00 2001 From: bluwy Date: Thu, 8 Aug 2024 22:49:58 +0800 Subject: [PATCH 02/10] done i think --- packages/astro/src/@types/astro.ts | 17 +++++++- packages/astro/src/core/config/settings.ts | 1 + packages/astro/src/vite-plugin-env/index.ts | 2 +- .../astro/src/vite-plugin-scanner/index.ts | 2 +- .../astro/src/vite-plugin-scanner/scan.ts | 15 ++++--- .../integrations/node/test/prerender.test.js | 43 +++++++------------ 6 files changed, 43 insertions(+), 37 deletions(-) diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index 862e53edf0a4..261a48dfd6b2 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -2223,7 +2223,22 @@ export interface ResolvedInjectedRoute extends InjectedRoute { resolvedEntryPoint?: URL; } -export type RouteOptionsHandler = (route: { prerender?: boolean }) => void; +export interface RouteOptions { + /** + * The path to this route relative to the project root. The slash is normalized as forward slash + * across all OS. + * @example "src/pages/blog/[...slug].astro" + */ + readonly component: string; + /** + * Whether this route should be prerendered. If the route has an explicit `prerender` export, + * the value will be passed here. Otherwise, it's undefined and will fallback to a prerender + * default depending on the `output` option. + */ + prerender?: boolean; +} + +export type RouteOptionsHandler = (route: RouteOptions) => void; /** * Resolved Astro Config diff --git a/packages/astro/src/core/config/settings.ts b/packages/astro/src/core/config/settings.ts index c3c62edd45a0..15ccfa367f6d 100644 --- a/packages/astro/src/core/config/settings.ts +++ b/packages/astro/src/core/config/settings.ts @@ -23,6 +23,7 @@ export function createBaseSettings(config: AstroConfig): AstroSettings { adapter: undefined, injectedRoutes: [], resolvedInjectedRoutes: [], + routeOptionsHandlers: [], serverIslandMap: new Map(), serverIslandNameMap: new Map(), pageExtensions: ['.astro', '.html', ...SUPPORTED_MARKDOWN_FILE_EXTENSIONS], diff --git a/packages/astro/src/vite-plugin-env/index.ts b/packages/astro/src/vite-plugin-env/index.ts index c0d4d5336396..a90de20f7818 100644 --- a/packages/astro/src/vite-plugin-env/index.ts +++ b/packages/astro/src/vite-plugin-env/index.ts @@ -189,7 +189,7 @@ export default function envVitePlugin({ settings, logger }: EnvPluginOptions): v `Exporting dynamic values from prerender is deprecated. Please use an integration with the "astro:config:setup" hook ` + `and update the route's prerender option with the \`handleRouteOptions()\` API. This allows for better treeshaking ` + `and bundling configuration in the future. See for a migration example. \n` + - `Found \`${bold(exportConstPrerenderStr)}\` in ${bold(id)}.` + `Found \`${bold(exportConstPrerenderStr)}\` in ${bold(id)}.`, ); } diff --git a/packages/astro/src/vite-plugin-scanner/index.ts b/packages/astro/src/vite-plugin-scanner/index.ts index 05be99a07422..64c7e05ef20e 100644 --- a/packages/astro/src/vite-plugin-scanner/index.ts +++ b/packages/astro/src/vite-plugin-scanner/index.ts @@ -40,7 +40,7 @@ export default function astroScannerPlugin({ const fileIsEndpoint = isEndpoint(fileURL, settings); if (!(fileIsPage || fileIsEndpoint)) return; const defaultPrerender = getPrerenderDefault(settings.config); - const pageOptions = await scan(code, id, settings); + const pageOptions = await scan(code, id, fileURL, settings); if (typeof pageOptions.prerender === 'undefined') { pageOptions.prerender = defaultPrerender; diff --git a/packages/astro/src/vite-plugin-scanner/scan.ts b/packages/astro/src/vite-plugin-scanner/scan.ts index 89730de03dbe..fa69d1e1acf8 100644 --- a/packages/astro/src/vite-plugin-scanner/scan.ts +++ b/packages/astro/src/vite-plugin-scanner/scan.ts @@ -1,4 +1,4 @@ -import type { AstroSettings } from '../@types/astro.js'; +import type { AstroSettings, RouteOptions } from '../@types/astro.js'; import type { PageOptions } from '../vite-plugin-astro/types.js'; import * as eslexer from 'es-module-lexer'; @@ -37,10 +37,11 @@ function isFalsy(value: string) { let didInit = false; -// NOTE: `settings` is only undefined in tests +// NOTE: `fileURL` and `settings` are only undefined in tests export async function scan( code: string, id: string, + fileURL?: URL, settings?: AstroSettings, ): Promise { if (!includesExport(code)) return {}; @@ -84,11 +85,11 @@ export async function scan( } } - if (settings) { - - const route = { - component: rootRelativePath(settings.config.root, new URL(id, 'file://'), true), - prerender: pageOptions.prerender }; + if (settings && fileURL) { + const route: RouteOptions = { + component: rootRelativePath(settings.config.root, fileURL, false), + prerender: pageOptions.prerender, + }; for (const handler of settings.routeOptionsHandlers) { handler(route); } diff --git a/packages/integrations/node/test/prerender.test.js b/packages/integrations/node/test/prerender.test.js index 0a0b49705bb0..b291abd070cb 100644 --- a/packages/integrations/node/test/prerender.test.js +++ b/packages/integrations/node/test/prerender.test.js @@ -57,6 +57,7 @@ describe('Prerendering', () => { assert.equal(res.status, 200); assert.equal($('h1').text(), 'Two'); + assert.ok(fixture.pathExists('/client/two/index.html')); }); it('Can render prerendered route with redirect and query params', async () => { @@ -131,6 +132,7 @@ describe('Prerendering', () => { assert.equal(res.status, 200); assert.equal($('h1').text(), 'Two'); + assert.ok(fixture.pathExists('/client/two/index.html')); }); it('Can render prerendered route with redirect and query params', async () => { @@ -158,10 +160,10 @@ describe('Prerendering', () => { fixture = await loadFixture({ root: './fixtures/prerender/', output: 'server', - outDir: './dist/hybrid-via-integration', + outDir: './dist/via-integration', build: { - client: './dist/hybrid-via-integration/client', - server: './dist/hybrid-via-integration/server', + client: './dist/via-integration/client', + server: './dist/via-integration/server', }, adapter: nodejs({ mode: 'standalone' }), integrations: [ @@ -170,12 +172,14 @@ describe('Prerendering', () => { hooks: { 'astro:config:setup': ({ handleRouteOptions }) => { handleRouteOptions((route) => { - console.log - }) - } - } - } - ] + if (route.component.endsWith('two.astro')) { + route.prerender = true; + } + }); + }, + }, + }, + ], }); await fixture.build(); const { startServer } = await fixture.loadAdapterEntryModule(); @@ -206,24 +210,7 @@ describe('Prerendering', () => { assert.equal(res.status, 200); assert.equal($('h1').text(), 'Two'); - }); - - it('Can render prerendered route with redirect and query params', async () => { - const res = await fetch(`http://${server.host}:${server.port}/two?foo=bar`); - const html = await res.text(); - const $ = cheerio.load(html); - - assert.equal(res.status, 200); - assert.equal($('h1').text(), 'Two'); - }); - - it('Can render prerendered route with query params', async () => { - const res = await fetch(`http://${server.host}:${server.port}/two/?foo=bar`); - const html = await res.text(); - const $ = cheerio.load(html); - - assert.equal(res.status, 200); - assert.equal($('h1').text(), 'Two'); + assert.ok(fixture.pathExists('/client/two/index.html')); }); }); @@ -318,6 +305,7 @@ describe('Hybrid rendering', () => { assert.equal(res.status, 200); assert.equal($('h1').text(), 'One'); + assert.ok(fixture.pathExists('/client/one/index.html')); }); it('Can render prerendered route with redirect and query params', async () => { @@ -391,6 +379,7 @@ describe('Hybrid rendering', () => { assert.equal(res.status, 200); assert.equal($('h1').text(), 'One'); + assert.ok(fixture.pathExists('/client/one/index.html')); }); it('Can render prerendered route with redirect and query params', async () => { From de8b1d8948e93477f98f8578389937091a9955ef Mon Sep 17 00:00:00 2001 From: bluwy Date: Thu, 8 Aug 2024 23:04:53 +0800 Subject: [PATCH 03/10] Add changeset --- .changeset/eleven-pens-glow.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .changeset/eleven-pens-glow.md diff --git a/.changeset/eleven-pens-glow.md b/.changeset/eleven-pens-glow.md new file mode 100644 index 000000000000..501581a914b1 --- /dev/null +++ b/.changeset/eleven-pens-glow.md @@ -0,0 +1,31 @@ +--- +'astro': minor +--- + +Deprecates exporting `prerender` with dynamic values. Only static values are supported by default. This allows for better treeshaking and bundling configuration in the future. + +To migrate, use an integration with the `"astro:config:setup"` hook and update the route's prerender option with the `handleRouteOptions()` API: + +```js +// astro.config.mjs +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + integrations: [setPrerender()], +}); + +function setPrerender() { + return { + name: 'set-prerender', + hooks: { + 'astro:config:setup': ({ handleRouteOptions }) => { + handleRouteOptions((route) => { + if (route.component.endsWith('/blog/[slug].astro')) { + route.prerender = true; + } + }); + }, + }, + }; +} +``` From 4cc1cd72175933eed7eab2009e5668e547ba6815 Mon Sep 17 00:00:00 2001 From: bluwy Date: Mon, 12 Aug 2024 23:19:47 +0800 Subject: [PATCH 04/10] Use hook instead --- .changeset/eleven-pens-glow.md | 12 +++-- packages/astro/src/@types/astro.ts | 8 ++-- packages/astro/src/core/config/settings.ts | 1 - packages/astro/src/integrations/hooks.ts | 45 +++++++++++++++++-- packages/astro/src/vite-plugin-env/index.ts | 8 ++-- .../astro/src/vite-plugin-scanner/index.ts | 2 +- .../astro/src/vite-plugin-scanner/scan.ts | 11 ++--- .../integrations/node/test/prerender.test.js | 10 ++--- 8 files changed, 66 insertions(+), 31 deletions(-) diff --git a/.changeset/eleven-pens-glow.md b/.changeset/eleven-pens-glow.md index 501581a914b1..73595e64b2b0 100644 --- a/.changeset/eleven-pens-glow.md +++ b/.changeset/eleven-pens-glow.md @@ -4,7 +4,7 @@ Deprecates exporting `prerender` with dynamic values. Only static values are supported by default. This allows for better treeshaking and bundling configuration in the future. -To migrate, use an integration with the `"astro:config:setup"` hook and update the route's prerender option with the `handleRouteOptions()` API: +To migrate, use an integration with the `"astro:route:setup"` hook and update the route's `prerender` option: ```js // astro.config.mjs @@ -18,12 +18,10 @@ function setPrerender() { return { name: 'set-prerender', hooks: { - 'astro:config:setup': ({ handleRouteOptions }) => { - handleRouteOptions((route) => { - if (route.component.endsWith('/blog/[slug].astro')) { - route.prerender = true; - } - }); + 'astro:route:setup': ({ route }) => { + if (route.component.endsWith('/blog/[slug].astro')) { + route.prerender = true; + } }, }, }; diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index c378119e6400..1ec2ec904866 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -2238,8 +2238,6 @@ export interface RouteOptions { prerender?: boolean; } -export type RouteOptionsHandler = (route: RouteOptions) => void; - /** * Resolved Astro Config * Config with user settings along with all defaults filled in. @@ -2371,7 +2369,6 @@ export interface AstroSettings { preferences: AstroPreferences; injectedRoutes: InjectedRoute[]; resolvedInjectedRoutes: ResolvedInjectedRoute[]; - routeOptionsHandlers: RouteOptionsHandler[]; pageExtensions: string[]; contentEntryTypes: ContentEntryType[]; dataEntryTypes: DataEntryType[]; @@ -3084,7 +3081,6 @@ declare global { addWatchFile: (path: URL | string) => void; injectScript: (stage: InjectedScriptStage, content: string) => void; injectRoute: (injectRoute: InjectedRoute) => void; - handleRouteOptions: (handler: RouteOptionsHandler) => void; addClientDirective: (directive: ClientDirectiveConfig) => void; /** * @deprecated Use `addDevToolbarApp` instead. @@ -3105,6 +3101,10 @@ declare global { setAdapter: (adapter: AstroAdapter) => void; logger: AstroIntegrationLogger; }) => void | Promise; + 'astro:route:setup': (options: { + route: RouteOptions; + logger: AstroIntegrationLogger; + }) => void | Promise; 'astro:server:setup': (options: { server: vite.ViteDevServer; logger: AstroIntegrationLogger; diff --git a/packages/astro/src/core/config/settings.ts b/packages/astro/src/core/config/settings.ts index 15ccfa367f6d..c3c62edd45a0 100644 --- a/packages/astro/src/core/config/settings.ts +++ b/packages/astro/src/core/config/settings.ts @@ -23,7 +23,6 @@ export function createBaseSettings(config: AstroConfig): AstroSettings { adapter: undefined, injectedRoutes: [], resolvedInjectedRoutes: [], - routeOptionsHandlers: [], serverIslandMap: new Map(), serverIslandNameMap: new Map(), pageExtensions: ['.astro', '.html', ...SUPPORTED_MARKDOWN_FILE_EXTENSIONS], diff --git a/packages/astro/src/integrations/hooks.ts b/packages/astro/src/integrations/hooks.ts index 0f489bafd982..32119a9d3557 100644 --- a/packages/astro/src/integrations/hooks.ts +++ b/packages/astro/src/integrations/hooks.ts @@ -13,6 +13,7 @@ import type { DataEntryType, HookParameters, RouteData, + RouteOptions, } from '../@types/astro.js'; import type { SerializedSSRManifest } from '../core/app/types.js'; import type { PageBuildData } from '../core/build/types.js'; @@ -182,9 +183,6 @@ export async function runHookConfigSetup({ } updatedSettings.injectedRoutes.push(injectRoute); }, - handleRouteOptions: (handler) => { - updatedSettings.routeOptionsHandlers.push(handler); - }, addWatchFile: (path) => { updatedSettings.watchFiles.push(path instanceof URL ? fileURLToPath(path) : path); }, @@ -338,6 +336,47 @@ export async function runHookConfigDone({ } } +export async function runHookRouteSetup({ + route, + settings, + logger, +}: { + route: RouteOptions; + settings: AstroSettings; + logger: Logger; +}) { + const prerenderChangeLogs: { integrationName: string; value: boolean | undefined }[] = []; + + for (const integration of settings.config.integrations) { + if (integration?.hooks?.['astro:route:setup']) { + const originalRoute = { ...route }; + const integrationLogger = getLogger(integration, logger); + + await withTakingALongTimeMsg({ + name: integration.name, + hookName: 'astro:route:setup', + hookResult: integration.hooks['astro:route:setup']({ + route, + logger: integrationLogger, + }), + logger, + }); + + if (route.prerender !== originalRoute.prerender) { + prerenderChangeLogs.push({ integrationName: integration.name, value: route.prerender }); + } + } + } + + if (prerenderChangeLogs.length > 1) { + logger.debug( + 'router', + `The ${route.component} route's prerender option has been changed multiple times by integrations:\n` + + prerenderChangeLogs.map((log) => `- ${log.integrationName}: ${log.value}`).join('\n'), + ); + } +} + export async function runHookServerSetup({ config, server, diff --git a/packages/astro/src/vite-plugin-env/index.ts b/packages/astro/src/vite-plugin-env/index.ts index a90de20f7818..350a29b7ffe1 100644 --- a/packages/astro/src/vite-plugin-env/index.ts +++ b/packages/astro/src/vite-plugin-env/index.ts @@ -186,10 +186,10 @@ export default function envVitePlugin({ settings, logger }: EnvPluginOptions): v if (exportConstPrerenderStr) { logger.warn( 'router', - `Exporting dynamic values from prerender is deprecated. Please use an integration with the "astro:config:setup" hook ` + - `and update the route's prerender option with the \`handleRouteOptions()\` API. This allows for better treeshaking ` + - `and bundling configuration in the future. See for a migration example. \n` + - `Found \`${bold(exportConstPrerenderStr)}\` in ${bold(id)}.`, + `Exporting dynamic values from prerender is deprecated. Please use an integration with the "astro:route:setup" hook ` + + `to update the route's \`prerender\` option instead. This allows for better treeshaking and bundling configuration ` + + `in the future. See https://docs.astro.build/en/reference/integrations-reference/#astroroutesetup for a migration example.` + + `\nFound \`${bold(exportConstPrerenderStr)}\` in ${bold(id)}.`, ); } diff --git a/packages/astro/src/vite-plugin-scanner/index.ts b/packages/astro/src/vite-plugin-scanner/index.ts index 64c7e05ef20e..20d2f1882866 100644 --- a/packages/astro/src/vite-plugin-scanner/index.ts +++ b/packages/astro/src/vite-plugin-scanner/index.ts @@ -40,7 +40,7 @@ export default function astroScannerPlugin({ const fileIsEndpoint = isEndpoint(fileURL, settings); if (!(fileIsPage || fileIsEndpoint)) return; const defaultPrerender = getPrerenderDefault(settings.config); - const pageOptions = await scan(code, id, fileURL, settings); + const pageOptions = await scan(code, id, fileURL, settings, logger); if (typeof pageOptions.prerender === 'undefined') { pageOptions.prerender = defaultPrerender; diff --git a/packages/astro/src/vite-plugin-scanner/scan.ts b/packages/astro/src/vite-plugin-scanner/scan.ts index fa69d1e1acf8..174785c581db 100644 --- a/packages/astro/src/vite-plugin-scanner/scan.ts +++ b/packages/astro/src/vite-plugin-scanner/scan.ts @@ -4,6 +4,8 @@ import type { PageOptions } from '../vite-plugin-astro/types.js'; import * as eslexer from 'es-module-lexer'; import { AstroError, AstroErrorData } from '../core/errors/index.js'; import { rootRelativePath } from '../core/viteUtils.js'; +import { runHookRouteSetup } from '../integrations/hooks.js'; +import type { Logger } from '../core/logger/core.js'; const BOOLEAN_EXPORTS = new Set(['prerender']); @@ -37,12 +39,13 @@ function isFalsy(value: string) { let didInit = false; -// NOTE: `fileURL` and `settings` are only undefined in tests +// NOTE: `fileURL`, `settings`, and `logger` are only undefined in tests export async function scan( code: string, id: string, fileURL?: URL, settings?: AstroSettings, + logger?: Logger, ): Promise { if (!includesExport(code)) return {}; if (!didInit) { @@ -85,14 +88,12 @@ export async function scan( } } - if (settings && fileURL) { + if (settings && logger && fileURL) { const route: RouteOptions = { component: rootRelativePath(settings.config.root, fileURL, false), prerender: pageOptions.prerender, }; - for (const handler of settings.routeOptionsHandlers) { - handler(route); - } + await runHookRouteSetup({ route, settings, logger }); pageOptions.prerender = route.prerender; } diff --git a/packages/integrations/node/test/prerender.test.js b/packages/integrations/node/test/prerender.test.js index b291abd070cb..e699a1b3c01b 100644 --- a/packages/integrations/node/test/prerender.test.js +++ b/packages/integrations/node/test/prerender.test.js @@ -170,12 +170,10 @@ describe('Prerendering', () => { { name: 'test', hooks: { - 'astro:config:setup': ({ handleRouteOptions }) => { - handleRouteOptions((route) => { - if (route.component.endsWith('two.astro')) { - route.prerender = true; - } - }); + 'astro:route:setup': ({ route }) => { + if (route.component.endsWith('two.astro')) { + route.prerender = true; + } }, }, }, From c60c8f637ba7f0f0df2f2ba8892114be73b6571e Mon Sep 17 00:00:00 2001 From: bluwy Date: Mon, 12 Aug 2024 23:54:09 +0800 Subject: [PATCH 05/10] Reorder hooks [skip ci] --- packages/astro/src/@types/astro.ts | 8 +-- packages/astro/src/integrations/hooks.ts | 82 ++++++++++++------------ 2 files changed, 45 insertions(+), 45 deletions(-) diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index 1ec2ec904866..7a9ecdcd3dff 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -3101,10 +3101,6 @@ declare global { setAdapter: (adapter: AstroAdapter) => void; logger: AstroIntegrationLogger; }) => void | Promise; - 'astro:route:setup': (options: { - route: RouteOptions; - logger: AstroIntegrationLogger; - }) => void | Promise; 'astro:server:setup': (options: { server: vite.ViteDevServer; logger: AstroIntegrationLogger; @@ -3147,6 +3143,10 @@ declare global { logger: AstroIntegrationLogger; cacheManifest: boolean; }) => void | Promise; + 'astro:route:setup': (options: { + route: RouteOptions; + logger: AstroIntegrationLogger; + }) => void | Promise; } } } diff --git a/packages/astro/src/integrations/hooks.ts b/packages/astro/src/integrations/hooks.ts index 32119a9d3557..e3de85ddd5f6 100644 --- a/packages/astro/src/integrations/hooks.ts +++ b/packages/astro/src/integrations/hooks.ts @@ -336,47 +336,6 @@ export async function runHookConfigDone({ } } -export async function runHookRouteSetup({ - route, - settings, - logger, -}: { - route: RouteOptions; - settings: AstroSettings; - logger: Logger; -}) { - const prerenderChangeLogs: { integrationName: string; value: boolean | undefined }[] = []; - - for (const integration of settings.config.integrations) { - if (integration?.hooks?.['astro:route:setup']) { - const originalRoute = { ...route }; - const integrationLogger = getLogger(integration, logger); - - await withTakingALongTimeMsg({ - name: integration.name, - hookName: 'astro:route:setup', - hookResult: integration.hooks['astro:route:setup']({ - route, - logger: integrationLogger, - }), - logger, - }); - - if (route.prerender !== originalRoute.prerender) { - prerenderChangeLogs.push({ integrationName: integration.name, value: route.prerender }); - } - } - } - - if (prerenderChangeLogs.length > 1) { - logger.debug( - 'router', - `The ${route.component} route's prerender option has been changed multiple times by integrations:\n` + - prerenderChangeLogs.map((log) => `- ${log.integrationName}: ${log.value}`).join('\n'), - ); - } -} - export async function runHookServerSetup({ config, server, @@ -600,6 +559,47 @@ export async function runHookBuildDone({ } } +export async function runHookRouteSetup({ + route, + settings, + logger, +}: { + route: RouteOptions; + settings: AstroSettings; + logger: Logger; +}) { + const prerenderChangeLogs: { integrationName: string; value: boolean | undefined }[] = []; + + for (const integration of settings.config.integrations) { + if (integration?.hooks?.['astro:route:setup']) { + const originalRoute = { ...route }; + const integrationLogger = getLogger(integration, logger); + + await withTakingALongTimeMsg({ + name: integration.name, + hookName: 'astro:route:setup', + hookResult: integration.hooks['astro:route:setup']({ + route, + logger: integrationLogger, + }), + logger, + }); + + if (route.prerender !== originalRoute.prerender) { + prerenderChangeLogs.push({ integrationName: integration.name, value: route.prerender }); + } + } + } + + if (prerenderChangeLogs.length > 1) { + logger.debug( + 'router', + `The ${route.component} route's prerender option has been changed multiple times by integrations:\n` + + prerenderChangeLogs.map((log) => `- ${log.integrationName}: ${log.value}`).join('\n'), + ); + } +} + export function isFunctionPerRouteEnabled(adapter: AstroAdapter | undefined): boolean { if (adapter?.adapterFeatures?.functionPerRoute === true) { return true; From 5170c61ed403d8720269c1e4b4d0415d1680307c Mon Sep 17 00:00:00 2001 From: Bjorn Lu Date: Tue, 13 Aug 2024 21:39:37 +0800 Subject: [PATCH 06/10] Update .changeset/eleven-pens-glow.md Co-authored-by: Sarah Rainsberger --- .changeset/eleven-pens-glow.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.changeset/eleven-pens-glow.md b/.changeset/eleven-pens-glow.md index 73595e64b2b0..3571335739e9 100644 --- a/.changeset/eleven-pens-glow.md +++ b/.changeset/eleven-pens-glow.md @@ -2,9 +2,18 @@ 'astro': minor --- -Deprecates exporting `prerender` with dynamic values. Only static values are supported by default. This allows for better treeshaking and bundling configuration in the future. +Deprecates the option for route-generating files to export a dynamic value for `prerender`. Only static values are now supported (e.g. `export const prerender = true` or `= false`). This allows for better treeshaking and bundling configuration in the future. -To migrate, use an integration with the `"astro:route:setup"` hook and update the route's `prerender` option: +Adds a new `"astro:route:setup"` hook to the Integrations API to allow you to dynamically set options for a route at build or request time through an integration, such as enabling [on-demand server rendering](/en/guides/server-side-rendering/#opting-in-to-pre-rendering-in-server-mode). + +To migrate from a dynamic export to the new hook, update or remove any dynamic `prerender` exports from individual routing files: + +```diff +// src/pages/blog/[slug].astro +- export const prerender = import.meta.env.PRERENDER +``` + +Instead, create an integration with the `"astro:route:setup"` hook and update the route's `prerender` option: ```js // astro.config.mjs From 111dbde18f7de49c8ab6510f71a4823dc52bca5d Mon Sep 17 00:00:00 2001 From: bluwy Date: Tue, 13 Aug 2024 21:58:20 +0800 Subject: [PATCH 07/10] Fix run --- packages/astro/src/core/util.ts | 2 +- .../astro/src/vite-plugin-scanner/index.ts | 36 +++++++++++++++---- .../astro/src/vite-plugin-scanner/scan.ts | 17 +-------- 3 files changed, 32 insertions(+), 23 deletions(-) diff --git a/packages/astro/src/core/util.ts b/packages/astro/src/core/util.ts index 2dd4ddd3b411..654d1982994c 100644 --- a/packages/astro/src/core/util.ts +++ b/packages/astro/src/core/util.ts @@ -153,7 +153,7 @@ export function isPage(file: URL, settings: AstroSettings): boolean { export function isEndpoint(file: URL, settings: AstroSettings): boolean { if (!isInPagesDir(file, settings.config)) return false; if (!isPublicRoute(file, settings.config)) return false; - return !endsWithPageExt(file, settings); + return !endsWithPageExt(file, settings) && !file.toString().includes('?astro'); } export function isServerLikeOutput(config: AstroConfig) { diff --git a/packages/astro/src/vite-plugin-scanner/index.ts b/packages/astro/src/vite-plugin-scanner/index.ts index 20d2f1882866..842857777a87 100644 --- a/packages/astro/src/vite-plugin-scanner/index.ts +++ b/packages/astro/src/vite-plugin-scanner/index.ts @@ -2,11 +2,13 @@ import { extname } from 'node:path'; import { bold } from 'kleur/colors'; import type { Plugin as VitePlugin } from 'vite'; import { normalizePath } from 'vite'; -import type { AstroSettings } from '../@types/astro.js'; +import type { AstroSettings, RouteOptions } from '../@types/astro.js'; import { type Logger } from '../core/logger/core.js'; import { isEndpoint, isPage, isServerLikeOutput } from '../core/util.js'; import { rootRelativePath } from '../core/viteUtils.js'; +import { runHookRouteSetup } from '../integrations/hooks.js'; import { getPrerenderDefault } from '../prerender/utils.js'; +import type { PageOptions } from '../vite-plugin-astro/types.js'; import { scan } from './scan.js'; export interface AstroPluginScannerOptions { @@ -39,12 +41,8 @@ export default function astroScannerPlugin({ const fileIsPage = isPage(fileURL, settings); const fileIsEndpoint = isEndpoint(fileURL, settings); if (!(fileIsPage || fileIsEndpoint)) return; - const defaultPrerender = getPrerenderDefault(settings.config); - const pageOptions = await scan(code, id, fileURL, settings, logger); + const pageOptions = await getPageOptions(code, id, fileURL, settings, logger); - if (typeof pageOptions.prerender === 'undefined') { - pageOptions.prerender = defaultPrerender; - } // `getStaticPaths` warning is just a string check, should be good enough for most cases if ( !pageOptions.prerender && @@ -76,3 +74,29 @@ export default function astroScannerPlugin({ }, }; } + +async function getPageOptions( + code: string, + id: string, + fileURL: URL, + settings: AstroSettings, + logger: Logger, +): Promise { + // Run initial scan + const pageOptions = await scan(code, id, settings); + + // Run integration hooks to alter page options + const route: RouteOptions = { + component: rootRelativePath(settings.config.root, fileURL, false), + prerender: pageOptions.prerender, + }; + await runHookRouteSetup({ route, settings, logger }); + pageOptions.prerender = route.prerender; + + // Fallback if unset + if (typeof pageOptions.prerender === 'undefined') { + pageOptions.prerender = getPrerenderDefault(settings.config); + } + + return pageOptions; +} diff --git a/packages/astro/src/vite-plugin-scanner/scan.ts b/packages/astro/src/vite-plugin-scanner/scan.ts index 174785c581db..f447a1f28f3e 100644 --- a/packages/astro/src/vite-plugin-scanner/scan.ts +++ b/packages/astro/src/vite-plugin-scanner/scan.ts @@ -1,11 +1,8 @@ -import type { AstroSettings, RouteOptions } from '../@types/astro.js'; +import type { AstroSettings } from '../@types/astro.js'; import type { PageOptions } from '../vite-plugin-astro/types.js'; import * as eslexer from 'es-module-lexer'; import { AstroError, AstroErrorData } from '../core/errors/index.js'; -import { rootRelativePath } from '../core/viteUtils.js'; -import { runHookRouteSetup } from '../integrations/hooks.js'; -import type { Logger } from '../core/logger/core.js'; const BOOLEAN_EXPORTS = new Set(['prerender']); @@ -39,13 +36,10 @@ function isFalsy(value: string) { let didInit = false; -// NOTE: `fileURL`, `settings`, and `logger` are only undefined in tests export async function scan( code: string, id: string, - fileURL?: URL, settings?: AstroSettings, - logger?: Logger, ): Promise { if (!includesExport(code)) return {}; if (!didInit) { @@ -88,14 +82,5 @@ export async function scan( } } - if (settings && logger && fileURL) { - const route: RouteOptions = { - component: rootRelativePath(settings.config.root, fileURL, false), - prerender: pageOptions.prerender, - }; - await runHookRouteSetup({ route, settings, logger }); - pageOptions.prerender = route.prerender; - } - return pageOptions; } From 9009aab6df14edf6bb5b200571e1e95ea8f34655 Mon Sep 17 00:00:00 2001 From: bluwy Date: Tue, 13 Aug 2024 22:00:44 +0800 Subject: [PATCH 08/10] Fix link --- .changeset/eleven-pens-glow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/eleven-pens-glow.md b/.changeset/eleven-pens-glow.md index 3571335739e9..29a46271f838 100644 --- a/.changeset/eleven-pens-glow.md +++ b/.changeset/eleven-pens-glow.md @@ -4,7 +4,7 @@ Deprecates the option for route-generating files to export a dynamic value for `prerender`. Only static values are now supported (e.g. `export const prerender = true` or `= false`). This allows for better treeshaking and bundling configuration in the future. -Adds a new `"astro:route:setup"` hook to the Integrations API to allow you to dynamically set options for a route at build or request time through an integration, such as enabling [on-demand server rendering](/en/guides/server-side-rendering/#opting-in-to-pre-rendering-in-server-mode). +Adds a new `"astro:route:setup"` hook to the Integrations API to allow you to dynamically set options for a route at build or request time through an integration, such as enabling [on-demand server rendering](https://docs.astro.build/en/guides/server-side-rendering/#opting-in-to-pre-rendering-in-server-mode). To migrate from a dynamic export to the new hook, update or remove any dynamic `prerender` exports from individual routing files: From c91dd973012650bd0d4d652d0d9166809d6ca19d Mon Sep 17 00:00:00 2001 From: bluwy Date: Tue, 13 Aug 2024 22:03:43 +0800 Subject: [PATCH 09/10] Add link Co-authored-by: Sarah Rainsberger --- .changeset/eleven-pens-glow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/eleven-pens-glow.md b/.changeset/eleven-pens-glow.md index 29a46271f838..52488eba59dc 100644 --- a/.changeset/eleven-pens-glow.md +++ b/.changeset/eleven-pens-glow.md @@ -4,7 +4,7 @@ Deprecates the option for route-generating files to export a dynamic value for `prerender`. Only static values are now supported (e.g. `export const prerender = true` or `= false`). This allows for better treeshaking and bundling configuration in the future. -Adds a new `"astro:route:setup"` hook to the Integrations API to allow you to dynamically set options for a route at build or request time through an integration, such as enabling [on-demand server rendering](https://docs.astro.build/en/guides/server-side-rendering/#opting-in-to-pre-rendering-in-server-mode). +Adds a new [`"astro:route:setup"` hook](https://docs.astro.build/en/reference/integrations-reference/#astroroutesetup) to the Integrations API to allow you to dynamically set options for a route at build or request time through an integration, such as enabling [on-demand server rendering](https://docs.astro.build/en/guides/server-side-rendering/#opting-in-to-pre-rendering-in-server-mode). To migrate from a dynamic export to the new hook, update or remove any dynamic `prerender` exports from individual routing files: From 16069fb32dd35a28eea613ef5f6abae3d9ea926d Mon Sep 17 00:00:00 2001 From: bluwy Date: Tue, 13 Aug 2024 22:24:39 +0800 Subject: [PATCH 10/10] More accurate migration [skip ci] --- .changeset/eleven-pens-glow.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.changeset/eleven-pens-glow.md b/.changeset/eleven-pens-glow.md index 52488eba59dc..d031fba49c70 100644 --- a/.changeset/eleven-pens-glow.md +++ b/.changeset/eleven-pens-glow.md @@ -18,18 +18,21 @@ Instead, create an integration with the `"astro:route:setup"` hook and update th ```js // astro.config.mjs import { defineConfig } from 'astro/config'; +import { loadEnv } from 'vite'; export default defineConfig({ integrations: [setPrerender()], }); function setPrerender() { + const { PRERENDER } = loadEnv(process.env.NODE_ENV, process.cwd(), ''); + return { name: 'set-prerender', hooks: { 'astro:route:setup': ({ route }) => { if (route.component.endsWith('/blog/[slug].astro')) { - route.prerender = true; + route.prerender = PRERENDER; } }, },