UNPKG

ng-bundle-analyzer

Version:

Generate and display treemaps of Angular bundle.

133 lines (106 loc) 4.82 kB
#!/usr/bin/env node 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; } })();