Add bundle size information to the build output (#169)
This commit is contained in:
parent
75b05b393a
commit
0054f78e42
6 changed files with 163 additions and 14 deletions
|
@ -54,6 +54,7 @@
|
|||
"find-up": "^5.0.0",
|
||||
"github-slugger": "^1.3.0",
|
||||
"gray-matter": "^4.0.2",
|
||||
"gzip-size": "^6.0.0",
|
||||
"hast-to-hyperscript": "~9.0.0",
|
||||
"kleur": "^4.1.4",
|
||||
"locate-character": "^2.0.5",
|
||||
|
@ -77,6 +78,7 @@
|
|||
"rollup-plugin-terser": "^7.0.2",
|
||||
"sass": "^1.32.8",
|
||||
"snowpack": "^3.3.7",
|
||||
"string-width": "^5.0.0",
|
||||
"source-map-support": "^0.5.19",
|
||||
"svelte": "^3.35.0",
|
||||
"unified": "^9.2.1",
|
||||
|
|
|
@ -4,7 +4,7 @@ import type { LogOptions } from './logger';
|
|||
import type { AstroRuntime, LoadResult } from './runtime';
|
||||
|
||||
import { existsSync, promises as fsPromises } from 'fs';
|
||||
import { bold, green, yellow, underline } from 'kleur/colors';
|
||||
import { bold, green, yellow } from 'kleur/colors';
|
||||
import path from 'path';
|
||||
import cheerio from 'cheerio';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
@ -16,6 +16,7 @@ import { generateRSS } from './build/rss.js';
|
|||
import { generateSitemap } from './build/sitemap.js';
|
||||
import { collectStatics } from './build/static.js';
|
||||
import { canonicalURL } from './build/util.js';
|
||||
import { createURLStats, mapBundleStatsToURLStats, logURLStats } from './build/stats.js';
|
||||
|
||||
const { mkdir, readFile, writeFile } = fsPromises;
|
||||
|
||||
|
@ -205,8 +206,11 @@ export async function build(astroConfig: AstroConfig): Promise<0 | 1> {
|
|||
const statics = new Set<string>();
|
||||
const collectImportsOptions = { astroConfig, logging, resolvePackageUrl, mode };
|
||||
|
||||
const pages = await allPages(pageRoot);
|
||||
let builtURLs: string[] = [];
|
||||
let urlStats = createURLStats();
|
||||
let importsToUrl = new Map<string, Set<string>>();
|
||||
|
||||
const pages = await allPages(pageRoot);
|
||||
|
||||
try {
|
||||
info(logging, 'build', yellow('! building pages...'));
|
||||
|
@ -218,8 +222,10 @@ export async function build(astroConfig: AstroConfig): Promise<0 | 1> {
|
|||
|
||||
const pageType = getPageType(filepath);
|
||||
const pageOptions: PageBuildOptions = { astroRoot, dist, filepath, runtime, site: astroConfig.buildOptions.site, sitemap: astroConfig.buildOptions.sitemap, statics };
|
||||
let urls: string[];
|
||||
if (pageType === 'collection') {
|
||||
const { canonicalURLs, rss } = await buildCollectionPage(pageOptions);
|
||||
urls = canonicalURLs;
|
||||
builtURLs.push(...canonicalURLs);
|
||||
if (rss) {
|
||||
const basename = path
|
||||
|
@ -230,10 +236,27 @@ export async function build(astroConfig: AstroConfig): Promise<0 | 1> {
|
|||
}
|
||||
} else {
|
||||
const { canonicalURLs } = await buildStaticPage(pageOptions);
|
||||
urls = canonicalURLs;
|
||||
builtURLs.push(...canonicalURLs);
|
||||
}
|
||||
|
||||
mergeSet(imports, await collectDynamicImports(filepath, collectImportsOptions));
|
||||
const dynamicImports = await collectDynamicImports(filepath, collectImportsOptions);
|
||||
mergeSet(imports, dynamicImports);
|
||||
|
||||
// Keep track of urls and dynamic imports for stats.
|
||||
for(const url of urls) {
|
||||
urlStats.set(url, {
|
||||
dynamicImports,
|
||||
stats: []
|
||||
});
|
||||
}
|
||||
|
||||
for(let imp of dynamicImports) {
|
||||
if(!importsToUrl.has(imp)) {
|
||||
importsToUrl.set(imp, new Set<string>());
|
||||
}
|
||||
mergeSet(importsToUrl.get(imp)!, new Set(urls));
|
||||
}
|
||||
})
|
||||
);
|
||||
info(logging, 'build', green('✔'), 'pages built.');
|
||||
|
@ -253,7 +276,8 @@ export async function build(astroConfig: AstroConfig): Promise<0 | 1> {
|
|||
if (imports.size > 0) {
|
||||
try {
|
||||
info(logging, 'build', yellow('! bundling client-side code.'));
|
||||
await bundle(imports, { dist, runtime, astroConfig });
|
||||
const bundleStats = await bundle(imports, { dist, runtime, astroConfig });
|
||||
mapBundleStatsToURLStats(urlStats, importsToUrl, bundleStats);
|
||||
info(logging, 'build', green('✔'), 'bundling complete.');
|
||||
} catch (err) {
|
||||
error(logging, 'build', err);
|
||||
|
@ -297,13 +321,8 @@ export async function build(astroConfig: AstroConfig): Promise<0 | 1> {
|
|||
info(logging, 'tip', `Set "buildOptions.site" in astro.config.mjs to generate a sitemap.xml`);
|
||||
}
|
||||
|
||||
builtURLs.sort((a, b) => a.localeCompare(b, 'en', { numeric: true }));
|
||||
info(logging, 'build', underline('Pages'));
|
||||
const lastIndex = builtURLs.length - 1;
|
||||
builtURLs.forEach((url, index) => {
|
||||
const sep = index === 0 ? '┌' : index === lastIndex ? '└' : '├';
|
||||
info(logging, null, ' ' + sep, url === '/' ? url : url + '/');
|
||||
});
|
||||
// Log in a table-like view.
|
||||
logURLStats(logging, urlStats, builtURLs);
|
||||
|
||||
await runtime.shutdown();
|
||||
info(logging, 'build', bold(green('▶ Build Complete!')));
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type { AstroConfig, RuntimeMode, ValidExtensionPlugins } from '../@types/astro';
|
||||
import type { ImportDeclaration } from '@babel/types';
|
||||
import type { InputOptions, OutputOptions } from 'rollup';
|
||||
import type { InputOptions, OutputOptions, OutputChunk } from 'rollup';
|
||||
import type { AstroRuntime } from '../runtime';
|
||||
import type { LogOptions } from '../logger';
|
||||
|
||||
|
@ -16,6 +16,7 @@ import babelParser from '@babel/parser';
|
|||
import path from 'path';
|
||||
import { rollup } from 'rollup';
|
||||
import { terser } from 'rollup-plugin-terser';
|
||||
import { createBundleStats, addBundleStats } from './stats.js';
|
||||
|
||||
const { transformSync } = esbuild;
|
||||
const { readFile } = fsPromises;
|
||||
|
@ -309,5 +310,12 @@ export async function bundle(imports: Set<string>, { runtime, dist }: BundleOpti
|
|||
],
|
||||
};
|
||||
|
||||
await build.write(outputOptions);
|
||||
const stats = createBundleStats();
|
||||
const {output} = await build.write(outputOptions);
|
||||
await Promise.all(output.map(async chunk => {
|
||||
const code = (chunk as OutputChunk).code || '';
|
||||
await addBundleStats(stats, code, chunk.fileName);
|
||||
}));
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
|
61
packages/astro/src/build/stats.ts
Normal file
61
packages/astro/src/build/stats.ts
Normal file
|
@ -0,0 +1,61 @@
|
|||
import type { LogOptions } from '../logger';
|
||||
|
||||
import { info, table } from '../logger.js';
|
||||
import { underline } from 'kleur/colors';
|
||||
import gzipSize from 'gzip-size';
|
||||
|
||||
interface BundleStats {
|
||||
size: number;
|
||||
gzipSize: number;
|
||||
}
|
||||
|
||||
interface URLStats {
|
||||
dynamicImports: Set<string>;
|
||||
stats: BundleStats[];
|
||||
}
|
||||
|
||||
export type BundleStatsMap = Map<string, BundleStats>;
|
||||
export type URLStatsMap = Map<string, URLStats>;
|
||||
|
||||
export function createURLStats(): URLStatsMap {
|
||||
return new Map<string, URLStats>();
|
||||
}
|
||||
|
||||
export function createBundleStats(): BundleStatsMap {
|
||||
return new Map<string, BundleStats>();
|
||||
}
|
||||
|
||||
export async function addBundleStats(bundleStatsMap: BundleStatsMap, code: string, filename: string) {
|
||||
const gzsize = await gzipSize(code);
|
||||
|
||||
bundleStatsMap.set(filename, {
|
||||
size: Buffer.byteLength(code),
|
||||
gzipSize: gzsize
|
||||
});
|
||||
}
|
||||
|
||||
export function mapBundleStatsToURLStats(urlStats: URLStatsMap, importsToUrl: Map<string, Set<string>>, bundleStats: BundleStatsMap) {
|
||||
for(let [imp, stats] of bundleStats) {
|
||||
for(let url of importsToUrl.get('/' + imp) || []) {
|
||||
urlStats.get(url)?.stats.push(stats);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function logURLStats(logging: LogOptions, urlStats: URLStatsMap, builtURLs: string[]) {
|
||||
builtURLs.sort((a, b) => a.localeCompare(b, 'en', { numeric: true }));
|
||||
info(logging, null, '');
|
||||
const log = table(logging, [60, 20]);
|
||||
log(info, ' ' + underline('Pages'), underline('GZip Size'));
|
||||
|
||||
const lastIndex = builtURLs.length - 1;
|
||||
builtURLs.forEach((url, index) => {
|
||||
const sep = index === 0 ? '┌' : index === lastIndex ? '└' : '├';
|
||||
const urlPart = (' ' + sep + ' ') + (url === '/' ? url : url + '/');
|
||||
|
||||
const bytes = urlStats.get(url)?.stats.map(s => s.gzipSize).reduce((a, b) => a + b, 0) || 0;
|
||||
const kb = (bytes * 0.001).toFixed(2);
|
||||
const sizePart = kb + ' kB';
|
||||
log(info, urlPart, sizePart);
|
||||
});
|
||||
}
|
|
@ -3,6 +3,7 @@ import type { CompileError } from 'astro-parser';
|
|||
import { bold, blue, red, grey, underline } from 'kleur/colors';
|
||||
import { Writable } from 'stream';
|
||||
import { format as utilFormat } from 'util';
|
||||
import stringWidth from 'string-width';
|
||||
|
||||
type ConsoleStream = Writable & {
|
||||
fd: 1 | 2;
|
||||
|
@ -102,6 +103,15 @@ export function error(opts: LogOptions, type: string | null, ...messages: Array<
|
|||
return log(opts, 'error', type, ...messages);
|
||||
}
|
||||
|
||||
type LogFn = typeof debug | typeof info | typeof warn | typeof error;
|
||||
|
||||
export function table(opts: LogOptions, columns: number[]) {
|
||||
return function logTable(logFn: LogFn, ...input: Array<any>) {
|
||||
const messages = columns.map((len, i) => padStr(input[i].toString(), len));
|
||||
logFn(opts, null, ...messages);
|
||||
};
|
||||
}
|
||||
|
||||
/** Pretty format error for display */
|
||||
export function parseError(opts: LogOptions, err: CompileError) {
|
||||
let frame = err.frame
|
||||
|
@ -142,3 +152,14 @@ export function trapWarn(cb: (...args: any[]) => void = () => {}) {
|
|||
};
|
||||
return () => (console.warn = warn);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function padStr(str: string, len: number) {
|
||||
const strLen = stringWidth(str);
|
||||
if(strLen > len) {
|
||||
return str.substring(0, len - 3) + '...';
|
||||
}
|
||||
const spaces = Array.from({ length: len - strLen }, () => ' ').join('');
|
||||
return str + spaces;
|
||||
}
|
40
yarn.lock
40
yarn.lock
|
@ -2019,6 +2019,11 @@ ansi-regex@^5.0.0:
|
|||
resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz"
|
||||
integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==
|
||||
|
||||
ansi-regex@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.0.tgz#ecc7f5933cbe5ac7b33e209a5ff409ab1669c6b2"
|
||||
integrity sha512-tAaOSrWCHF+1Ear1Z4wnJCXA9GGox4K6Ic85a5qalES2aeEwQGr7UC93mwef49536PkCYjzkp0zIxfFvexJ6zQ==
|
||||
|
||||
ansi-styles@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz"
|
||||
|
@ -3972,7 +3977,7 @@ duplexer3@^0.1.4:
|
|||
resolved "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz"
|
||||
integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=
|
||||
|
||||
duplexer@^0.1.1:
|
||||
duplexer@^0.1.1, duplexer@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz"
|
||||
integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==
|
||||
|
@ -4059,6 +4064,11 @@ emoji-regex@^8.0.0:
|
|||
resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz"
|
||||
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
|
||||
|
||||
emoji-regex@^9.2.2:
|
||||
version "9.2.2"
|
||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
|
||||
integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
|
||||
|
||||
emojis-list@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz"
|
||||
|
@ -5263,6 +5273,13 @@ gray-matter@^4.0.2:
|
|||
section-matter "^1.0.0"
|
||||
strip-bom-string "^1.0.0"
|
||||
|
||||
gzip-size@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462"
|
||||
integrity sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==
|
||||
dependencies:
|
||||
duplexer "^0.1.2"
|
||||
|
||||
hamljs@^0.6.2:
|
||||
version "0.6.2"
|
||||
resolved "https://registry.npmjs.org/hamljs/-/hamljs-0.6.2.tgz"
|
||||
|
@ -6054,6 +6071,11 @@ is-fullwidth-code-point@^3.0.0:
|
|||
resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz"
|
||||
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
|
||||
|
||||
is-fullwidth-code-point@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz#fae3167c729e7463f8461ce512b080a49268aa88"
|
||||
integrity sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==
|
||||
|
||||
is-glob@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz"
|
||||
|
@ -10453,6 +10475,15 @@ string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2
|
|||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
string-width@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.0.0.tgz#19191f152f937b96f4ec54ba0986a5656660c5a2"
|
||||
integrity sha512-zwXcRmLUdiWhMPrHz6EXITuyTgcEnUqDzspTkCLhQovxywWz6NP9VHgqfVg20V/1mUg0B95AKbXxNT+ALRmqCw==
|
||||
dependencies:
|
||||
emoji-regex "^9.2.2"
|
||||
is-fullwidth-code-point "^4.0.0"
|
||||
strip-ansi "^7.0.0"
|
||||
|
||||
string.prototype.trimend@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz"
|
||||
|
@ -10521,6 +10552,13 @@ strip-ansi@^6.0.0:
|
|||
dependencies:
|
||||
ansi-regex "^5.0.0"
|
||||
|
||||
strip-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.0.tgz#1dc49b980c3a4100366617adac59327eefdefcb0"
|
||||
integrity sha512-UhDTSnGF1dc0DRbUqr1aXwNoY3RgVkSWG8BrpnuFIxhP57IqbS7IRta2Gfiavds4yCxc5+fEAVVOgBZWnYkvzg==
|
||||
dependencies:
|
||||
ansi-regex "^6.0.0"
|
||||
|
||||
strip-ansi@~0.1.0:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz"
|
||||
|
|
Loading…
Reference in a new issue