ng-bundle-analyzer
Version:
Generate and display treemaps of Angular bundle.
133 lines (106 loc) • 4.82 kB
JavaScript
import { exec as _exec } from 'child_process';
import { fromBytes, fromPath, fromDate, fromMs } from './transformers.js';
import printRunningAnimation from './print-running-animation.js';
import readAppName from './read-app-name.js';
import logSymbols from 'log-symbols';
import open from 'open';
import archiver from 'archiver';
import fs from 'fs-extra';
import { promisify } from 'util';
const startGlobal = Date.now();
const exec = promisify(_exec);
(async () => {
// 0. Cleanup old bundle
let stopRunning = startRunning('Removing old builds');
const appName = readAppName();
const distPath = 'dist/ng-bundle-analyzer';
if (fs.existsSync(distPath)) {
await fs.rm(distPath, { recursive: true, force: true });
}
await fs.copy('node_modules/ng-bundle-analyzer/page/assets', distPath + '/assets');
stopRunning();
// --------------------------
// 1. Build Angular App.
stopRunning = startRunning('Building application');
await exec(
`npx ng build --delete-output-path=false --configuration=production --output-path=${distPath} --source-map --named-chunks`,
);
stopRunning();
// --------------------------
// 2. Generate bundle stats.
stopRunning = startRunning('Collecting bundle stats');
await exec(`npx source-map-explorer ${distPath}/*.js --json ${distPath}/chunk-stats.json --no-border-checks`);
stopRunning();
// --------------------------
// 3. Generate HTML treemaps.
stopRunning = startRunning('Generating tree maps of JS chunks');
// 3.1 Generate HTML treemap of combined chunks.
await exec(`npx source-map-explorer ${distPath}/*.js --html ${distPath}/combined-treemap.html --no-border-checks`);
// 3.2 Generate HTML treemap for individual chunks.
const { results: chunkStats } = JSON.parse((await fs.readFile(`${distPath}/chunk-stats.json`)).toString());
chunkStats.sort((a, b) => b.totalBytes - a.totalBytes);
for (const stat of chunkStats) {
await exec(`npx source-map-explorer ${stat.bundleName} --html ${stat.bundleName}.html --no-border-checks`);
}
stopRunning();
// --------------------------
// Interpolate summary HTML page with chunk stats.
stopRunning = startRunning('Building summary HTML page');
const combinedChunksSize = chunkStats.reduce((acc, curr) => acc + curr.totalBytes, 0);
const chunkStatsHtml =
chunkStats.reduce((acc, curr) => acc + buildChunkHtml(curr.bundleName, curr.totalBytes), '') +
buildChunkHtml(`Combined (${chunkStats.length} chunks)`, combinedChunksSize, 'combined-treemap.html');
const zipName = `${appName}-bundle-${getRandomInt(1, 500)}.zip`;
const interpolatedHtml = (await fs.readFile('node_modules/ng-bundle-analyzer/page/index.html'))
.toString()
.replace('<!-- $CHUNKS$ -->', chunkStatsHtml)
.replace('<!-- $APP-NAME$ -->', appName)
.replace('$ZIP_NAME$', zipName)
.replace('<!-- $DATE$ -->', fromDate(new Date()));
createZip(zipName);
await fs.writeFile(`${distPath}/ng-bundle-analyzer.html`, interpolatedHtml);
stopRunning();
// --------------------------
// Open interpolated summary HTML page.
open(`${distPath}/ng-bundle-analyzer.html`);
console.log(`===> Finished after ` + `${fromMs(Date.now() - startGlobal)}`.blue);
console.log(`===> Results at ` + `${distPath}/ng-bundle-analyzer.html`.blue);
function startRunning(text) {
const start = Date.now();
const interval = printRunningAnimation(text);
return () => {
clearInterval(interval);
process.stdout.write(`\r${logSymbols.success} ${text} (${fromMs(Date.now() - start)})\n`);
};
}
function buildChunkHtml(path, bytes, link) {
const chunksHtml = `
<div class="tr">
<span>${fromPath(path)}</span>
<span>${fromBytes(bytes)}</span>
<a href="${link ? link : fromPath(path) + '.html'}" target="_blank">Open</a>
</div> `;
return chunksHtml;
}
function createZip(name) {
const output = fs.createWriteStream(`${distPath}/${name}`);
const archive = archiver('zip', {
zlib: { level: 9 }, // Sets the compression level.
});
archive.pipe(output);
archive.file(`${distPath}/combined-treemap.html`, { name: 'combined-treemap.html' });
for (const chunkStat of chunkStats) {
archive.file(chunkStat.bundleName + '.html', { name: `${fromPath(chunkStat.bundleName)}.html` });
}
archive.file(`${distPath}/ng-bundle-analyzer.html`, { name: 'OPEN_ME.html' });
archive.directory(`${distPath}/assets/`, 'assets');
archive.finalize();
return name;
}
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
})();