Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deprecates exporting prerender with dynamic values #11657

Merged
merged 12 commits into from
Aug 14, 2024
29 changes: 29 additions & 0 deletions .changeset/eleven-pens-glow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
'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:route:setup"` hook and update the route's `prerender` option:
bluwy marked this conversation as resolved.
Show resolved Hide resolved

```js
// astro.config.mjs
import { defineConfig } from 'astro/config';

export default defineConfig({
integrations: [setPrerender()],
});

function setPrerender() {
return {
name: 'set-prerender',
hooks: {
'astro:route:setup': ({ route }) => {
if (route.component.endsWith('/blog/[slug].astro')) {
route.prerender = true;
}
},
},
};
}
```
19 changes: 19 additions & 0 deletions packages/astro/src/@types/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -3128,6 +3143,10 @@ declare global {
logger: AstroIntegrationLogger;
cacheManifest: boolean;
}) => void | Promise<void>;
'astro:route:setup': (options: {
route: RouteOptions;
logger: AstroIntegrationLogger;
}) => void | Promise<void>;
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/core/create-vite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
42 changes: 42 additions & 0 deletions packages/astro/src/integrations/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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']) {
bluwy marked this conversation as resolved.
Show resolved Hide resolved
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;
Expand Down
17 changes: 16 additions & 1 deletion packages/astro/src/vite-plugin-env/index.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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(),
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/vite-plugin-scanner/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, logger);

if (typeof pageOptions.prerender === 'undefined') {
pageOptions.prerender = defaultPrerender;
Expand Down
17 changes: 16 additions & 1 deletion packages/astro/src/vite-plugin-scanner/scan.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
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';
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']);

Expand Down Expand Up @@ -36,10 +39,13 @@ 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<PageOptions> {
if (!includesExport(code)) return {};
if (!didInit) {
Expand Down Expand Up @@ -82,5 +88,14 @@ 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;
}
62 changes: 62 additions & 0 deletions packages/integrations/node/test/prerender.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down Expand Up @@ -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 () => {
Expand All @@ -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;

Expand Down Expand Up @@ -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 () => {
Expand Down Expand Up @@ -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 () => {
Expand Down
Loading