diff --git a/.gitignore b/.gitignore index 3a8ec2b..55558b6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules dist -package-lock.json \ No newline at end of file +package-lock.json +.history \ No newline at end of file diff --git a/README.md b/README.md index d004f9b..a91562a 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,26 @@ module.exports = { --- +## Options + +### History +This option will store a json file with the history of your build that you then can use to do bundle analysis over a period of time +```js +// webpack.config.js +const SizePlugin = require('size-plugin'); + +module.exports = { + plugins: [ + new SizePlugin({ + json: true, + filename: 'report.json' // Relative to where webpack is ran + }) + ] +} +``` + +--- + ## License [Apache 2.0](LICENSE) diff --git a/package.json b/package.json index 5543d2f..26a9e90 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "escape-string-regexp": "^1.0.5", "glob": "^7.1.2", "gzip-size": "^5.0.0", + "jsx": "^0.9.89", "minimatch": "^3.0.4", "pretty-bytes": "^5.1.0", "util.promisify": "^1.0.0" diff --git a/src/index.mjs b/src/index.mjs index 0268f73..6c4b1e4 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -15,6 +15,7 @@ */ import path from 'path'; +import fs from 'fs'; import promisify from 'util.promisify'; import globPromise from 'glob'; import minimatch from 'minimatch'; @@ -33,6 +34,9 @@ export default class SizePlugin { this.options = options || {}; this.pattern = this.options.pattern || '**/*.{mjs,js,css,html}'; this.exclude = this.options.exclude; + this.json = this.options.json; + this.jsonPath = path.resolve(process.cwd(), this.options.filename || 'build-sizes.json'); + this.buildTimestamp = Math.floor(Date.now()); } reverseTemplate(filename, template) { @@ -81,8 +85,8 @@ export default class SizePlugin { stripHash(filename) { return ( this.reverseTemplate(filename, this.output.filename) || - this.reverseTemplate(filename, this.output.chunkFilename) || - filename + this.reverseTemplate(filename, this.output.chunkFilename) || + filename ); } @@ -104,6 +108,55 @@ export default class SizePlugin { }); } + async writeFile (file, data) { + return new Promise((resolve, reject) => { + fs.writeFile(file, data, error => { + if (error) reject(error); + resolve(); + }); + }); + } + + async readFile (file) { + return new Promise((resolve, reject) => { + fs.readFile(file, (error, data) => { + if (error && error.code !== 'ENOENT') reject(error); + resolve(data); + }); + }); + } + + async storeToFile (buildResult) { + console.log(this.jsonPath); + try { + const fileContents = await this.readFile(this.jsonPath); + let json = []; + if (fileContents) { + json = JSON.parse(fileContents); + } + + json.push(buildResult); + await this.writeFile(this.jsonPath, JSON.stringify(json)); + console.log(chalk.green(`file got stored at: ${this.jsonPath}`)); + } + catch (e) { + console.error(chalk.green(`Couldn't store json: ${e}`)); + } + } + + async getPreviousSizeFromJson (fileName) { + const fileContents = await this.readFile(this.jsonPath); + if (fileContents) { + const json = JSON.parse(fileContents); + const files = json[json.length - 1].files; + const result = files.find( file => file.filename === fileName ); + + return result.size; + } + + return undefined; + } + async outputSizes (assets) { // map of filenames to their previous size // Fix #7 - fast-async doesn't allow non-promise values. @@ -112,7 +165,7 @@ export default class SizePlugin { const isExcluded = this.exclude ? minimatch.filter(this.exclude) : () => false; const assetNames = Object.keys(assets).filter(file => isMatched(file) && !isExcluded(file)); const sizes = await Promise.all(assetNames.map(name => gzipSize(assets[name].source()))); - + // map of de-hashed filenames to their final size this.sizes = toMap(assetNames.map(filename => this.stripHash(filename)), sizes); @@ -121,12 +174,32 @@ export default class SizePlugin { const width = Math.max(...files.map(file => file.length)); let output = ''; + + let jsonOutput= { + timestamp: this.buildTimestamp, + files: [] + }; + for (const name of files) { - const size = this.sizes[name] || 0; - const delta = size - (sizesBefore[name] || 0); - const msg = new Array(width - name.length + 2).join(' ') + name + ' ⏤ '; + const size = this.sizes[name] || 0; + let sizeBefore = sizesBefore[name] || 0; + if (sizeBefore === 0) { + sizeBefore = await this.getPreviousSizeFromJson(name); + } + const delta = size - (sizeBefore || 0); + const msg = `${new Array(width - name.length + 2).join(' ')}${name} ⏤ `; const color = size > 100 * 1024 ? 'red' : size > 40 * 1024 ? 'yellow' : size > 20 * 1024 ? 'cyan' : 'green'; - let sizeText = chalk[color](prettyBytes(size)); + + if (this.json){ + jsonOutput.files.push({ + filename: name, + previous: sizeBefore || size, + size: size, + diff: delta || 0 + }); + } + + let sizeText = chalk[color](prettyBytes(size)); if (delta) { let deltaText = (delta > 0 ? '+' : '') + prettyBytes(delta); if (delta > 1024) { @@ -141,6 +214,9 @@ export default class SizePlugin { output += msg + sizeText + '\n'; } if (output) { + if (this.json){ + this.storeToFile(jsonOutput); + } console.log(output); } } diff --git a/yarn.lock b/yarn.lock index 65e29c7..d1715a6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2087,6 +2087,23 @@ escodegen@^1.9.1: optionalDependencies: source-map "~0.6.1" +"escodegen@~ 0.0.20", "escodegen@~ 0.0.28": + version "0.0.28" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-0.0.28.tgz#0e4ff1715f328775d6cab51ac44a406cd7abffd3" + integrity sha1-Dk/xcV8yh3XWyrUaxEpAbNer/9M= + dependencies: + esprima "~1.0.2" + estraverse "~1.3.0" + optionalDependencies: + source-map ">= 0.1.2" + +"escope@~ 1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/escope/-/escope-1.0.3.tgz#759dce8496c4248fec2d0caaf4108bcf3f1a7f5d" + integrity sha1-dZ3OhJbEJI/sLQyq9BCLzz8af10= + dependencies: + estraverse "^2.0.0" + eslint-config-developit@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/eslint-config-developit/-/eslint-config-developit-1.1.1.tgz#724c0855ffcbeab81be033ea7a47afec4b246d2c" @@ -2192,6 +2209,19 @@ eslint@^5.4.0: table "^4.0.3" text-table "^0.2.0" +"esmangle@~ 0.0.14": + version "0.0.17" + resolved "https://registry.yarnpkg.com/esmangle/-/esmangle-0.0.17.tgz#4c5c93607cde5d1276bad396e836229dba68d90c" + integrity sha1-TFyTYHzeXRJ2utOW6DYinbpo2Qw= + dependencies: + escodegen "~ 0.0.28" + escope "~ 1.0.0" + esprima "~ 1.0.2" + esshorten "~ 0.0.2" + estraverse "~ 1.3.2" + optimist "*" + source-map "~ 0.1.8" + espree@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/espree/-/espree-4.0.0.tgz#253998f20a0f82db5d866385799d912a83a36634" @@ -2211,6 +2241,11 @@ esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" +"esprima@~ 1.0.2", esprima@~1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-1.0.4.tgz#9f557e08fc3b4d26ece9dd34f8fbf476b62585ad" + integrity sha1-n1V+CPw7TSbs6d00+Pv0drYlha0= + esquery@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.1.tgz#406c51658b1f5991a5f9b62b1dc25b00e3e5c708" @@ -2223,10 +2258,33 @@ esrecurse@^4.1.0: dependencies: estraverse "^4.1.0" +"esshorten@~ 0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/esshorten/-/esshorten-0.0.2.tgz#28a652f1efd40c8e227f8c6de7dbe6b560ee8129" + integrity sha1-KKZS8e/UDI4if4xt59vmtWDugSk= + dependencies: + escope "~ 1.0.0" + estraverse "~ 1.2.0" + +estraverse@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-2.0.0.tgz#5ae46963243600206674ccb24a09e16674fcdca1" + integrity sha1-WuRpYyQ2ACBmdMyySgnhZnT83KE= + estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" +"estraverse@~ 1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.2.0.tgz#6a3dc8a46a5d6766e5668639fc782976ce5660fd" + integrity sha1-aj3IpGpdZ2blZoY5/Hgpds5WYP0= + +"estraverse@~ 1.3.2", estraverse@~1.3.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.3.2.tgz#37c2b893ef13d723f276d878d60d8535152a6c42" + integrity sha1-N8K4k+8T1yPydth41g2FNRUqbEI= + estree-walker@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.2.1.tgz#bdafe8095383d8414d5dc2ecf4c9173b6db9412e" @@ -3684,6 +3742,17 @@ jsx-ast-utils@^2.0.1: dependencies: array-includes "^3.0.3" +jsx@^0.9.89: + version "0.9.89" + resolved "https://registry.yarnpkg.com/jsx/-/jsx-0.9.89.tgz#c589688dc9ffe04a211fdb633cc7d202dd78a922" + integrity sha1-xYlojcn/4EohH9tjPMfSAt14qSI= + dependencies: + escodegen "~ 0.0.20" + esmangle "~ 0.0.14" + esprima "~ 1.0.2" + source-map "~ 0.1.22" + source-map-support "~ 0.2.1" + kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" @@ -4348,9 +4417,10 @@ onetime@^2.0.0: dependencies: mimic-fn "^1.0.0" -optimist@^0.6.1: +optimist@*, optimist@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" + integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY= dependencies: minimist "~0.0.1" wordwrap "~0.0.2" @@ -5633,10 +5703,29 @@ source-map-support@^0.5.6: buffer-from "^1.0.0" source-map "^0.6.0" +"source-map-support@~ 0.2.1": + version "0.2.10" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.2.10.tgz#ea5a3900a1c1cb25096a0ae8cc5c2b4b10ded3dc" + integrity sha1-6lo5AKHByyUJagrozFwrSxDe09w= + dependencies: + source-map "0.1.32" + source-map-url@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" +source-map@0.1.32: + version "0.1.32" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.32.tgz#c8b6c167797ba4740a8ea33252162ff08591b266" + integrity sha1-yLbBZ3l7pHQKjqMyUhYv8IWRsmY= + dependencies: + amdefine ">=0.0.4" + +"source-map@>= 0.1.2": + version "0.7.3" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" + integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== + source-map@^0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" @@ -5651,6 +5740,13 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" +"source-map@~ 0.1.22", "source-map@~ 0.1.8": + version "0.1.43" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.43.tgz#c24bc146ca517c1471f5dacbe2571b2b7f9e3346" + integrity sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y= + dependencies: + amdefine ">=0.0.4" + spdx-correct@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.0.0.tgz#05a5b4d7153a195bc92c3c425b69f3b2a9524c82"