summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Matthew Phillips <matthew@matthewphillips.info> 2021-05-04 14:06:38 -0400
committerGravatar GitHub <noreply@github.com> 2021-05-04 14:06:38 -0400
commit0054f78e42212e172af83b653808bf43151120df (patch)
tree88c729a9d9a1e7c5d31903518739b13cc7f13d0b
parent75b05b393a98f6bd9e47ac9a49eee21ce512d39e (diff)
downloadastro-0054f78e42212e172af83b653808bf43151120df.tar.gz
astro-0054f78e42212e172af83b653808bf43151120df.tar.zst
astro-0054f78e42212e172af83b653808bf43151120df.zip
Add bundle size information to the build output (#169)
-rw-r--r--packages/astro/package.json2
-rw-r--r--packages/astro/src/build.ts41
-rw-r--r--packages/astro/src/build/bundle.ts12
-rw-r--r--packages/astro/src/build/stats.ts61
-rw-r--r--packages/astro/src/logger.ts21
-rw-r--r--yarn.lock40
6 files changed, 163 insertions, 14 deletions
diff --git a/packages/astro/package.json b/packages/astro/package.json
index 03a07666f..1979ebdd1 100644
--- a/packages/astro/package.json
+++ b/packages/astro/package.json
@@ -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",
diff --git a/packages/astro/src/build.ts b/packages/astro/src/build.ts
index b7cca32e5..a09043db7 100644
--- a/packages/astro/src/build.ts
+++ b/packages/astro/src/build.ts
@@ -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!')));
diff --git a/packages/astro/src/build/bundle.ts b/packages/astro/src/build/bundle.ts
index 0191e8c09..c8bc37ece 100644
--- a/packages/astro/src/build/bundle.ts
+++ b/packages/astro/src/build/bundle.ts
@@ -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;
}
diff --git a/packages/astro/src/build/stats.ts b/packages/astro/src/build/stats.ts
new file mode 100644
index 000000000..e29409994
--- /dev/null
+++ b/packages/astro/src/build/stats.ts
@@ -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);
+ });
+} \ No newline at end of file
diff --git a/packages/astro/src/logger.ts b/packages/astro/src/logger.ts
index a97482459..d5d4fac89 100644
--- a/packages/astro/src/logger.ts
+++ b/packages/astro/src/logger.ts
@@ -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;
+} \ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 0a80fe6f9..515ddf0ca 100644
--- a/yarn.lock
+++ b/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"