diff --git a/README.md b/README.md index ccbfeeb..5ec75bf 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@

> 🙋 Using Rollup? Check out the [rollup-plugin-size](https://github.com/luwes/rollup-plugin-size) port. +> +> 🙋‍♂ Using CI ? Check out [size-plugin-bot](https://github.com/kuldeepkeshwar/size-plugin-bot) 🤖 to automate intergation with CI ## Installation @@ -62,6 +64,7 @@ module.exports = { #### Parameters - `options` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** + - `options.compression` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** compression method(gzip/brotli) to use, default: 'gzip' - `options.pattern` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** minimatch pattern of files to track - `options.exclude` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** minimatch pattern of files NOT to track - `options.filename` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** file name to save filesizes to disk diff --git a/package.json b/package.json index a46b579..4c79649 100644 --- a/package.json +++ b/package.json @@ -66,15 +66,8 @@ "webpack": "^4.39.2" }, "dependencies": { - "axios": "^0.19.0", - "chalk": "^2.4.2", - "ci-env": "^1.9.0", "escape-string-regexp": "^1.0.5", - "glob": "^7.1.4", - "gzip-size": "^5.1.1", - "minimatch": "^3.0.4", - "pretty-bytes": "^5.3.0", - "util.promisify": "^1.0.0" + "size-plugin-core": "^0.0.7" }, "peerDependencies": { "webpack": "*" diff --git a/src/index.mjs b/src/index.mjs index b4e12f4..e505999 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -14,24 +14,15 @@ * the License. */ -import path from 'path'; -import promisify from 'util.promisify'; -import globPromise from 'glob'; -import minimatch from 'minimatch'; -import gzipSize from 'gzip-size'; -import chalk from 'chalk'; -import prettyBytes from 'pretty-bytes'; import escapeRegExp from 'escape-string-regexp'; -import { toMap, dedupe, toFileMap } from './util.mjs'; -import { publishSizes, publishDiff } from './publish-size.mjs'; -import fs from 'fs-extra'; +import SizePluginCore from 'size-plugin-core'; -const glob = promisify(globPromise); const NAME = 'SizePlugin'; /** * `new SizePlugin(options)` * @param {Object} options + * @param {string} [options.compression] compression method(gzip/brotli) to use, default: 'gzip' * @param {string} [options.pattern] minimatch pattern of files to track * @param {string} [options.exclude] minimatch pattern of files NOT to track * @param {string} [options.filename] file name to save filesizes to disk @@ -44,12 +35,10 @@ const NAME = 'SizePlugin'; */ export default class SizePlugin { constructor(options) { - this.options = options || {}; - this.pattern = this.options.pattern || '**/*.{mjs,js,css,html}'; - this.exclude = this.options.exclude; - this.options.filename = this.options.filename || 'size-plugin.json'; - this.options.writeFile = this.options.writeFile !== false; - this.filename = path.join(process.cwd(), this.options.filename); + const pluginOptions=options||{}; + const coreOptions={ ...pluginOptions,stripHash: pluginOptions.stripHash||this.stripHash.bind(this) }; + const core = new SizePluginCore(coreOptions); + this.core = core; } reverseTemplate(filename, template) { @@ -100,66 +89,27 @@ export default class SizePlugin { stripHash(filename) { return ( - (this.options.stripHash && this.options.stripHash(filename)) || this.reverseTemplate(filename, this.output.filename) || this.reverseTemplate(filename, this.output.chunkFilename) || filename ); } - async readFromDisk(filename) { - try { - await fs.ensureFile(filename); - const oldStats = await fs.readJSON(filename); - return oldStats.sort((a, b) => b.timestamp - a.timestamp); - } - catch (err) { - return []; - } - } - async writeToDisk(filename, stats) { - if ( - this.mode === 'production' && - stats.files.some(file => file.diff !== 0) - ) { - const data = await this.readFromDisk(filename); - data.unshift(stats); - if (this.options.writeFile) { - await fs.ensureFile(filename); - await fs.writeJSON(filename, data); - } - this.options.publish && (await publishSizes(data, this.options.filename)); - } - } - async save(files) { - const stats = { - timestamp: Date.now(), - files: files.map(file => ({ - filename: file.name, - previous: file.sizeBefore, - size: file.size, - diff: file.size - file.sizeBefore - })) - }; - this.options.publish && (await publishDiff(stats, this.options.filename)); - this.options.save && (await this.options.save(stats)); - await this.writeToDisk(this.filename, stats); - } - async load(outputPath) { - const data = await this.readFromDisk(this.filename); - if (data.length) { - const [{ files }] = data; - return toFileMap(files); - } - return this.getSizes(outputPath); - } + async apply(compiler) { - const outputPath = compiler.options.output.path; this.output = compiler.options.output; - this.sizes = this.load(outputPath); - this.mode = compiler.options.mode; + this.core.options.mode = compiler.options.mode; + const outputPath = compiler.options.output.path; const afterEmit = (compilation, callback) => { - this.outputSizes(compilation.assets) + const assets = Object.keys(compilation.assets).reduce((agg, key) => { + agg[key] = { + source: compilation.assets[key].source() + }; + return agg; + }, {}); + // assets => {'a.js':{source:'console.log(1)'}} + + this.core.execute(assets, outputPath) .then(output => { if (output) { process.nextTick(() => { @@ -180,107 +130,8 @@ export default class SizePlugin { compiler.plugin('after-emit', afterEmit); } } - - async outputSizes(assets) { - // map of filenames to their previous size - // Fix #7 - fast-async doesn't allow non-promise values. - const sizesBefore = await Promise.resolve(this.sizes); - const isMatched = minimatch.filter(this.pattern); - 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 - ); - - // get a list of unique filenames - const files = [ - ...Object.keys(sizesBefore), - ...Object.keys(this.sizes) - ].filter(dedupe); - - const width = Math.max(...files.map(file => file.length)); - let output = ''; - const items = []; - for (const name of files) { - const size = this.sizes[name] || 0; - const sizeBefore = sizesBefore[name] || 0; - const delta = size - sizeBefore; - 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)); - let deltaText = ''; - if (delta && Math.abs(delta) > 1) { - deltaText = (delta > 0 ? '+' : '') + prettyBytes(delta); - if (delta > 1024) { - sizeText = chalk.bold(sizeText); - deltaText = chalk.red(deltaText); - } - else if (delta < -10) { - deltaText = chalk.green(deltaText); - } - sizeText += ` (${deltaText})`; - } - let text = msg + sizeText + '\n'; - const item = { - name, - sizeBefore, - size, - sizeText, - delta, - deltaText, - msg, - color - }; - items.push(item); - if (this.options.decorateItem) { - text = this.options.decorateItem(text, item) || text; - } - output += text; - } - if (this.options.decorateAfter) { - const opts = { - sizes: items, - raw: { sizesBefore, sizes: this.sizes }, - output - }; - const text = this.options.decorateAfter(opts); - if (text) { - output += '\n' + text.replace(/^\n/g, ''); - } - } - await this.save(items); - return output; - } - - async getSizes(cwd) { - const files = await glob(this.pattern, { cwd, ignore: this.exclude }); - - const sizes = await Promise.all( - files.map(file => gzipSize.file(path.join(cwd, file)).catch(() => null)) - ); - - return toMap(files.map(filename => this.stripHash(filename)), sizes); - } } - /** * @name Item * @typedef Item @@ -295,7 +146,6 @@ export default class SizePlugin { * @public */ - /** * @name Data * @typedef Data diff --git a/src/publish-size.mjs b/src/publish-size.mjs deleted file mode 100644 index 91ebe11..0000000 --- a/src/publish-size.mjs +++ /dev/null @@ -1,30 +0,0 @@ -import { repo, sha, event, branch, pull_request_number, ci } from 'ci-env'; -import axios from 'axios'; - -const SIZE_STORE_ENDPOINT = process.env.SIZE_STORE_ENDPOINT || 'https://size-plugin-store.now.sh' ; - -// TODO: add option to turn off publishing of sizes. - -export async function publishDiff(diff,filename) { - if (process.env.NODE_ENV !=='test' && ci && event == 'pull_request') { - try { - const params = { ci,repo, branch, sha, pull_request_number, diff,filename }; - await axios.post(`${SIZE_STORE_ENDPOINT}/diff`, params); - } - catch (error) { - console.error('error: while publishing diff', error); - } - } -} -export async function publishSizes(size,filename) { - // TODO: read allowed branch from configuration - if (process.env.NODE_ENV !=='test' && ci && event == 'push' && branch==='master') { - try { - const params = { ci,repo, branch, sha, pull_request_number, size,filename }; - await axios.post(`${SIZE_STORE_ENDPOINT}/size`, params); - } - catch (error) { - console.error('error: while publishing sizes', error); - } - } -} diff --git a/src/util.mjs b/src/util.mjs deleted file mode 100644 index 4d6fbe2..0000000 --- a/src/util.mjs +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright 2018 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -export function toMap(names, values) { - return names.reduce((map, name, i) => { - map[name] = values[i]; - return map; - }, {}); -} - -export function dedupe(item, index, arr) { - return arr.indexOf(item) === index; -} -export function toFileMap(files){ - return files.reduce((result, file) => { - if (file.size){ // excluding files with size 0 - result[file.filename] = file.size; - } - return result; - }, {}); -}