From 2b5961c18cf770e03229b24739920976e92e249a Mon Sep 17 00:00:00 2001 From: Evilebot Tnawi Date: Wed, 6 May 2020 16:27:12 +0300 Subject: [PATCH] fix: parallel in multi compilation mode --- src/index.js | 34 ++-- test/TerserPlugin.test.js | 73 ++++++++ .../TerserPlugin.test.js.snap.webpack4 | 164 ++++++++++++++++++ .../TerserPlugin.test.js.snap.webpack5 | 113 ++++++++++++ 4 files changed, 364 insertions(+), 20 deletions(-) diff --git a/src/index.js b/src/index.js index 2d3bc7fd..36b22c40 100644 --- a/src/index.js +++ b/src/index.js @@ -428,20 +428,13 @@ class TerserPlugin { yield task; } - async runTask(task) { - if (this.worker) { - return this.worker.transform(serialize(task)); - } - - return minifyFn(task); - } - async runTasks() { const availableNumberOfCores = TerserPlugin.getAvailableNumberOfCores( this.options.parallel ); let concurrency = Infinity; + let worker; if (availableNumberOfCores > 0) { // Do not create unnecessary workers when the number of files is less than the available cores, it saves memory @@ -452,10 +445,10 @@ class TerserPlugin { concurrency = numWorkers; - this.worker = new Worker(require.resolve('./minify'), { numWorkers }); + worker = new Worker(require.resolve('./minify'), { numWorkers }); // https://github.com/facebook/jest/issues/8872#issuecomment-524822081 - const workerStdout = this.worker.getStdout(); + const workerStdout = worker.getStdout(); if (workerStdout) { workerStdout.on('data', (chunk) => { @@ -463,7 +456,7 @@ class TerserPlugin { }); } - const workerStderr = this.worker.getStderr(); + const workerStderr = worker.getStderr(); if (workerStderr) { workerStderr.on('data', (chunk) => { @@ -480,7 +473,11 @@ class TerserPlugin { let taskResult; try { - taskResult = await this.runTask(task); + if (worker) { + taskResult = await worker.transform(serialize(task)); + } else { + taskResult = minifyFn(task); + } } catch (error) { taskResult = { error }; } @@ -518,15 +515,13 @@ class TerserPlugin { ); } - return Promise.all(scheduledTasks); - } + return Promise.all(scheduledTasks).then(() => { + if (worker) { + return worker.end(); + } - async exitTasks() { - if (!this.worker) { return Promise.resolve(); - } - - return this.worker.end(); + }); } apply(compiler) { @@ -610,7 +605,6 @@ class TerserPlugin { ); await this.runTasks(); - await this.exitTasks(); Object.keys(allExtractedComments).forEach((commentsFilename) => { const extractedComments = new Set([ diff --git a/test/TerserPlugin.test.js b/test/TerserPlugin.test.js index f86f60f2..39a91eed 100644 --- a/test/TerserPlugin.test.js +++ b/test/TerserPlugin.test.js @@ -124,6 +124,79 @@ describe('TerserPlugin', () => { }); }); + it('should work in multi compiler mode with the one plugin', async () => { + const plugins = [new TerserPlugin()]; + const multiCompiler = getCompiler([ + { + mode: 'production', + bail: true, + cache: getCompiler.isWebpack4() ? false : { type: 'memory' }, + entry: `${__dirname}/fixtures/entry.js`, + output: { + path: `${__dirname}/dist`, + filename: '[name]-1.js', + chunkFilename: '[id]-1.[name].js', + }, + optimization: { + minimize: false, + }, + }, + { + mode: 'production', + bail: true, + cache: getCompiler.isWebpack4() ? false : { type: 'memory' }, + entry: `${__dirname}/fixtures/entry.js`, + output: { + path: `${__dirname}/dist`, + filename: '[name]-2.js', + chunkFilename: '[id]-2.[name].js', + }, + optimization: { + minimize: false, + }, + plugins, + }, + { + mode: 'production', + bail: true, + cache: getCompiler.isWebpack4() ? false : { type: 'memory' }, + entry: `${__dirname}/fixtures/import-export/entry.js`, + output: { + path: `${__dirname}/dist-MultiCompiler`, + filename: '[name]-3.js', + chunkFilename: '[id]-3.[name].js', + }, + optimization: { + minimize: false, + }, + plugins, + }, + ]); + + const emptyPluginCount = countPlugins(multiCompiler.compilers[0]); + const expectedPluginCount = countPlugins(multiCompiler.compilers[1]); + + expect(emptyPluginCount).not.toEqual(expectedPluginCount); + + multiCompiler.compilers.slice(2).forEach((compiler) => { + const pluginCount = countPlugins(compiler); + + expect(pluginCount).not.toEqual(emptyPluginCount); + expect(pluginCount).toEqual(expectedPluginCount); + expect(pluginCount).toMatchSnapshot('compiler plugin count'); + }); + + const multiStats = await compile(multiCompiler); + + multiStats.stats.forEach((stats, index) => { + expect( + readsAssets(multiCompiler.compilers[index], stats) + ).toMatchSnapshot('assets'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + }); + }); + it('should work as a plugin', async () => { const compiler = getCompiler({ plugins: [new TerserPlugin()], diff --git a/test/__snapshots__/TerserPlugin.test.js.snap.webpack4 b/test/__snapshots__/TerserPlugin.test.js.snap.webpack4 index b5bf44b9..278a1b73 100644 --- a/test/__snapshots__/TerserPlugin.test.js.snap.webpack4 +++ b/test/__snapshots__/TerserPlugin.test.js.snap.webpack4 @@ -184,6 +184,170 @@ exports[`TerserPlugin should work as a plugin: errors 1`] = `Array []`; exports[`TerserPlugin should work as a plugin: warnings 1`] = `Array []`; +exports[`TerserPlugin should work in multi compiler mode with the one plugin: assets 1`] = ` +Object { + "main-1.js": "/******/ (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) { + +// foo +/* @preserve*/ +// bar +const a = 2 + 2; + +module.exports = function Foo() { + const b = 2 + 2; + console.log(b + 1 + 2); +}; + + +/***/ }) +/******/ ]);", +} +`; + +exports[`TerserPlugin should work in multi compiler mode with the one plugin: assets 2`] = ` +Object { + "main-2.js": "!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=0)}([function(e,t){e.exports=function(){console.log(7)}}]);", +} +`; + +exports[`TerserPlugin should work in multi compiler mode with the one plugin: assets 3`] = ` +Object { + "main-3.js": "!function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){\\"undefined\\"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:\\"Module\\"}),Object.defineProperty(e,\\"__esModule\\",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&\\"object\\"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,\\"default\\",{enumerable:!0,value:e}),2&t&&\\"string\\"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,\\"a\\",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p=\\"\\",r(r.s=0)}([function(e,t,r){\\"use strict\\";r.r(t);t.default=function(){const e=\\"baz\\"+Math.random();return()=>({a:\\"foobar\\"+e,b:\\"foo\\",baz:e})}}]);", +} +`; + +exports[`TerserPlugin should work in multi compiler mode with the one plugin: compiler plugin count 1`] = ` +Object { + "additionalPass": 0, + "afterCompile": 0, + "afterEmit": 1, + "afterEnvironment": 0, + "afterPlugins": 0, + "afterResolvers": 2, + "assetEmitted": 0, + "beforeCompile": 0, + "beforeRun": 1, + "compilation": 42, + "compile": 0, + "contextModuleFactory": 0, + "done": 1, + "emit": 0, + "entryOption": 1, + "environment": 0, + "failed": 0, + "infrastructureLog": 0, + "infrastructurelog": 0, + "invalid": 1, + "make": 1, + "normalModuleFactory": 1, + "run": 0, + "shouldEmit": 1, + "thisCompilation": 3, + "watchClose": 0, + "watchRun": 0, +} +`; + +exports[`TerserPlugin should work in multi compiler mode with the one plugin: errors 1`] = `Array []`; + +exports[`TerserPlugin should work in multi compiler mode with the one plugin: errors 2`] = `Array []`; + +exports[`TerserPlugin should work in multi compiler mode with the one plugin: errors 3`] = `Array []`; + +exports[`TerserPlugin should work in multi compiler mode with the one plugin: warnings 1`] = `Array []`; + +exports[`TerserPlugin should work in multi compiler mode with the one plugin: warnings 2`] = `Array []`; + +exports[`TerserPlugin should work in multi compiler mode with the one plugin: warnings 3`] = `Array []`; + exports[`TerserPlugin should work in multi compiler mode: assets 1`] = ` Object { "main-1.js": "/******/ (function(modules) { // webpackBootstrap diff --git a/test/__snapshots__/TerserPlugin.test.js.snap.webpack5 b/test/__snapshots__/TerserPlugin.test.js.snap.webpack5 index 191c2017..c8e37861 100644 --- a/test/__snapshots__/TerserPlugin.test.js.snap.webpack5 +++ b/test/__snapshots__/TerserPlugin.test.js.snap.webpack5 @@ -184,6 +184,119 @@ exports[`TerserPlugin should work as a plugin: errors 1`] = `Array []`; exports[`TerserPlugin should work as a plugin: warnings 1`] = `Array []`; +exports[`TerserPlugin should work in multi compiler mode with the one plugin: assets 1`] = ` +Object { + "main-1.js": "/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ({ + +/***/ 791: +/***/ ((module) => { + +// foo +/* @preserve*/ +// bar +const a = 2 + 2; + +module.exports = function Foo() { + const b = 2 + 2; + console.log(b + 1 + 2); +}; + + +/***/ }) + +/******/ }); +/************************************************************************/ +/******/ // The module cache +/******/ var __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ if(__webpack_module_cache__[moduleId]) { +/******/ return __webpack_module_cache__[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/************************************************************************/ +/******/ // startup +/******/ // Load entry module +/******/ // This entry module is referenced by other modules so it can't be inlined +/******/ __webpack_require__(791); +/******/ })() +;", +} +`; + +exports[`TerserPlugin should work in multi compiler mode with the one plugin: assets 2`] = ` +Object { + "main-2.js": "(()=>{var r={791:r=>{r.exports=function(){console.log(7)}}},o={};!function t(e){if(o[e])return o[e].exports;var n=o[e]={exports:{}};return r[e](n,n.exports,t),n.exports}(791)})();", +} +`; + +exports[`TerserPlugin should work in multi compiler mode with the one plugin: assets 3`] = ` +Object { + "main-3.js": "(()=>{\\"use strict\\"})();", +} +`; + +exports[`TerserPlugin should work in multi compiler mode with the one plugin: compiler plugin count 1`] = ` +Object { + "additionalPass": 0, + "afterCompile": 0, + "afterDone": 0, + "afterEmit": 1, + "afterEnvironment": 0, + "afterPlugins": 0, + "afterResolvers": 0, + "assetEmitted": 0, + "beforeCompile": 0, + "beforeRun": 1, + "compilation": 48, + "compile": 0, + "contextModuleFactory": 0, + "done": 1, + "emit": 0, + "entryOption": 1, + "environment": 0, + "failed": 0, + "infrastructureLog": 0, + "initialize": 0, + "invalid": 1, + "make": 1, + "normalModuleFactory": 1, + "run": 0, + "shouldEmit": 1, + "thisCompilation": 5, + "watchClose": 0, + "watchRun": 0, +} +`; + +exports[`TerserPlugin should work in multi compiler mode with the one plugin: errors 1`] = `Array []`; + +exports[`TerserPlugin should work in multi compiler mode with the one plugin: errors 2`] = `Array []`; + +exports[`TerserPlugin should work in multi compiler mode with the one plugin: errors 3`] = `Array []`; + +exports[`TerserPlugin should work in multi compiler mode with the one plugin: warnings 1`] = `Array []`; + +exports[`TerserPlugin should work in multi compiler mode with the one plugin: warnings 2`] = `Array []`; + +exports[`TerserPlugin should work in multi compiler mode with the one plugin: warnings 3`] = `Array []`; + exports[`TerserPlugin should work in multi compiler mode: assets 1`] = ` Object { "main-1.js": "/******/ (() => { // webpackBootstrap