summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/astro/package.json1
-rw-r--r--packages/astro/src/@types/astro.ts4
-rw-r--r--packages/astro/src/cli/index.ts17
-rw-r--r--packages/astro/src/core/build/index.ts20
-rw-r--r--packages/astro/src/core/build/page-data.ts78
-rw-r--r--packages/astro/src/core/build/scan-based-build.ts3
-rw-r--r--packages/astro/src/core/build/static-build.ts16
-rw-r--r--packages/astro/src/core/logger.ts26
-rw-r--r--packages/astro/src/core/ssr/index.ts68
-rw-r--r--packages/astro/src/core/ssr/route-cache.ts76
-rw-r--r--packages/astro/src/core/ssr/rss.ts50
-rw-r--r--packages/astro/src/vite-plugin-astro-server/index.ts22
-rw-r--r--packages/astro/src/vite-plugin-build-html/index.ts3
-rw-r--r--yarn.lock2
14 files changed, 190 insertions, 196 deletions
diff --git a/packages/astro/package.json b/packages/astro/package.json
index bd21e98d1..494ca3f49 100644
--- a/packages/astro/package.json
+++ b/packages/astro/package.json
@@ -69,6 +69,7 @@
"@proload/core": "^0.2.1",
"@proload/plugin-tsm": "^0.1.0",
"@types/babel__core": "^7.1.15",
+ "@types/debug": "^4.1.7",
"@web/parse5-utils": "^1.3.0",
"astring": "^1.7.5",
"ci-info": "^3.2.0",
diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts
index abe62b13a..dd43cba5c 100644
--- a/packages/astro/src/@types/astro.ts
+++ b/packages/astro/src/@types/astro.ts
@@ -341,8 +341,6 @@ export interface RouteData {
type: 'page';
}
-export type RouteCache = Record<string, GetStaticPathsResultKeyed>;
-
export type RuntimeMode = 'development' | 'production';
/**
@@ -385,7 +383,7 @@ export interface RSS {
}[];
}
-export type RSSFunction = (args: RSS) => void;
+export type RSSFunction = (args: RSS) => RSSResult;
export type FeedResult = { url: string; content?: string };
export type RSSResult = { xml: FeedResult; xsl?: FeedResult };
diff --git a/packages/astro/src/cli/index.ts b/packages/astro/src/cli/index.ts
index ae8b0ac56..3860d9bba 100644
--- a/packages/astro/src/cli/index.ts
+++ b/packages/astro/src/cli/index.ts
@@ -90,12 +90,8 @@ export async function cli(args: string[]) {
try {
config = await loadConfig({ cwd: projectRoot, flags });
} catch (err) {
- if (err instanceof z.ZodError) {
- console.error(formatConfigError(err));
- } else {
- console.error(colors.red((err as any).toString() || err));
- }
- process.exit(1);
+ throwAndExit(err);
+ return;
}
switch (cmd) {
@@ -143,6 +139,13 @@ export async function cli(args: string[]) {
/** Display error and exit */
function throwAndExit(err: any) {
- console.error(colors.red(err.toString() || err));
+ if (err instanceof z.ZodError) {
+ console.error(formatConfigError(err));
+ } else if (err.stack) {
+ const [mainMsg, ...stackMsg] = err.stack.split('\n');
+ console.error(colors.red(mainMsg) + '\n' + colors.dim(stackMsg.join('\n')));
+ } else {
+ console.error(colors.red(err.toString() || err));
+ }
process.exit(1);
}
diff --git a/packages/astro/src/core/build/index.ts b/packages/astro/src/core/build/index.ts
index a93b1f81b..024dc4d05 100644
--- a/packages/astro/src/core/build/index.ts
+++ b/packages/astro/src/core/build/index.ts
@@ -1,4 +1,4 @@
-import type { AstroConfig, ManifestData, RouteCache } from '../../@types/astro';
+import type { AstroConfig, ManifestData } from '../../@types/astro';
import type { LogOptions } from '../logger';
import fs from 'fs';
@@ -13,6 +13,7 @@ import { generateSitemap } from '../ssr/sitemap.js';
import { collectPagesData } from './page-data.js';
import { build as scanBasedBuild } from './scan-based-build.js';
import { staticBuild } from './static-build.js';
+import { RouteCache } from '../ssr/route-cache.js';
export interface BuildOptions {
mode?: string;
@@ -35,7 +36,7 @@ class AstroBuilder {
private logging: LogOptions;
private mode = 'production';
private origin: string;
- private routeCache: RouteCache = {};
+ private routeCache: RouteCache;
private manifest: ManifestData;
private viteServer?: ViteDevServer;
private viteConfig?: ViteConfigWithSSR;
@@ -49,6 +50,7 @@ class AstroBuilder {
this.config = config;
const port = config.devOptions.port; // no need to save this (don’t rely on port in builder)
this.logging = options.logging;
+ this.routeCache = new RouteCache(this.logging);
this.origin = config.buildOptions.site ? new URL(config.buildOptions.site).origin : `http://localhost:${port}`;
this.manifest = createRouteManifest({ config }, this.logging);
}
@@ -74,7 +76,7 @@ class AstroBuilder {
this.viteConfig = viteConfig;
const viteServer = await vite.createServer(viteConfig);
this.viteServer = viteServer;
- debug(logging, 'build', timerMessage('Vite started', timer.viteStart));
+ debug('build', timerMessage('Vite started', timer.viteStart));
timer.loadStart = performance.now();
const { assets, allPages } = await collectPagesData({
@@ -92,13 +94,13 @@ class AstroBuilder {
// TODO: add better type inference to data.preload[1]
const frontmatter = (data.preload[1] as any).frontmatter;
if (Boolean(frontmatter.draft) && !this.config.buildOptions.drafts) {
- debug(logging, 'build', timerMessage(`Skipping draft page ${page}`, timer.loadStart));
+ debug('build', timerMessage(`Skipping draft page ${page}`, timer.loadStart));
delete allPages[page];
}
}
});
- debug(logging, 'build', timerMessage('All pages loaded', timer.loadStart));
+ debug('build', timerMessage('All pages loaded', timer.loadStart));
// The names of each pages
const pageNames: string[] = [];
@@ -130,7 +132,7 @@ class AstroBuilder {
viteServer: this.viteServer,
});
}
- debug(logging, 'build', timerMessage('Vite build finished', timer.buildStart));
+ debug('build', timerMessage('Vite build finished', timer.buildStart));
// Write any additionally generated assets to disk.
timer.assetsStart = performance.now();
@@ -141,7 +143,7 @@ class AstroBuilder {
fs.writeFileSync(filePath, assets[k], 'utf8');
delete assets[k]; // free up memory
});
- debug(logging, 'build', timerMessage('Additional assets copied', timer.assetsStart));
+ debug('build', timerMessage('Additional assets copied', timer.assetsStart));
// Build your final sitemap.
timer.sitemapStart = performance.now();
@@ -151,7 +153,7 @@ class AstroBuilder {
await fs.promises.mkdir(new URL('./', sitemapPath), { recursive: true });
await fs.promises.writeFile(sitemapPath, sitemap, 'utf8');
}
- debug(logging, 'build', timerMessage('Sitemap built', timer.sitemapStart));
+ debug('build', timerMessage('Sitemap built', timer.sitemapStart));
// You're done! Time to clean up.
await viteServer.close();
@@ -162,8 +164,6 @@ class AstroBuilder {
/** Stats */
private async printStats({ logging, timeStart, pageCount }: { logging: LogOptions; timeStart: number; pageCount: number }) {
- /* eslint-disable no-console */
- debug(logging, ''); // empty line for debug
const buildTime = performance.now() - timeStart;
const total = buildTime < 750 ? `${Math.round(buildTime)}ms` : `${(buildTime / 1000).toFixed(2)}s`;
const perPage = `${Math.round(buildTime / pageCount)}ms`;
diff --git a/packages/astro/src/core/build/page-data.ts b/packages/astro/src/core/build/page-data.ts
index 43620ac4e..106e09a05 100644
--- a/packages/astro/src/core/build/page-data.ts
+++ b/packages/astro/src/core/build/page-data.ts
@@ -1,4 +1,4 @@
-import type { AstroConfig, ComponentInstance, GetStaticPathsResult, ManifestData, RouteCache, RouteData, RSSResult } from '../../@types/astro';
+import type { AstroConfig, ComponentInstance, ManifestData, RouteData, RSSResult } from '../../@types/astro';
import type { AllPagesData } from './types';
import type { LogOptions } from '../logger';
import type { ViteDevServer } from '../vite.js';
@@ -7,10 +7,8 @@ import { fileURLToPath } from 'url';
import * as colors from 'kleur/colors';
import { debug } from '../logger.js';
import { preload as ssrPreload } from '../ssr/index.js';
-import { validateGetStaticPathsModule, validateGetStaticPathsResult } from '../ssr/routing.js';
-import { generatePaginateFunction } from '../ssr/paginate.js';
import { generateRssFunction } from '../ssr/rss.js';
-import { assignStaticPaths } from '../ssr/route-cache.js';
+import { callGetStaticPaths, RouteCache, RouteCacheEntry } from '../ssr/route-cache.js';
export interface CollectPagesDataOptions {
astroConfig: AstroConfig;
@@ -57,11 +55,11 @@ export async function collectPagesData(opts: CollectPagesDataOptions): Promise<C
})
.then((routes) => {
const html = `${route.pathname}`.replace(/\/?$/, '/index.html');
- debug(logging, 'build', `├── ${colors.bold(colors.green('✔'))} ${route.component} → ${colors.yellow(html)}`);
+ debug('build', `├── ${colors.bold(colors.green('✔'))} ${route.component} → ${colors.yellow(html)}`);
return routes;
})
.catch((err) => {
- debug(logging, 'build', `├── ${colors.bold(colors.red('✘'))} ${route.component}`);
+ debug('build', `├── ${colors.bold(colors.red('✘'))} ${route.component}`);
throw err;
}),
};
@@ -69,50 +67,50 @@ export async function collectPagesData(opts: CollectPagesDataOptions): Promise<C
}
// dynamic route:
const result = await getStaticPathsForRoute(opts, route)
- .then((routes) => {
- const label = routes.paths.length === 1 ? 'page' : 'pages';
- debug(logging, 'build', `├── ${colors.bold(colors.green('✔'))} ${route.component} → ${colors.magenta(`[${routes.paths.length} ${label}]`)}`);
- return routes;
+ .then((_result) => {
+ const label = _result.staticPaths.length === 1 ? 'page' : 'pages';
+ debug('build', `├── ${colors.bold(colors.green('✔'))} ${route.component} → ${colors.magenta(`[${_result.staticPaths.length} ${label}]`)}`);
+ return _result;
})
.catch((err) => {
- debug(logging, 'build', `├── ${colors.bold(colors.red('✗'))} ${route.component}`);
+ debug('build', `├── ${colors.bold(colors.red('✗'))} ${route.component}`);
throw err;
});
- if (result.rss?.length) {
- for (let i = 0; i < result.rss.length; i++) {
- const rss = result.rss[i];
- if (rss.xml) {
- const { url, content } = rss.xml;
- if (content) {
- const rssFile = new URL(url.replace(/^\/?/, './'), astroConfig.dist);
- if (assets[fileURLToPath(rssFile)]) {
- throw new Error(`[getStaticPaths] RSS feed ${url} already exists.\nUse \`rss(data, {url: '...'})\` to choose a unique, custom URL. (${route.component})`);
- }
- assets[fileURLToPath(rssFile)] = content;
+ const rssFn = generateRssFunction(astroConfig.buildOptions.site, route);
+ for (const rssCallArg of result.rss) {
+ const rssResult = rssFn(rssCallArg);
+ if (rssResult.xml) {
+ const { url, content } = rssResult.xml;
+ if (content) {
+ const rssFile = new URL(url.replace(/^\/?/, './'), astroConfig.dist);
+ if (assets[fileURLToPath(rssFile)]) {
+ throw new Error(`[getStaticPaths] RSS feed ${url} already exists.\nUse \`rss(data, {url: '...'})\` to choose a unique, custom URL. (${route.component})`);
}
+ assets[fileURLToPath(rssFile)] = content;
}
- if (rss.xsl?.content) {
- const { url, content } = rss.xsl;
- const stylesheetFile = new URL(url.replace(/^\/?/, './'), astroConfig.dist);
- if (assets[fileURLToPath(stylesheetFile)]) {
- throw new Error(
- `[getStaticPaths] RSS feed stylesheet ${url} already exists.\nUse \`rss(data, {stylesheet: '...'})\` to choose a unique, custom URL. (${route.component})`
- );
- }
- assets[fileURLToPath(stylesheetFile)] = content;
+ }
+ if (rssResult.xsl?.content) {
+ const { url, content } = rssResult.xsl;
+ const stylesheetFile = new URL(url.replace(/^\/?/, './'), astroConfig.dist);
+ if (assets[fileURLToPath(stylesheetFile)]) {
+ throw new Error(
+ `[getStaticPaths] RSS feed stylesheet ${url} already exists.\nUse \`rss(data, {stylesheet: '...'})\` to choose a unique, custom URL. (${route.component})`
+ );
}
+ assets[fileURLToPath(stylesheetFile)] = content;
}
}
+ const finalPaths = result.staticPaths.map((staticPath) => staticPath.params && route.generate(staticPath.params)).filter(Boolean);
allPages[route.component] = {
route,
- paths: result.paths,
+ paths: finalPaths,
preload: await ssrPreload({
astroConfig,
filePath: new URL(`./${route.component}`, astroConfig.projectRoot),
logging,
mode: 'production',
origin,
- pathname: result.paths[0],
+ pathname: finalPaths[0],
route,
routeCache,
viteServer,
@@ -124,18 +122,12 @@ export async function collectPagesData(opts: CollectPagesDataOptions): Promise<C
return { assets, allPages };
}
-async function getStaticPathsForRoute(opts: CollectPagesDataOptions, route: RouteData): Promise<{ paths: string[]; rss?: RSSResult[] }> {
+async function getStaticPathsForRoute(opts: CollectPagesDataOptions, route: RouteData): Promise<RouteCacheEntry> {
const { astroConfig, logging, routeCache, viteServer } = opts;
if (!viteServer) throw new Error(`vite.createServer() not called!`);
const filePath = new URL(`./${route.component}`, astroConfig.projectRoot);
const mod = (await viteServer.ssrLoadModule(fileURLToPath(filePath))) as ComponentInstance;
- validateGetStaticPathsModule(mod);
- const rss = generateRssFunction(astroConfig.buildOptions.site, route);
- await assignStaticPaths(routeCache, route, mod, rss.generator);
- const staticPaths = routeCache[route.component];
- validateGetStaticPathsResult(staticPaths, logging);
- return {
- paths: staticPaths.map((staticPath) => staticPath.params && route.generate(staticPath.params)).filter(Boolean),
- rss: rss.rss,
- };
+ const result = await callGetStaticPaths(mod, route, false, logging);
+ routeCache.set(route, result);
+ return result;
}
diff --git a/packages/astro/src/core/build/scan-based-build.ts b/packages/astro/src/core/build/scan-based-build.ts
index 3a295c0db..c11795fd8 100644
--- a/packages/astro/src/core/build/scan-based-build.ts
+++ b/packages/astro/src/core/build/scan-based-build.ts
@@ -1,5 +1,5 @@
import type { ViteDevServer } from '../vite.js';
-import type { AstroConfig, RouteCache } from '../../@types/astro';
+import type { AstroConfig } from '../../@types/astro';
import type { AllPagesData } from './types';
import type { LogOptions } from '../logger';
import type { ViteConfigWithSSR } from '../create-vite.js';
@@ -9,6 +9,7 @@ import vite from '../vite.js';
import { createBuildInternals } from '../../core/build/internal.js';
import { rollupPluginAstroBuildHTML } from '../../vite-plugin-build-html/index.js';
import { rollupPluginAstroBuildCSS } from '../../vite-plugin-build-css/index.js';
+import { RouteCache } from '../ssr/route-cache.js';
export interface ScanBasedBuildOptions {
allPages: AllPagesData;
diff --git a/packages/astro/src/core/build/static-build.ts b/packages/astro/src/core/build/static-build.ts
index 0df6194dd..ad2b51203 100644
--- a/packages/astro/src/core/build/static-build.ts
+++ b/packages/astro/src/core/build/static-build.ts
@@ -1,6 +1,6 @@
import type { OutputChunk, OutputAsset, PreRenderedChunk, RollupOutput } from 'rollup';
import type { Plugin as VitePlugin, UserConfig } from '../vite';
-import type { AstroConfig, Renderer, RouteCache, SSRElement } from '../../@types/astro';
+import type { AstroConfig, Renderer, SSRElement } from '../../@types/astro';
import type { AllPagesData } from './types';
import type { LogOptions } from '../logger';
import type { ViteConfigWithSSR } from '../create-vite';
@@ -22,6 +22,7 @@ import { createResult } from '../ssr/result.js';
import { renderPage } from '../../runtime/server/index.js';
import { prepareOutDir } from './fs.js';
import { vitePluginHoistedScripts } from './vite-plugin-hoisted-scripts.js';
+import { RouteCache } from '../ssr/route-cache.js';
export interface StaticBuildOptions {
allPages: AllPagesData;
@@ -182,7 +183,7 @@ async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, inp
root: viteConfig.root,
envPrefix: 'PUBLIC_',
server: viteConfig.server,
- base: astroConfig.buildOptions.site ? fileURLToPath(new URL(astroConfig.buildOptions.site)) : '/',
+ base: astroConfig.buildOptions.site ? new URL(astroConfig.buildOptions.site).pathname : '/',
ssr: viteConfig.ssr,
} as ViteConfigWithSSR);
}
@@ -223,7 +224,7 @@ async function clientBuild(opts: StaticBuildOptions, internals: BuildInternals,
root: viteConfig.root,
envPrefix: 'PUBLIC_',
server: viteConfig.server,
- base: astroConfig.buildOptions.site ? fileURLToPath(new URL(astroConfig.buildOptions.site)) : '/',
+ base: astroConfig.buildOptions.site ? new URL(astroConfig.buildOptions.site).pathname : '/',
});
}
@@ -255,7 +256,7 @@ async function collectRenderers(opts: StaticBuildOptions): Promise<Renderer[]> {
}
async function generatePages(result: RollupOutput, opts: StaticBuildOptions, internals: BuildInternals, facadeIdToPageDataMap: Map<string, PageBuildData>) {
- debug(opts.logging, 'generate', 'End build step, now generating');
+ debug('build', 'Finish build. Begin generating.');
// Get renderers to be shared for each page generation.
const renderers = await collectRenderers(opts);
@@ -330,15 +331,10 @@ async function generatePath(pathname: string, opts: StaticBuildOptions, gopts: G
const [params, pageProps] = await getParamsAndProps({
route: pageData.route,
routeCache,
- logging,
pathname,
- mod,
- // Do not validate as validation already occurred for static routes
- // and validation is relatively expensive.
- validate: false,
});
- debug(logging, 'generate', `Generating: ${pathname}`);
+ debug('build', `Generating: ${pathname}`);
const rootpath = new URL(astroConfig.buildOptions.site || 'http://localhost/').pathname;
const links = new Set<SSRElement>(
diff --git a/packages/astro/src/core/logger.ts b/packages/astro/src/core/logger.ts
index ed6240bcc..c00e71347 100644
--- a/packages/astro/src/core/logger.ts
+++ b/packages/astro/src/core/logger.ts
@@ -4,6 +4,7 @@ import { bold, blue, dim, red, grey, underline, yellow } from 'kleur/colors';
import { performance } from 'perf_hooks';
import { Writable } from 'stream';
import stringWidth from 'string-width';
+import debugPackage from 'debug';
import { format as utilFormat } from 'util';
type ConsoleStream = Writable & {
@@ -61,7 +62,7 @@ interface LogWritable<T> extends Writable {
}
export type LoggerLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent'; // same as Pino
-export type LoggerEvent = 'debug' | 'info' | 'warn' | 'error';
+export type LoggerEvent = 'info' | 'warn' | 'error';
export interface LogOptions {
dest?: LogWritable<LogMessage>;
@@ -107,27 +108,35 @@ export function log(opts: LogOptions = {}, level: LoggerLevel, type: string | nu
dest.write(event);
}
-/** Emit a message only shown in debug mode */
-export function debug(opts: LogOptions, type: string | null, ...messages: Array<any>) {
- return log(opts, 'debug', type, ...messages);
+const debuggers: Record<string, debugPackage.Debugger['log']> = {};
+/**
+ * Emit a message only shown in debug mode.
+ * Astro (along with many of its dependencies) uses the `debug` package for debug logging.
+ * You can enable these logs with the `DEBUG=astro:*` environment variable.
+ * More info https://github.com/debug-js/debug#environment-variables
+ */
+export function debug(type: string, ...messages: Array<any>) {
+ const namespace = `astro:${type}`;
+ debuggers[namespace] = debuggers[namespace] || debugPackage(namespace);
+ return debuggers[namespace](messages);
}
-/** Emit a general info message (be careful using this too much!) */
+/** Emit a user-facing message. Useful for UI and other console messages. */
export function info(opts: LogOptions, type: string | null, ...messages: Array<any>) {
return log(opts, 'info', type, ...messages);
}
-/** Emit a warning a user should be aware of */
+/** Emit a warning message. Useful for high-priority messages that aren't necessarily errors. */
export function warn(opts: LogOptions, type: string | null, ...messages: Array<any>) {
return log(opts, 'warn', type, ...messages);
}
-/** Emit a fatal error message the user should address. */
+/** Emit a error message, Useful when Astro can't recover from some error. */
export function error(opts: LogOptions, type: string | null, ...messages: Array<any>) {
return log(opts, 'error', type, ...messages);
}
-type LogFn = typeof debug | typeof info | typeof warn | typeof error;
+type LogFn = typeof info | typeof warn | typeof error;
export function table(opts: LogOptions, columns: number[]) {
return function logTable(logFn: LogFn, ...input: Array<any>) {
@@ -163,7 +172,6 @@ ${frame}
// A default logger for when too lazy to pass LogOptions around.
export const logger = {
- debug: debug.bind(null, defaultLogOptions),
info: info.bind(null, defaultLogOptions),
warn: warn.bind(null, defaultLogOptions),
error: error.bind(null, defaultLogOptions),
diff --git a/packages/astro/src/core/ssr/index.ts b/packages/astro/src/core/ssr/index.ts
index e7299d20a..c4d214a72 100644
--- a/packages/astro/src/core/ssr/index.ts
+++ b/packages/astro/src/core/ssr/index.ts
@@ -1,20 +1,7 @@
import type { BuildResult } from 'esbuild';
import type vite from '../vite';
-import type {
- AstroConfig,
- ComponentInstance,
- GetStaticPathsResult,
- GetStaticPathsResultKeyed,
- Params,
- Props,
- Renderer,
- RouteCache,
- RouteData,
- RuntimeMode,
- SSRElement,
- SSRError,
-} from '../../@types/astro';
-import type { LogOptions } from '../logger';
+import type { AstroConfig, ComponentInstance, Params, Props, Renderer, RouteData, RuntimeMode, SSRElement, SSRError } from '../../@types/astro';
+import { LogOptions, warn } from '../logger.js';
import eol from 'eol';
import fs from 'fs';
@@ -24,10 +11,9 @@ import { renderPage } from '../../runtime/server/index.js';
import { codeFrame, resolveDependency } from '../util.js';
import { getStylesForURL } from './css.js';
import { injectTags } from './html.js';
-import { generatePaginateFunction } from './paginate.js';
-import { getParams, validateGetStaticPathsModule, validateGetStaticPathsResult } from './routing.js';
+import { getParams, validateGetStaticPathsResult } from './routing.js';
import { createResult } from './result.js';
-import { assignStaticPaths, ensureRouteCached, findPathItemByKey } from './route-cache.js';
+import { callGetStaticPaths, findPathItemByKey, RouteCache } from './route-cache.js';
const svelteStylesRE = /svelte\?svelte&type=style/;
@@ -139,21 +125,7 @@ export async function preload({ astroConfig, filePath, viteServer }: SSROptions)
return [renderers, mod];
}
-export async function getParamsAndProps({
- route,
- routeCache,
- logging,
- pathname,
- mod,
- validate = true,
-}: {
- route: RouteData | undefined;
- routeCache: RouteCache;
- pathname: string;
- mod: ComponentInstance;
- logging: LogOptions;
- validate?: boolean;
-}): Promise<[Params, Props]> {
+export async function getParamsAndProps({ route, routeCache, pathname }: { route: RouteData | undefined; routeCache: RouteCache; pathname: string }): Promise<[Params, Props]> {
// Handle dynamic routes
let params: Params = {};
let pageProps: Props;
@@ -164,19 +136,12 @@ export async function getParamsAndProps({
params = getParams(route.params)(paramsMatch);
}
}
- if (validate) {
- validateGetStaticPathsModule(mod);
+ const routeCacheEntry = routeCache.get(route);
+ if (!routeCacheEntry) {
+ throw new Error(`[${route.component}] Internal error: route cache was empty, but expected to be full.`);
}
- if (!routeCache[route.component]) {
- await assignStaticPaths(routeCache, route, mod);
- }
- if (validate) {
- // This validation is expensive so we only want to do it in dev.
- validateGetStaticPathsResult(routeCache[route.component], logging);
- }
- const staticPaths: GetStaticPathsResultKeyed = routeCache[route.component];
const paramsKey = JSON.stringify(params);
- const matchedStaticPath = findPathItemByKey(staticPaths, paramsKey, logging);
+ const matchedStaticPath = findPathItemByKey(routeCacheEntry.staticPaths, paramsKey);
if (!matchedStaticPath) {
throw new Error(`[getStaticPaths] route pattern matched, but no matching static path found. (${pathname})`);
}
@@ -203,11 +168,16 @@ export async function render(renderers: Renderer[], mod: ComponentInstance, ssrO
params = getParams(route.params)(paramsMatch);
}
}
- validateGetStaticPathsModule(mod);
- await ensureRouteCached(routeCache, route, mod);
- validateGetStaticPathsResult(routeCache[route.component], logging);
- const routePathParams: GetStaticPathsResult = routeCache[route.component];
- const matchedStaticPath = routePathParams.find(({ params: _params }) => JSON.stringify(_params) === JSON.stringify(params));
+ let routeCacheEntry = routeCache.get(route);
+ // TODO(fks): All of our getStaticPaths logic should live in a single place,
+ // to prevent duplicate runs during the build. This is not expected to run
+ // anymore and we should change this check to thrown an internal error.
+ if (!routeCacheEntry) {
+ warn(logging, 'routeCache', `Internal Warning: getStaticPaths() called twice during the build. (${route.component})`);
+ routeCacheEntry = await callGetStaticPaths(mod, route, true, logging);
+ routeCache.set(route, routeCacheEntry);
+ }
+ const matchedStaticPath = routeCacheEntry.staticPaths.find(({ params: _params }) => JSON.stringify(_params) === JSON.stringify(params));
if (!matchedStaticPath) {
throw new Error(`[getStaticPaths] route pattern matched, but no matching static path found. (${pathname})`);
}
diff --git a/packages/astro/src/core/ssr/route-cache.ts b/packages/astro/src/core/ssr/route-cache.ts
index 3ebfc4d7e..11988d36b 100644
--- a/packages/astro/src/core/ssr/route-cache.ts
+++ b/packages/astro/src/core/ssr/route-cache.ts
@@ -1,20 +1,22 @@
-import type { ComponentInstance, GetStaticPathsItem, GetStaticPathsResult, GetStaticPathsResultKeyed, RouteCache, RouteData } from '../../@types/astro';
-import type { LogOptions } from '../logger';
+import type { ComponentInstance, GetStaticPathsItem, GetStaticPathsResult, GetStaticPathsResultKeyed, RouteData, RSS } from '../../@types/astro';
+import { LogOptions, warn, debug } from '../logger.js';
-import { debug } from '../logger.js';
import { generatePaginateFunction } from '../ssr/paginate.js';
+import { validateGetStaticPathsModule, validateGetStaticPathsResult } from './routing.js';
type RSSFn = (...args: any[]) => any;
-export async function callGetStaticPaths(mod: ComponentInstance, route: RouteData, rssFn?: RSSFn): Promise<GetStaticPathsResultKeyed> {
+export async function callGetStaticPaths(mod: ComponentInstance, route: RouteData, isValidate: boolean, logging: LogOptions): Promise<RouteCacheEntry> {
+ validateGetStaticPathsModule(mod);
+ const resultInProgress = {
+ rss: [] as RSS[],
+ };
const staticPaths: GetStaticPathsResult = await (
await mod.getStaticPaths!({
paginate: generatePaginateFunction(route),
- rss:
- rssFn ||
- (() => {
- /* noop */
- }),
+ rss: (data) => {
+ resultInProgress.rss.push(data);
+ },
})
).flat();
@@ -24,31 +26,59 @@ export async function callGetStaticPaths(mod: ComponentInstance, route: RouteDat
const paramsKey = JSON.stringify(sp.params);
keyedStaticPaths.keyed.set(paramsKey, sp);
}
-
- return keyedStaticPaths;
+ if (isValidate) {
+ validateGetStaticPathsResult(keyedStaticPaths, logging);
+ }
+ return {
+ rss: resultInProgress.rss,
+ staticPaths: keyedStaticPaths,
+ };
}
-export async function assignStaticPaths(routeCache: RouteCache, route: RouteData, mod: ComponentInstance, rssFn?: RSSFn): Promise<void> {
- const staticPaths = await callGetStaticPaths(mod, route, rssFn);
- routeCache[route.component] = staticPaths;
+export interface RouteCacheEntry {
+ staticPaths: GetStaticPathsResultKeyed;
+ rss: RSS[];
}
-export async function ensureRouteCached(routeCache: RouteCache, route: RouteData, mod: ComponentInstance, rssFn?: RSSFn): Promise<GetStaticPathsResultKeyed> {
- if (!routeCache[route.component]) {
- const staticPaths = await callGetStaticPaths(mod, route, rssFn);
- routeCache[route.component] = staticPaths;
- return staticPaths;
- } else {
- return routeCache[route.component];
+/**
+ * Manange the route cache, responsible for caching data related to each route,
+ * including the result of calling getStaticPath() so that it can be reused across
+ * responses during dev and only ever called once during build.
+ */
+export class RouteCache {
+ private logging: LogOptions;
+ private cache: Record<string, RouteCacheEntry> = {};
+
+ constructor(logging: LogOptions) {
+ this.logging = logging;
+ }
+
+ /** Clear the cache. */
+ clearAll() {
+ this.cache = {};
+ }
+
+ set(route: RouteData, entry: RouteCacheEntry): void {
+ // NOTE: This shouldn't be called on an already-cached component.
+ // Warn here so that an unexpected double-call of getStaticPaths()
+ // isn't invisible and developer can track down the issue.
+ if (this.cache[route.component]) {
+ warn(this.logging, 'routeCache', `Internal Warning: route cache overwritten. (${route.component})`);
+ }
+ this.cache[route.component] = entry;
+ }
+
+ get(route: RouteData): RouteCacheEntry | undefined {
+ return this.cache[route.component];
}
}
-export function findPathItemByKey(staticPaths: GetStaticPathsResultKeyed, paramsKey: string, logging: LogOptions) {
+export function findPathItemByKey(staticPaths: GetStaticPathsResultKeyed, paramsKey: string) {
let matchedStaticPath = staticPaths.keyed.get(paramsKey);
if (matchedStaticPath) {
return matchedStaticPath;
}
- debug(logging, 'findPathItemByKey', `Unexpected cache miss looking for ${paramsKey}`);
+ debug('findPathItemByKey', `Unexpected cache miss looking for ${paramsKey}`);
matchedStaticPath = staticPaths.find(({ params: _params }) => JSON.stringify(_params) === paramsKey);
}
diff --git a/packages/astro/src/core/ssr/rss.ts b/packages/astro/src/core/ssr/rss.ts
index cba4b4e5d..18cce36a1 100644
--- a/packages/astro/src/core/ssr/rss.ts
+++ b/packages/astro/src/core/ssr/rss.ts
@@ -83,33 +83,29 @@ export function generateRSSStylesheet() {
}
/** Generated function to be run */
-export function generateRssFunction(site: string | undefined, route: RouteData): { generator: RSSFunction; rss?: RSSResult[] } {
- let results: RSSResult[] = [];
- return {
- generator: function rssUtility(args: RSS) {
- if (!site) {
- throw new Error(`[${route.component}] rss() tried to generate RSS but "buildOptions.site" missing in astro.config.mjs`);
- }
- let result: RSSResult = {} as any;
- const { dest, ...rssData } = args;
- const feedURL = dest || '/rss.xml';
- if (rssData.stylesheet === true) {
- rssData.stylesheet = feedURL.replace(/\.xml$/, '.xsl');
- result.xsl = {
- url: rssData.stylesheet,
- content: generateRSSStylesheet(),
- };
- } else if (typeof rssData.stylesheet === 'string') {
- result.xsl = {
- url: rssData.stylesheet,
- };
- }
- result.xml = {
- url: feedURL,
- content: generateRSS({ rssData, site, srcFile: route.component }),
+export function generateRssFunction(site: string | undefined, route: RouteData): RSSFunction {
+ return function rssUtility(args: RSS): RSSResult {
+ if (!site) {
+ throw new Error(`[${route.component}] rss() tried to generate RSS but "buildOptions.site" missing in astro.config.mjs`);
+ }
+ let result: RSSResult = {} as any;
+ const { dest, ...rssData } = args;
+ const feedURL = dest || '/rss.xml';
+ if (rssData.stylesheet === true) {
+ rssData.stylesheet = feedURL.replace(/\.xml$/, '.xsl');
+ result.xsl = {
+ url: rssData.stylesheet,
+ content: generateRSSStylesheet(),
};
- results.push(result);
- },
- rss: results,
+ } else if (typeof rssData.stylesheet === 'string') {
+ result.xsl = {
+ url: rssData.stylesheet,
+ };
+ }
+ result.xml = {
+ url: feedURL,
+ content: generateRSS({ rssData, site, srcFile: route.component }),
+ };
+ return result;
};
}
diff --git a/packages/astro/src/vite-plugin-astro-server/index.ts b/packages/astro/src/vite-plugin-astro-server/index.ts
index 701457f93..de57e1593 100644
--- a/packages/astro/src/vite-plugin-astro-server/index.ts
+++ b/packages/astro/src/vite-plugin-astro-server/index.ts
@@ -1,6 +1,6 @@
import type vite from '../core/vite';
import type http from 'http';
-import type { AstroConfig, ManifestData, RouteCache, RouteData } from '../@types/astro';
+import type { AstroConfig, ManifestData, RouteData } from '../@types/astro';
import { info, LogOptions } from '../core/logger.js';
import { fileURLToPath } from 'url';
import { createRouteManifest, matchRoute } from '../core/ssr/routing.js';
@@ -12,6 +12,7 @@ import * as msg from '../core/messages.js';
import notFoundTemplate, { subpathNotUsedTemplate } from '../template/4xx.js';
import serverErrorTemplate from '../template/5xx.js';
+import { RouteCache } from '../core/ssr/route-cache.js';
interface AstroPluginOptions {
config: AstroConfig;
@@ -126,22 +127,19 @@ export default function createPlugin({ config, logging }: AstroPluginOptions): v
name: 'astro:server',
configureServer(viteServer) {
const pagesDirectory = fileURLToPath(config.pages);
- let routeCache: RouteCache = {};
+ let routeCache = new RouteCache(logging);
let manifest: ManifestData = createRouteManifest({ config: config }, logging);
- /** rebuild the route cache + manifest if the changed file impacts routing. */
- function rebuildManifestIfNeeded(file: string) {
- if (file.startsWith(pagesDirectory)) {
- routeCache = {};
+ /** rebuild the route cache + manifest, as needed. */
+ function rebuildManifest(needsManifestRebuild: boolean, file: string) {
+ routeCache.clearAll();
+ if (needsManifestRebuild) {
manifest = createRouteManifest({ config: config }, logging);
}
}
// Rebuild route manifest on file change, if needed.
- viteServer.watcher.on('add', rebuildManifestIfNeeded);
- viteServer.watcher.on('unlink', rebuildManifestIfNeeded);
- // No need to rebuild routes on content-only changes.
- // However, we DO want to clear the cache in case
- // the change caused a getStaticPaths() return to change.
- viteServer.watcher.on('change', () => (routeCache = {}));
+ viteServer.watcher.on('add', rebuildManifest.bind(null, true));
+ viteServer.watcher.on('unlink', rebuildManifest.bind(null, true));
+ viteServer.watcher.on('change', rebuildManifest.bind(null, false));
return () => {
removeViteHttpMiddleware(viteServer.middlewares);
viteServer.middlewares.use(async (req, res) => {
diff --git a/packages/astro/src/vite-plugin-build-html/index.ts b/packages/astro/src/vite-plugin-build-html/index.ts
index 9a0bc6957..542340581 100644
--- a/packages/astro/src/vite-plugin-build-html/index.ts
+++ b/packages/astro/src/vite-plugin-build-html/index.ts
@@ -1,4 +1,4 @@
-import type { AstroConfig, RouteCache } from '../@types/astro';
+import type { AstroConfig } from '../@types/astro';
import type { LogOptions } from '../core/logger.js';
import type { ViteDevServer, Plugin as VitePlugin } from '../core/vite';
import type { OutputChunk, PreRenderedChunk } from 'rollup';
@@ -15,6 +15,7 @@ import { isBuildableImage, isBuildableLink, isHoistedScript, isInSrcDirectory, h
import { render as ssrRender } from '../core/ssr/index.js';
import { getAstroStyleId, getAstroPageStyleId } from '../vite-plugin-build-css/index.js';
import { prependDotSlash, removeEndingForwardSlash } from '../core/path.js';
+import { RouteCache } from '../core/ssr/route-cache.js';
// This package isn't real ESM, so have to coerce it
const matchSrcset: typeof srcsetParse = (srcsetParse as any).default;
diff --git a/yarn.lock b/yarn.lock
index f46009e71..e08bce4e4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1752,7 +1752,7 @@
dependencies:
"@types/node" "*"
-"@types/debug@^4.0.0":
+"@types/debug@^4.0.0", "@types/debug@^4.1.7":
version "4.1.7"
resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.7.tgz#7cc0ea761509124709b8b2d1090d8f6c17aadb82"
integrity sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==