Skip to content

Commit

Permalink
refactor the route cache and other build internals
Browse files Browse the repository at this point in the history
  • Loading branch information
FredKSchott committed Jan 31, 2022
1 parent f46afa9 commit 9575ce6
Show file tree
Hide file tree
Showing 11 changed files with 158 additions and 160 deletions.
4 changes: 1 addition & 3 deletions packages/astro/src/@types/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,8 +341,6 @@ export interface RouteData {
type: 'page';
}

export type RouteCache = Record<string, GetStaticPathsResultKeyed>;

export type RuntimeMode = 'development' | 'production';

/**
Expand Down Expand Up @@ -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 };
Expand Down
17 changes: 10 additions & 7 deletions packages/astro/src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
}
6 changes: 4 additions & 2 deletions packages/astro/src/core/build/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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);
}
Expand Down
72 changes: 32 additions & 40 deletions packages/astro/src/core/build/page-data.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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;
Expand Down Expand Up @@ -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(logging, '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}`);
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,
Expand All @@ -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;
}
3 changes: 2 additions & 1 deletion packages/astro/src/core/build/scan-based-build.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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;
Expand Down
11 changes: 4 additions & 7 deletions packages/astro/src/core/build/static-build.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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 : '/',
});
}

Expand Down Expand Up @@ -332,10 +333,6 @@ async function generatePath(pathname: string, opts: StaticBuildOptions, gopts: G
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}`);
Expand Down
58 changes: 19 additions & 39 deletions packages/astro/src/core/ssr/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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/;

Expand Down Expand Up @@ -142,17 +128,13 @@ export async function preload({ astroConfig, filePath, viteServer }: SSROptions)
export async function getParamsAndProps({
route,
routeCache,
logging,
pathname,
mod,
validate = true,
logging,
}: {
route: RouteData | undefined;
routeCache: RouteCache;
pathname: string;
mod: ComponentInstance;
logging: LogOptions;
validate?: boolean;
}): Promise<[Params, Props]> {
// Handle dynamic routes
let params: Params = {};
Expand All @@ -164,19 +146,12 @@ export async function getParamsAndProps({
params = getParams(route.params)(paramsMatch);
}
}
if (validate) {
validateGetStaticPathsModule(mod);
}
if (!routeCache[route.component]) {
await assignStaticPaths(routeCache, route, 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 (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, logging);
if (!matchedStaticPath) {
throw new Error(`[getStaticPaths] route pattern matched, but no matching static path found. (${pathname})`);
}
Expand All @@ -203,11 +178,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})`);
}
Expand Down
Loading

0 comments on commit 9575ce6

Please sign in to comment.