summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Bjorn Lu <bjornlu.dev@gmail.com> 2024-08-14 18:05:19 +0800
committerGravatar GitHub <noreply@github.com> 2024-08-14 11:05:19 +0100
commita23c69d0d0bed229bee52a32e61f135f9ebf9122 (patch)
tree080a91484d91d69ea24ce01b8642ef86c9adee50
parentd3d99fba269da9e812e748539a11dfed785ef8a4 (diff)
downloadastro-a23c69d0d0bed229bee52a32e61f135f9ebf9122.tar.gz
astro-a23c69d0d0bed229bee52a32e61f135f9ebf9122.tar.zst
astro-a23c69d0d0bed229bee52a32e61f135f9ebf9122.zip
Deprecates exporting prerender with dynamic values (#11657)
* wip * done i think * Add changeset * Use hook instead * Reorder hooks [skip ci] * Update .changeset/eleven-pens-glow.md Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Fix run * Fix link * Add link Co-authored-by: Sarah Rainsberger <sarah11918@users.noreply.github.com> * More accurate migration [skip ci] --------- Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> Co-authored-by: Sarah Rainsberger <sarah11918@users.noreply.github.com>
-rw-r--r--.changeset/eleven-pens-glow.md41
-rw-r--r--packages/astro/src/@types/astro.ts19
-rw-r--r--packages/astro/src/core/create-vite.ts2
-rw-r--r--packages/astro/src/core/util.ts2
-rw-r--r--packages/astro/src/integrations/hooks.ts42
-rw-r--r--packages/astro/src/vite-plugin-env/index.ts17
-rw-r--r--packages/astro/src/vite-plugin-scanner/index.ts36
-rw-r--r--packages/integrations/node/test/prerender.test.js62
8 files changed, 212 insertions, 9 deletions
diff --git a/.changeset/eleven-pens-glow.md b/.changeset/eleven-pens-glow.md
new file mode 100644
index 000000000..d031fba49
--- /dev/null
+++ b/.changeset/eleven-pens-glow.md
@@ -0,0 +1,41 @@
+---
+'astro': minor
+---
+
+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](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:
+
+```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
+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 = PRERENDER;
+ }
+ },
+ },
+ };
+}
+```
diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts
index e9c42954a..645b07900 100644
--- a/packages/astro/src/@types/astro.ts
+++ b/packages/astro/src/@types/astro.ts
@@ -2223,6 +2223,21 @@ export interface ResolvedInjectedRoute extends InjectedRoute {
resolvedEntryPoint?: URL;
}
+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;
+}
+
/**
* Resolved Astro Config
* Config with user settings along with all defaults filled in.
@@ -3128,6 +3143,10 @@ declare global {
logger: AstroIntegrationLogger;
cacheManifest: boolean;
}) => void | Promise<void>;
+ 'astro:route:setup': (options: {
+ route: RouteOptions;
+ logger: AstroIntegrationLogger;
+ }) => void | Promise<void>;
}
}
}
diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts
index 0570d9d5d..23a4068a8 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/core/util.ts b/packages/astro/src/core/util.ts
index 2dd4ddd3b..654d19829 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/integrations/hooks.ts b/packages/astro/src/integrations/hooks.ts
index 9b2859e48..e3de85ddd 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';
@@ -558,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;
diff --git a/packages/astro/src/vite-plugin-env/index.ts b/packages/astro/src/vite-plugin-env/index.ts
index ea75e752f..350a29b7f 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<string, string>;
let defaultDefines: Record<string, string>;
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: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)}.`,
+ );
+ }
return {
code: s.toString(),
diff --git a/packages/astro/src/vite-plugin-scanner/index.ts b/packages/astro/src/vite-plugin-scanner/index.ts
index 05be99a07..842857777 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, settings);
+ 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<PageOptions> {
+ // 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/integrations/node/test/prerender.test.js b/packages/integrations/node/test/prerender.test.js
index d856d9d3e..e699a1b3c 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 () => {
@@ -152,6 +154,64 @@ describe('Prerendering', () => {
});
});
+ describe('Via integration', () => {
+ before(async () => {
+ process.env.PRERENDER = false;
+ fixture = await loadFixture({
+ root: './fixtures/prerender/',
+ output: 'server',
+ outDir: './dist/via-integration',
+ build: {
+ client: './dist/via-integration/client',
+ server: './dist/via-integration/server',
+ },
+ adapter: nodejs({ mode: 'standalone' }),
+ integrations: [
+ {
+ name: 'test',
+ hooks: {
+ 'astro:route:setup': ({ route }) => {
+ if (route.component.endsWith('two.astro')) {
+ route.prerender = true;
+ }
+ },
+ },
+ },
+ ],
+ });
+ 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');
+ assert.ok(fixture.pathExists('/client/two/index.html'));
+ });
+ });
+
describe('Dev', () => {
let devServer;
@@ -243,6 +303,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 () => {
@@ -316,6 +377,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 () => {