Skip to content

Experimental Prerender API #5297

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

Merged
merged 46 commits into from
Dec 16, 2022
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
ada9899
wip: hybrid output
natemoo-re Oct 25, 2022
4a7ddbc
wip: hybrid output mvp
natemoo-re Oct 26, 2022
a1e660c
refactor: move hybrid => server
natemoo-re Nov 2, 2022
df4af7e
wip: hybrid support for `output: 'server'`
natemoo-re Nov 3, 2022
cce444d
feat(hybrid): overwrite static files
natemoo-re Nov 3, 2022
972106f
fix: update static build
natemoo-re Nov 4, 2022
600b5f2
feat(hybrid): skip page generation if no static entrypoints
natemoo-re Nov 4, 2022
f135155
feat: migrate from hybrid output => prerender flag
natemoo-re Nov 8, 2022
d88a9af
fix: appease typescript
natemoo-re Nov 8, 2022
b9c69c3
fix: appease typescript
natemoo-re Nov 8, 2022
b88f2aa
fix: appease typescript
natemoo-re Nov 8, 2022
2acf3a8
fix: appease typescript
natemoo-re Nov 8, 2022
2412fab
fix: improve static cleanup
natemoo-re Nov 8, 2022
fd70e94
attempt: avoid preprocess scanning
natemoo-re Nov 8, 2022
5aab0b5
hack: force generated .js files to be treated as ESM
natemoo-re Nov 8, 2022
76e14f6
better handling for astro metadata
natemoo-re Nov 8, 2022
49e14ab
fix: update scanner plugin
natemoo-re Nov 23, 2022
0663dfa
fix: page name bug
natemoo-re Nov 23, 2022
5133b03
fix: keep ssr false when generating pages
natemoo-re Dec 5, 2022
b1fe3f4
fix: force output to be treated as ESM
natemoo-re Dec 5, 2022
6835c6d
fix: client output should respect buildConfig
natemoo-re Dec 5, 2022
e202954
fix: ensure outDir is always created
natemoo-re Dec 5, 2022
126c979
fix: do not replace files with noop
natemoo-re Dec 9, 2022
f3af698
fix(netlify): add support for `experimental_prerender` pages
natemoo-re Dec 9, 2022
8dbd664
feat: switch to `experimental_prerender`
natemoo-re Dec 9, 2022
5d5b240
chore: update es-module-lexer code in test
natemoo-re Dec 9, 2022
c4274db
feat: improved code-splitting, cleanup
natemoo-re Dec 13, 2022
163cea4
feat: move prerender behind flag
natemoo-re Dec 14, 2022
035c376
test: prerender
natemoo-re Dec 15, 2022
2f90932
test: update prerender test
natemoo-re Dec 15, 2022
a7c6d34
chore: update lockfile
natemoo-re Dec 15, 2022
ed43e8c
fix: only match `.html` files when resolving assets
natemoo-re Dec 15, 2022
5c8ba60
chore: update changeset
natemoo-re Dec 15, 2022
2028e45
chore: remove ESM hack
natemoo-re Dec 15, 2022
9e4a190
chore: allow `--experimental-prerender` flag, move `--experimental-er…
natemoo-re Dec 15, 2022
02e616f
chore: update changeset
natemoo-re Dec 15, 2022
08e2d98
test(vite-plugin-scanner): add proper unit tests for vite-plugin-scanner
natemoo-re Dec 15, 2022
0c9ea57
chore: remove leftover code
natemoo-re Dec 15, 2022
7aa1914
chore: add comment on cleanup task
natemoo-re Dec 15, 2022
ecc44ab
refactor: move manual chunks logic to vite-plugin-prerender
natemoo-re Dec 15, 2022
d1546a2
fix: do not support let declarations
natemoo-re Dec 16, 2022
b17b6dd
test: add var test
natemoo-re Dec 16, 2022
c233008
refactor: prefer existing util
natemoo-re Dec 16, 2022
490eefe
Update packages/astro/src/@types/astro.ts
natemoo-re Dec 16, 2022
1ac3eff
Update packages/astro/src/core/errors/errors-data.ts
natemoo-re Dec 16, 2022
fc51dd8
Update packages/astro/src/@types/astro.ts
natemoo-re Dec 16, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .changeset/funny-waves-worry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
'astro': minor
'@astrojs/netlify': minor
---

Introduces the **experimental** Prerender API.

> **Note**
> This API is not yet stable and is subject to possible breaking changes!

- Deploy an Astro server without sacrificing the speed or cacheability of static HTML.
- The Prerender API allows you to statically prerender specific `pages/` at build time.

**Usage**

- First, run `astro build --experimental-prerender` or enable `experimental: { prerender: true }` in your `astro.config.mjs` file.
- Then, include `export const prerender = true` in any file in the `pages/` directory that you wish to prerender.
2 changes: 1 addition & 1 deletion packages/astro/e2e/error-cyclic.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { expect } from '@playwright/test';
import { testFactory, getErrorOverlayContent } from './test-utils.js';

const test = testFactory({
experimentalErrorOverlay: true,
experimental: { errorOverlay: true },
root: './fixtures/error-cyclic/',
});

Expand Down
2 changes: 1 addition & 1 deletion packages/astro/e2e/error-react-spectrum.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { expect } from '@playwright/test';
import { testFactory, getErrorOverlayContent } from './test-utils.js';

const test = testFactory({
experimentalErrorOverlay: true,
experimental: { errorOverlay: true },
root: './fixtures/error-react-spectrum/',
});

Expand Down
2 changes: 1 addition & 1 deletion packages/astro/e2e/error-sass.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { expect } from '@playwright/test';
import { testFactory, getErrorOverlayContent } from './test-utils.js';

const test = testFactory({
experimentalErrorOverlay: true,
experimental: { errorOverlay: true },
root: './fixtures/error-sass/',
});

Expand Down
2 changes: 1 addition & 1 deletion packages/astro/e2e/errors.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { expect } from '@playwright/test';
import { getErrorOverlayContent, testFactory } from './test-utils.js';

const test = testFactory({
experimentalErrorOverlay: true,
experimental: { errorOverlay: true },
root: './fixtures/errors/',
});

Expand Down
2 changes: 1 addition & 1 deletion packages/astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@
"debug": "^4.3.4",
"deepmerge-ts": "^4.2.2",
"diff": "^5.1.0",
"es-module-lexer": "^0.10.5",
"es-module-lexer": "^1.1.0",
"execa": "^6.1.0",
"fast-glob": "^3.2.11",
"github-slugger": "^1.4.0",
Expand Down
38 changes: 34 additions & 4 deletions packages/astro/src/@types/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export interface CLIFlags {
config?: string;
drafts?: boolean;
experimentalErrorOverlay?: boolean;
experimentalPrerender?: boolean;
}

export interface BuildConfig {
Expand Down Expand Up @@ -895,11 +896,40 @@ export interface AstroUserConfig {
astroFlavoredMarkdown?: boolean;
};

/**
* @hidden
* Turn on experimental support for the new error overlay component.
/**
* @docs
* @kind heading
* @name Experimental Flags
* @description
* Astro offers experimental flags to give users early access to new features.
* These flags are not guaranteed to be stable.
*/
experimentalErrorOverlay?: boolean;
experimental?: {
/**
* @hidden
* Turn on experimental support for the new error overlay component.
*/
errorOverlay?: boolean;
/**
* @docs
* @name experimental.prerender
* @type {boolean}
* @default `false`
* @version 1.7.0
* @description
* Enable experimental support for Prerendered pages when generating a server.
*
* To enable this feature, set `experimental.prerender` to `true` in your Astro config:
*
* ```js
* {
* experimental: {
* prerender: true,
* },
* }
*/
prerender?: boolean;
};

// Legacy options to be removed

Expand Down
4 changes: 3 additions & 1 deletion packages/astro/src/core/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
createLinkStylesheetElementSet,
createModuleScriptElement,
} from '../render/ssr-element.js';
import { matchRoute } from '../routing/match.js';
import { matchAssets, matchRoute } from '../routing/match.js';
export { deserializeManifest } from './common.js';

export const pagesVirtualModuleId = '@astrojs-pages-virtual-entry';
Expand Down Expand Up @@ -100,6 +100,8 @@ export class App {
let routeData = matchRoute(pathname, this.#manifestData);

if (routeData) {
const asset = matchAssets(routeData, this.#manifest.assets);
if (asset) return undefined;
return routeData;
} else if (matchNotFound) {
return matchRoute('/404', this.#manifestData);
Expand Down
9 changes: 7 additions & 2 deletions packages/astro/src/core/build/common.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import npath from 'path';
import { createHash } from 'crypto'
import { fileURLToPath, pathToFileURL } from 'url';
import type { AstroConfig, RouteType } from '../../@types/astro';
import { appendForwardSlash } from '../../core/path.js';
Expand All @@ -7,7 +8,11 @@ const STATUS_CODE_PAGES = new Set(['/404', '/500']);
const FALLBACK_OUT_DIR_NAME = './.astro/';

function getOutRoot(astroConfig: AstroConfig): URL {
return new URL('./', astroConfig.outDir);
if (astroConfig.output === 'static') {
return new URL('./', astroConfig.outDir);
} else {
return new URL('./', astroConfig.build.client);
}
}

export function getOutFolder(
Expand Down Expand Up @@ -41,7 +46,7 @@ export function getOutFile(
astroConfig: AstroConfig,
outFolder: URL,
pathname: string,
routeType: RouteType
routeType: RouteType,
): URL {
switch (routeType) {
case 'endpoint':
Expand Down
31 changes: 23 additions & 8 deletions packages/astro/src/core/build/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type {
RouteType,
SSRLoadedRenderer,
} from '../../@types/astro';
import type { BuildInternals } from '../../core/build/internal.js';
import { BuildInternals, hasPrerenderedPages } from '../../core/build/internal.js';
import {
prependForwardSlash,
removeLeadingForwardSlash,
Expand All @@ -29,7 +29,12 @@ import { createRequest } from '../request.js';
import { matchRoute } from '../routing/match.js';
import { getOutputFilename } from '../util.js';
import { getOutDirWithinCwd, getOutFile, getOutFolder } from './common.js';
import { eachPageData, getPageDataByComponent, sortedCSS } from './internal.js';
import {
eachPrerenderedPageData,
eachPageData,
getPageDataByComponent,
sortedCSS,
} from './internal.js';
import type { PageBuildData, SingleFileBuiltModule, StaticBuildOptions } from './types';
import { getTimeStat } from './util.js';

Expand Down Expand Up @@ -70,17 +75,27 @@ export function chunkIsPage(

export async function generatePages(opts: StaticBuildOptions, internals: BuildInternals) {
const timer = performance.now();
info(opts.logging, null, `\n${bgGreen(black(' generating static routes '))}`);

const ssr = opts.settings.config.output === 'server';
const serverEntry = opts.buildConfig.serverEntry;
const outFolder = ssr ? opts.buildConfig.server : getOutDirWithinCwd(opts.settings.config.outDir);

if (opts.settings.config.experimental.prerender && opts.settings.config.output === 'server' && !hasPrerenderedPages(internals)) return;

const verb = ssr ? 'prerendering' : 'generating';
info(opts.logging, null, `\n${bgGreen(black(` ${verb} static routes `))}`);

const ssrEntryURL = new URL('./' + serverEntry + `?time=${Date.now()}`, outFolder);
const ssrEntry = await import(ssrEntryURL.toString());
const builtPaths = new Set<string>();

for (const pageData of eachPageData(internals)) {
await generatePage(opts, internals, pageData, ssrEntry, builtPaths);
if (opts.settings.config.experimental.prerender && opts.settings.config.output === 'server') {
for (const pageData of eachPrerenderedPageData(internals)) {
await generatePage(opts, internals, pageData, ssrEntry, builtPaths);
}
} else {
for (const pageData of eachPageData(internals)) {
await generatePage(opts, internals, pageData, ssrEntry, builtPaths);
}
}

await runHookBuildGenerated({
Expand All @@ -106,7 +121,7 @@ async function generatePage(
const linkIds: string[] = sortedCSS(pageData);
const scripts = pageInfo?.hoistedScript ?? null;

const pageModule = ssrEntry.pageMap.get(pageData.component);
const pageModule = ssrEntry.pageMap?.get(pageData.component);

if (!pageModule) {
throw new Error(
Expand Down Expand Up @@ -163,7 +178,7 @@ async function getPathsForRoute(
route: pageData.route,
isValidate: false,
logging: opts.logging,
ssr: opts.settings.config.output === 'server',
ssr: false,
})
.then((_result) => {
const label = _result.staticPaths.length === 1 ? 'page' : 'pages';
Expand Down
40 changes: 39 additions & 1 deletion packages/astro/src/core/build/internal.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import type { OutputChunk, RenderedChunk } from 'rollup';
import type { PageBuildData, ViteID } from './types';
import type { PageBuildData, PageOutput, ViteID } from './types';

import { prependForwardSlash, removeFileExtension } from '../path.js';
import { viteID } from '../util.js';
import { PageOptions } from '../../vite-plugin-astro/types';

export interface BuildInternals {
/**
Expand All @@ -20,11 +21,21 @@ export interface BuildInternals {
// Used to render pages with the correct specifiers.
entrySpecifierToBundleMap: Map<string, string>;

/**
* A map to get a specific page's bundled output file.
*/
pageToBundleMap: Map<string, string>;

/**
* A map for page-specific information.
*/
pagesByComponent: Map<string, PageBuildData>;

/**
* A map for page-specific output.
*/
pageOptionsByPage: Map<string, PageOptions>;

/**
* A map for page-specific information by Vite ID (a path-like string)
*/
Expand Down Expand Up @@ -73,8 +84,10 @@ export function createBuildInternals(): BuildInternals {
hoistedScriptIdToHoistedMap,
hoistedScriptIdToPagesMap,
entrySpecifierToBundleMap: new Map<string, string>(),
pageToBundleMap: new Map<string, string>(),

pagesByComponent: new Map(),
pageOptionsByPage: new Map(),
pagesByViteID: new Map(),
pagesByClientOnly: new Map(),

Expand Down Expand Up @@ -189,6 +202,31 @@ export function* eachPageData(internals: BuildInternals) {
yield* internals.pagesByComponent.values();
}

export function hasPrerenderedPages(internals: BuildInternals) {
for (const id of internals.pagesByViteID.keys()) {
if (internals.pageOptionsByPage.get(id)?.prerender) {
return true
}
}
return false
}

export function* eachPrerenderedPageData(internals: BuildInternals) {
for (const [id, pageData] of internals.pagesByViteID.entries()) {
if (internals.pageOptionsByPage.get(id)?.prerender) {
yield pageData;
}
}
}

export function* eachServerPageData(internals: BuildInternals) {
for (const [id, pageData] of internals.pagesByViteID.entries()) {
if (!internals.pageOptionsByPage.get(id)?.prerender) {
yield pageData;
}
}
}

/**
* Sort a page's CSS by depth. A higher depth means that the CSS comes from shared subcomponents.
* A lower depth means it comes directly from the top-level page.
Expand Down
Loading