0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-01-06 22:10:10 -05:00

Fix build order (#1609)

* Bugfix: restore build to get all paths earlier, when build. Same as main.

* Also re-add timings
This commit is contained in:
Drew Powers 2021-10-20 11:05:43 -06:00 committed by Drew Powers
parent e1b52506f7
commit bbf93de41e
2 changed files with 72 additions and 52 deletions

View file

@ -10,7 +10,7 @@ import vite, { ViteDevServer } from '../vite.js';
import { fileURLToPath } from 'url';
import { createVite } from '../create-vite.js';
import { pad } from '../dev/util.js';
import { defaultLogOptions, levels, warn } from '../logger.js';
import { debug, defaultLogOptions, levels, timerMessage, warn } from '../logger.js';
import { ssr } from '../ssr/index.js';
import { generatePaginateFunction } from '../ssr/paginate.js';
import { createRouteManifest, validateGetStaticPathsModule, validateGetStaticPathsResult } from '../ssr/routing.js';
@ -36,6 +36,7 @@ class AstroBuilder {
private origin: string;
private routeCache: RouteCache = {};
private manifest: ManifestData;
private viteServer?: ViteDevServer;
constructor(config: AstroConfig, options: BuildOptions) {
if (!config.buildOptions.site && config.buildOptions.sitemap !== false) {
@ -52,9 +53,10 @@ class AstroBuilder {
/** Build all pages */
async build() {
const start = performance.now();
const timer: Record<string, number> = {}; // keep track of performance timers
// 1. initialize fresh Vite instance
timer.viteStart = performance.now();
const { logging, origin } = this;
const viteConfig = await createVite(
{
@ -68,50 +70,64 @@ class AstroBuilder {
{ astroConfig: this.config, logging }
);
const viteServer = await vite.createServer(viteConfig);
this.viteServer = viteServer;
debug(logging, 'build', timerMessage('Vite started', timer.viteStart));
// 2. get all routes
const input: InputHTMLOptions[] = [];
timer.renderStart = performance.now();
const assets: Record<string, string> = {}; // additional assets to be written
for (const route of this.manifest.routes) {
const { pathname } = route;
const filePath = new URL(`./${route.component}`, this.config.projectRoot);
// static pages (note: should these be )
if (pathname) {
input.push(
await ssr({ astroConfig: this.config, filePath, logging, mode: 'production', origin, route, routeCache: this.routeCache, pathname, viteServer }).then((html) => ({
html,
name: pathname.replace(/\/?$/, '/index.html').replace(/^\//, ''),
}))
);
}
// dynamic pages
else {
const staticPaths = await this.getStaticPathsForRoute(route, viteServer);
// handle RSS (TODO: improve this?)
if (staticPaths.rss && staticPaths.rss.xml) {
const rssFile = new URL(staticPaths.rss.url.replace(/^\/?/, './'), this.config.dist);
const allPages: Record<string, RouteData & { paths: string[] }> = {};
// 2a. determine all possible routes first before rendering
await Promise.all(
this.manifest.routes.map(async (route) => {
// static route
if (route.pathname) {
allPages[route.component] = { ...route, paths: [route.pathname] };
return;
}
// dynamic route
const result = await this.getStaticPathsForRoute(route);
// handle RSS while generating routes
if (result.rss?.xml) {
const rssFile = new URL(result.rss.url.replace(/^\/?/, './'), this.config.dist);
if (assets[fileURLToPath(rssFile)]) {
throw new Error(
`[getStaticPaths] RSS feed ${staticPaths.rss.url} already exists.\nUse \`rss(data, {url: '...'})\` to choose a unique, custom URL. (${route.component})`
);
throw new Error(`[getStaticPaths] RSS feed ${result.rss.url} already exists.\nUse \`rss(data, {url: '...'})\` to choose a unique, custom URL. (${route.component})`);
}
assets[fileURLToPath(rssFile)] = staticPaths.rss.xml;
assets[fileURLToPath(rssFile)] = result.rss.xml;
}
// TODO: throw error if conflict
for (const staticPath of staticPaths.paths) {
input.push(
await ssr({ astroConfig: this.config, filePath, logging, mode: 'production', origin, route, routeCache: this.routeCache, pathname: staticPath, viteServer }).then(
(html) => ({
html,
name: staticPath.replace(/\/?$/, '/index.html').replace(/^\//, ''),
})
)
);
}
}
}
allPages[route.component] = { ...route, paths: result.paths };
})
);
// 2b. after all paths have been determined, render all pages
const input: InputHTMLOptions[] = [];
await Promise.all(
Object.entries(allPages).map(([component, route]) =>
Promise.all(
route.paths.map(async (pathname) => {
input.push({
html: await ssr({
astroConfig: this.config,
filePath: new URL(`./${component}`, this.config.projectRoot),
logging,
mode: 'production',
origin,
pathname,
route,
routeCache: this.routeCache,
viteServer,
}),
name: pathname.replace(/\/?$/, '/index.html').replace(/^\//, ''),
});
})
)
)
);
debug(logging, 'build', timerMessage('All pages rendered', timer.renderStart));
// 3. build with Vite
timer.buildStart = performance.now();
await vite.build({
logLevel: 'error',
mode: 'production',
@ -133,8 +149,10 @@ class AstroBuilder {
root: viteConfig.root,
server: viteConfig.server,
});
debug(logging, 'build', timerMessage('Vite build finished', timer.buildStart));
// 4. write assets to disk
timer.assetsStart = performance.now();
Object.keys(assets).map((k) => {
if (!assets[k]) return;
const filePath = new URL(`file://${k}`);
@ -142,36 +160,33 @@ class AstroBuilder {
fs.writeFileSync(filePath, assets[k], 'utf8');
delete assets[k]; // free up memory
});
debug(logging, 'build', timerMessage('Additional assets copied', timer.assetsStart));
// 5. build sitemap
let sitemapTime = 0;
timer.sitemapStart = performance.now();
if (this.config.buildOptions.sitemap && this.config.buildOptions.site) {
const sitemapStart = performance.now();
const sitemap = generateSitemap(input.map(({ name }) => new URL(`/${name}`, this.config.buildOptions.site).href));
const sitemapPath = new URL('./sitemap.xml', this.config.dist);
await fs.promises.mkdir(new URL('./', sitemapPath), { recursive: true });
await fs.promises.writeFile(sitemapPath, sitemap, 'utf8');
sitemapTime = performance.now() - sitemapStart;
}
debug(logging, 'build', timerMessage('Sitemap built', timer.sitemapStart));
// 6. clean up
await viteServer.close();
// 7. log output
if (logging.level && levels[logging.level] <= levels['info']) {
await this.printStats({
cwd: this.config.dist,
pageCount: input.length,
pageTime: Math.round(performance.now() - start),
sitemapTime,
});
await this.printStats({ cwd: this.config.dist, pageCount: input.length });
}
}
/** Extract all static paths from a dynamic route */
private async getStaticPathsForRoute(route: RouteData, viteServer: ViteDevServer): Promise<{ paths: string[]; rss?: RSSResult }> {
private async getStaticPathsForRoute(route: RouteData): Promise<{ paths: string[]; rss?: RSSResult }> {
if (!this.viteServer) throw new Error(`vite.createServer() not called!`);
const filePath = new URL(`./${route.component}`, this.config.projectRoot);
const mod = (await viteServer.ssrLoadModule(fileURLToPath(filePath))) as ComponentInstance;
const mod = (await this.viteServer.ssrLoadModule(fileURLToPath(filePath))) as ComponentInstance;
validateGetStaticPathsModule(mod);
const rss = generateRssFunction(this.config.buildOptions.site, route);
const staticPaths: GetStaticPathsResult = (await mod.getStaticPaths!({ paginate: generatePaginateFunction(route), rss: rss.generator })).flat();
@ -184,12 +199,11 @@ class AstroBuilder {
}
/** Stats */
private async printStats({ cwd, pageTime, pageCount, sitemapTime }: { cwd: URL; pageTime: number; pageCount: number; sitemapTime: number }) {
const end = Math.round(performance.now() - pageTime);
private async printStats({ cwd, pageCount }: { cwd: URL; pageCount: number }) {
const [js, html] = await Promise.all([profileJS({ cwd, entryHTML: new URL('./index.html', cwd) }), profileHTML({ cwd })]);
/* eslint-disable no-console */
console.log(`${pad(bold(cyan('Done')), 70)}${dim(` ${pad(`${end}ms`, 8, 'left')}`)}
console.log(`${bold(cyan('Done'))}
Pages (${pageCount} total)
${green(`✔ All pages under ${kb(html.maxSize)}`)}
JS
@ -201,6 +215,5 @@ CSS
Images
${green(`✔ All images under 50 kB`)}
`);
if (sitemapTime > 0) console.log(`Sitemap\n ${green(`✔ Built in ${sitemapTime}`)}`);
}
}

View file

@ -185,3 +185,10 @@ if (process.argv.includes('--verbose')) {
} else {
defaultLogLevel = 'info';
}
/** Print out a timer message for debug() */
export function timerMessage(message: string, startTime: number = performance.now()) {
let timeDiff = performance.now() - startTime;
let timeDisplay = timeDiff < 750 ? `${Math.round(timeDiff)}ms` : `${(timeDiff / 1000).toFixed(1)}s`;
return `${message}: ${dim(timeDisplay)}]`;
}