From 7ffe57cadc542cb7771b56066abb63ad9e54bae5 Mon Sep 17 00:00:00 2001 From: Evilebot Tnawi Date: Mon, 17 Dec 2018 20:35:08 +0300 Subject: [PATCH] feat: `chunkFilter` option for filtering chunks (#38) --- README.md | 29 +++++ src/index.js | 5 + src/options.json | 3 + .../chunkFilter-option.test.js.snap | 108 ++++++++++++++++++ test/__snapshots__/validation.test.js.snap | 31 +++-- test/chunkFilter-option.test.js | 44 +++++++ test/validation.test.js | 8 ++ 7 files changed, 216 insertions(+), 12 deletions(-) create mode 100644 test/__snapshots__/chunkFilter-option.test.js.snap create mode 100644 test/chunkFilter-option.test.js diff --git a/README.md b/README.md index a7fa649d..2ad85341 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,35 @@ module.exports = { }; ``` +### `chunkFilter` + +Type: `Function<(chunk) -> boolean>` +Default: `() => true` + +Allowing to filter which chunks should be uglified (by default all chunks are uglified). +Return `true` to uglify the chunk, `false` otherwise. + +**webpack.config.js** + +```js +module.exports = { + optimization: { + minimizer: [ + new TerserPlugin({ + chunkFilter: (chunk) => { + // Exclude uglification for the `vendor` chunk + if (chunk.name === 'vendor') { + return false; + } + + return true; + } + }), + ], + }, +}; +``` + ### `cache` Type: `Boolean|String` diff --git a/src/index.js b/src/index.js index 8827e7f8..a2989271 100644 --- a/src/index.js +++ b/src/index.js @@ -23,6 +23,7 @@ class TerserPlugin { minify, terserOptions = {}, test = /\.m?js(\?.*)?$/i, + chunkFilter = () => true, warningsFilter = () => true, extractComments = false, sourceMap = false, @@ -35,6 +36,7 @@ class TerserPlugin { this.options = { test, + chunkFilter, warningsFilter, extractComments, sourceMap, @@ -165,7 +167,10 @@ class TerserPlugin { const processedAssets = new WeakSet(); const tasks = []; + const { chunkFilter } = this.options; + Array.from(chunks) + .filter((chunk) => chunkFilter && chunkFilter(chunk)) .reduce((acc, chunk) => acc.concat(chunk.files || []), []) .concat(compilation.additionalChunkAssets || []) .filter(ModuleFilenameHelpers.matchObject.bind(null, this.options)) diff --git a/src/options.json b/src/options.json index 39db2417..f937c2d1 100644 --- a/src/options.json +++ b/src/options.json @@ -64,6 +64,9 @@ } ] }, + "chunkFilter": { + "instanceof": "Function" + }, "cache": { "anyOf": [ { diff --git a/test/__snapshots__/chunkFilter-option.test.js.snap b/test/__snapshots__/chunkFilter-option.test.js.snap new file mode 100644 index 00000000..0c0a747e --- /dev/null +++ b/test/__snapshots__/chunkFilter-option.test.js.snap @@ -0,0 +1,108 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`when applied with \`chunkFilter\` option matches snapshot for a single \`chunkFilter\`: entry.ad91bb81010ae4d51b51.js 1`] = `"!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){\\"undefined\\"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:\\"Module\\"}),Object.defineProperty(e,\\"__esModule\\",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&\\"object\\"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,\\"default\\",{enumerable:!0,value:e}),2&t&&\\"string\\"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,\\"a\\",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p=\\"\\",n(n.s=1)}([,function(e,t){e.exports=function(){console.log(7)}}]);"`; + +exports[`when applied with \`chunkFilter\` option matches snapshot for a single \`chunkFilter\`: errors 1`] = `Array []`; + +exports[`when applied with \`chunkFilter\` option matches snapshot for a single \`chunkFilter\`: included.509052cb854cc0a5be63.js 1`] = ` +"/******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); +/******/ } +/******/ }; +/******/ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = function(exports) { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/******/ // create a fake namespace object +/******/ // mode & 1: value is a module id, require it +/******/ // mode & 2: merge all properties of value into the ns +/******/ // mode & 4: return value when already ns object +/******/ // mode & 8|1: behave like require +/******/ __webpack_require__.t = function(value, mode) { +/******/ if(mode & 1) value = __webpack_require__(value); +/******/ if(mode & 8) return value; +/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; +/******/ var ns = Object.create(null); +/******/ __webpack_require__.r(ns); +/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); +/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); +/******/ return ns; +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = \\"\\"; +/******/ +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, exports) { + +module.exports = function Bar1() { + const b = 2 + 2; + console.log(b + 1 + 2); +}; + + +/***/ }) +/******/ ]);" +`; + +exports[`when applied with \`chunkFilter\` option matches snapshot for a single \`chunkFilter\`: warnings 1`] = `Array []`; diff --git a/test/__snapshots__/validation.test.js.snap b/test/__snapshots__/validation.test.js.snap index 74be1a0a..2a2976dc 100644 --- a/test/__snapshots__/validation.test.js.snap +++ b/test/__snapshots__/validation.test.js.snap @@ -78,20 +78,27 @@ options.exclude should match some schema in anyOf exports[`validation 7`] = ` "Terser Plugin Invalid Options +options.chunkFilter should pass \\"instanceof\\" keyword validation +" +`; + +exports[`validation 8`] = ` +"Terser Plugin Invalid Options + options.cache should be boolean options.cache should be string options.cache should match some schema in anyOf " `; -exports[`validation 8`] = ` +exports[`validation 9`] = ` "Terser Plugin Invalid Options options.cacheKeys should pass \\"instanceof\\" keyword validation " `; -exports[`validation 9`] = ` +exports[`validation 10`] = ` "Terser Plugin Invalid Options options.parallel should be boolean @@ -100,7 +107,7 @@ options.parallel should match some schema in anyOf " `; -exports[`validation 10`] = ` +exports[`validation 11`] = ` "Terser Plugin Invalid Options options.parallel should be boolean @@ -109,28 +116,28 @@ options.parallel should match some schema in anyOf " `; -exports[`validation 11`] = ` +exports[`validation 12`] = ` "Terser Plugin Invalid Options options.sourceMap should be boolean " `; -exports[`validation 12`] = ` +exports[`validation 13`] = ` "Terser Plugin Invalid Options options.minify should pass \\"instanceof\\" keyword validation " `; -exports[`validation 13`] = ` +exports[`validation 14`] = ` "Terser Plugin Invalid Options options.terserOptions should be object " `; -exports[`validation 14`] = ` +exports[`validation 15`] = ` "Terser Plugin Invalid Options options.extractComments should be boolean @@ -146,7 +153,7 @@ options.extractComments should match some schema in anyOf " `; -exports[`validation 15`] = ` +exports[`validation 16`] = ` "Terser Plugin Invalid Options options.extractComments should be boolean @@ -160,7 +167,7 @@ options.extractComments should match some schema in anyOf " `; -exports[`validation 16`] = ` +exports[`validation 17`] = ` "Terser Plugin Invalid Options options.extractComments should be boolean @@ -175,7 +182,7 @@ options.extractComments should match some schema in anyOf " `; -exports[`validation 17`] = ` +exports[`validation 18`] = ` "Terser Plugin Invalid Options options.extractComments should be boolean @@ -187,14 +194,14 @@ options.extractComments should match some schema in anyOf " `; -exports[`validation 18`] = ` +exports[`validation 19`] = ` "Terser Plugin Invalid Options options.warningsFilter should pass \\"instanceof\\" keyword validation " `; -exports[`validation 19`] = ` +exports[`validation 20`] = ` "Terser Plugin Invalid Options options should NOT have additional properties diff --git a/test/chunkFilter-option.test.js b/test/chunkFilter-option.test.js new file mode 100644 index 00000000..e738d55e --- /dev/null +++ b/test/chunkFilter-option.test.js @@ -0,0 +1,44 @@ +import TerserPlugin from '../src/index'; + +import { cleanErrorStack, createCompiler, compile } from './helpers'; + +describe('when applied with `chunkFilter` option', () => { + let compiler; + + beforeEach(() => { + compiler = createCompiler({ + entry: { + included: `${__dirname}/fixtures/included1.js`, + entry: `${__dirname}/fixtures/entry.js`, + }, + }); + }); + + it('matches snapshot for a single `chunkFilter`', () => { + new TerserPlugin({ + chunkFilter: (chunk) => { + if (chunk.name === 'included') { + return false; + } + + return true; + }, + }).apply(compiler); + + return compile(compiler).then((stats) => { + const errors = stats.compilation.errors.map(cleanErrorStack); + const warnings = stats.compilation.warnings.map(cleanErrorStack); + + expect(errors).toMatchSnapshot('errors'); + expect(warnings).toMatchSnapshot('warnings'); + + for (const file in stats.compilation.assets) { + if ( + Object.prototype.hasOwnProperty.call(stats.compilation.assets, file) + ) { + expect(stats.compilation.assets[file].source()).toMatchSnapshot(file); + } + } + }); + }); +}); diff --git a/test/validation.test.js b/test/validation.test.js index 41790f68..b3a5d74f 100644 --- a/test/validation.test.js +++ b/test/validation.test.js @@ -98,6 +98,14 @@ it('validation', () => { new TerserPlugin({ exclude: [true] }); }).toThrowErrorMatchingSnapshot(); + expect(() => { + new TerserPlugin({ chunkFilter: () => {} }); + }).not.toThrow(); + + expect(() => { + new TerserPlugin({ chunkFilter: true }); + }).toThrowErrorMatchingSnapshot(); + expect(() => { new TerserPlugin({ cache: true }); }).not.toThrow();