| 1 | 'use strict'; |
| 2 | |
| 3 | const Table = require('cli-table'); |
| 4 | const filesize = require('filesize'); |
| 5 | const chalk = require('chalk'); |
| 6 | const join = require('path').join; |
| 7 | const fs = require('fs'); |
| 8 | const mkdirp = require('mkdirp'); |
| 9 | |
| 10 | const BUNDLE_SIZES_FILE_NAME = join(__dirname, '../../build/bundle-sizes.json'); |
| 11 | const prevBuildResults = fs.existsSync(BUNDLE_SIZES_FILE_NAME) |
| 12 | ? require(BUNDLE_SIZES_FILE_NAME) |
| 13 | : {bundleSizes: []}; |
| 14 | |
| 15 | const currentBuildResults = { |
| 16 | // Mutated inside build.js during a build run. |
| 17 | bundleSizes: [], |
| 18 | }; |
| 19 | |
| 20 | function saveResults() { |
| 21 | if (process.env.CI === true) { |
| 22 | mkdirp.sync('build/sizes'); |
| 23 | fs.writeFileSync( |
| 24 | join('build', 'sizes', `bundle-sizes-${process.env.NODE_INDEX}.json`), |
| 25 | JSON.stringify(currentBuildResults, null, 2) |
| 26 | ); |
| 27 | } else { |
| 28 | // Write all the bundle sizes to a single JSON file. |
| 29 | fs.writeFileSync( |
| 30 | BUNDLE_SIZES_FILE_NAME, |
| 31 | JSON.stringify(currentBuildResults, null, 2) |
| 32 | ); |
| 33 | } |
| 34 | } |
| 35 | |
| 36 | function fractionalChange(prev, current) { |
| 37 | return (current - prev) / prev; |
| 38 | } |
| 39 | |
| 40 | function percentChangeString(change) { |
| 41 | if (!isFinite(change)) { |
| 42 | // When a new package is created |
| 43 | return 'n/a'; |
| 44 | } |
| 45 | const formatted = (change * 100).toFixed(1); |
| 46 | if (/^-|^0(?:\.0+)$/.test(formatted)) { |
| 47 | return `${formatted}%`; |
| 48 | } else { |
| 49 | return `+${formatted}%`; |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | const resultsHeaders = [ |
| 54 | 'Bundle', |
| 55 | 'Prev Size', |
| 56 | 'Current Size', |
| 57 | 'Diff', |
| 58 | 'Prev Gzip', |
| 59 | 'Current Gzip', |
| 60 | 'Diff', |
| 61 | ]; |
| 62 | |
| 63 | function generateResultsArray(current, prevResults) { |
| 64 | return current.bundleSizes |
| 65 | .map(result => { |
| 66 | const prev = prevResults.bundleSizes.filter( |
| 67 | res => |
| 68 | res.filename === result.filename && |
| 69 | res.bundleType === result.bundleType |
| 70 | )[0]; |
| 71 | if (result === prev) { |
| 72 | // We didn't rebuild this bundle. |
| 73 | return; |
| 74 | } |
| 75 | |
| 76 | const size = result.size; |
| 77 | const gzip = result.gzip; |
| 78 | let prevSize = prev ? prev.size : 0; |
| 79 | let prevGzip = prev ? prev.gzip : 0; |
| 80 | |
| 81 | return { |
| 82 | filename: result.filename, |
| 83 | bundleType: result.bundleType, |
| 84 | packageName: result.packageName, |
| 85 | prevSize: filesize(prevSize), |
| 86 | prevFileSize: filesize(size), |
| 87 | prevFileSizeChange: fractionalChange(prevSize, size), |
| 88 | prevFileSizeAbsoluteChange: size - prevSize, |
| 89 | prevGzip: filesize(prevGzip), |
| 90 | prevGzipSize: filesize(gzip), |
| 91 | prevGzipSizeChange: fractionalChange(prevGzip, gzip), |
| 92 | prevGzipSizeAbsoluteChange: gzip - prevGzip, |
| 93 | }; |
| 94 | // Strip any nulls |
| 95 | }) |
| 96 | .filter(f => f); |
| 97 | } |
| 98 | |
| 99 | function printResults() { |
| 100 | const table = new Table({ |
| 101 | head: resultsHeaders.map(label => chalk.gray.yellow(label)), |
| 102 | }); |
| 103 | |
| 104 | const results = generateResultsArray(currentBuildResults, prevBuildResults); |
| 105 | results.forEach(result => { |
| 106 | table.push([ |
| 107 | chalk.white.bold(`${result.filename} (${result.bundleType})`), |
| 108 | chalk.gray.bold(result.prevSize), |
| 109 | chalk.white.bold(result.prevFileSize), |
| 110 | percentChangeString(result.prevFileSizeChange), |
| 111 | chalk.gray.bold(result.prevGzip), |
| 112 | chalk.white.bold(result.prevGzipSize), |
| 113 | percentChangeString(result.prevGzipSizeChange), |
| 114 | ]); |
| 115 | }); |
| 116 | |
| 117 | return table.toString(); |
| 118 | } |
| 119 | |
| 120 | module.exports = { |
| 121 | currentBuildResults, |
| 122 | generateResultsArray, |
| 123 | printResults, |
| 124 | saveResults, |
| 125 | resultsHeaders, |
| 126 | }; |