From 84782d4942487301334f0f3c9ae2a8054be84e37 Mon Sep 17 00:00:00 2001 From: Ivan Rudoy Date: Wed, 10 Apr 2019 20:22:21 +1000 Subject: [PATCH 01/11] fix: remove empty modules --- .editorconfig | 3 + src/index.js | 30 +++++ src/loader.js | 5 + .../expected/{1.async.css => 0.async.css} | 0 .../expected/{1.main.css => 0.main.css} | 0 test/cases/nested/expected/main.js | 110 ++++++++++++++++++ .../shared-import/expected/{1.css => 0.css} | 0 .../simple-async-source-map/expected/0.css | 6 + .../expected/0.css.map | 1 + .../simple-async-source-map/expected/1.css | 6 - .../expected/1.css.map | 1 - .../simple-async-source-map/expected/2.css | 4 +- .../expected/2.css.map | 2 +- test/cases/simple-async/expected/0.css | 4 + test/cases/simple-async/expected/1.css | 4 - test/cases/simple-async/expected/2.css | 4 +- test/cases/simple/expected/main.js | 97 +++++++++++++++ 17 files changed, 261 insertions(+), 16 deletions(-) rename test/cases/chunkFilename/expected/{1.async.css => 0.async.css} (100%) rename test/cases/filename-without-template/expected/{1.main.css => 0.main.css} (100%) create mode 100644 test/cases/nested/expected/main.js rename test/cases/shared-import/expected/{1.css => 0.css} (100%) create mode 100644 test/cases/simple-async-source-map/expected/0.css create mode 100644 test/cases/simple-async-source-map/expected/0.css.map delete mode 100644 test/cases/simple-async-source-map/expected/1.css delete mode 100644 test/cases/simple-async-source-map/expected/1.css.map create mode 100644 test/cases/simple-async/expected/0.css delete mode 100644 test/cases/simple-async/expected/1.css create mode 100644 test/cases/simple/expected/main.js diff --git a/.editorconfig b/.editorconfig index 9f89f364..903808f8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,3 +11,6 @@ trim_trailing_whitespace = true [*.md] insert_final_newline = true trim_trailing_whitespace = false + +[test/cases/**/*] +insert_final_newline = false diff --git a/src/index.js b/src/index.js index d9f6bf2d..dd8ed469 100644 --- a/src/index.js +++ b/src/index.js @@ -390,6 +390,36 @@ class MiniCssExtractPlugin { return source; } ); + + compilation.hooks.optimizeChunks.tap(pluginName, (chunks) => { + for (const chunk of chunks) { + chunk.modulesIterable.forEach((module) => { + // eslint-disable-next-line no-param-reassign + module.dependencies = module.dependencies.filter((dep) => { + const hasCssDep = + dep.module && + dep.module.dependencies.find( + (d) => d.module && d.module.type === MODULE_TYPE + ); + + if (hasCssDep) { + dep.disconnect(); + return false; + } + + return true; + }); + + if ( + module.type === MODULE_TYPE && + module.issuer && + module.issuer.buildMeta.extracted + ) { + chunk.removeModule(module.issuer); + } + }); + } + }); }); } diff --git a/src/loader.js b/src/loader.js index 619c48d3..37d28197 100644 --- a/src/loader.js +++ b/src/loader.js @@ -217,6 +217,11 @@ export function pitch(request) { ? hotLoader(result, { context: this.context, options, locals }) : result; + this._module.buildMeta = { + ...this._module.buildMeta, + extracted: !locals && !options.hmr, + }; + return callback(null, resultSource); }); } diff --git a/test/cases/chunkFilename/expected/1.async.css b/test/cases/chunkFilename/expected/0.async.css similarity index 100% rename from test/cases/chunkFilename/expected/1.async.css rename to test/cases/chunkFilename/expected/0.async.css diff --git a/test/cases/filename-without-template/expected/1.main.css b/test/cases/filename-without-template/expected/0.main.css similarity index 100% rename from test/cases/filename-without-template/expected/1.main.css rename to test/cases/filename-without-template/expected/0.main.css diff --git a/test/cases/nested/expected/main.js b/test/cases/nested/expected/main.js new file mode 100644 index 00000000..844818b5 --- /dev/null +++ b/test/cases/nested/expected/main.js @@ -0,0 +1,110 @@ +/******/ (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, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var _component__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); + + + + + +/***/ }), +/* 1 */, +/* 2 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); + + + +/***/ }) +/******/ ]); \ No newline at end of file diff --git a/test/cases/shared-import/expected/1.css b/test/cases/shared-import/expected/0.css similarity index 100% rename from test/cases/shared-import/expected/1.css rename to test/cases/shared-import/expected/0.css diff --git a/test/cases/simple-async-source-map/expected/0.css b/test/cases/simple-async-source-map/expected/0.css new file mode 100644 index 00000000..483851fe --- /dev/null +++ b/test/cases/simple-async-source-map/expected/0.css @@ -0,0 +1,6 @@ +.async { + background: blue; +} + + +/*# sourceMappingURL=0.css.map*/ \ No newline at end of file diff --git a/test/cases/simple-async-source-map/expected/0.css.map b/test/cases/simple-async-source-map/expected/0.css.map new file mode 100644 index 00000000..ddec27ed --- /dev/null +++ b/test/cases/simple-async-source-map/expected/0.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["webpack:///./async.css"],"names":[],"mappings":"AAAA;AACA;AACA","file":"0.css","sourcesContent":[".async {\n background: blue;\n}\n"],"sourceRoot":""} \ No newline at end of file diff --git a/test/cases/simple-async-source-map/expected/1.css b/test/cases/simple-async-source-map/expected/1.css deleted file mode 100644 index 18c06fae..00000000 --- a/test/cases/simple-async-source-map/expected/1.css +++ /dev/null @@ -1,6 +0,0 @@ -.in-async { - background: green; -} - - -/*# sourceMappingURL=1.css.map*/ \ No newline at end of file diff --git a/test/cases/simple-async-source-map/expected/1.css.map b/test/cases/simple-async-source-map/expected/1.css.map deleted file mode 100644 index 80ae7050..00000000 --- a/test/cases/simple-async-source-map/expected/1.css.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["webpack:///./in-async.css"],"names":[],"mappings":"AAAA;AACA;AACA","file":"1.css","sourcesContent":[".in-async {\n background: green;\n}\n"],"sourceRoot":""} \ No newline at end of file diff --git a/test/cases/simple-async-source-map/expected/2.css b/test/cases/simple-async-source-map/expected/2.css index 64cde675..cc80fa58 100644 --- a/test/cases/simple-async-source-map/expected/2.css +++ b/test/cases/simple-async-source-map/expected/2.css @@ -1,5 +1,5 @@ -.async { - background: blue; +.in-async { + background: green; } diff --git a/test/cases/simple-async-source-map/expected/2.css.map b/test/cases/simple-async-source-map/expected/2.css.map index f31c083f..806983fa 100644 --- a/test/cases/simple-async-source-map/expected/2.css.map +++ b/test/cases/simple-async-source-map/expected/2.css.map @@ -1 +1 @@ -{"version":3,"sources":["webpack:///./async.css"],"names":[],"mappings":"AAAA;AACA;AACA","file":"2.css","sourcesContent":[".async {\n background: blue;\n}\n"],"sourceRoot":""} \ No newline at end of file +{"version":3,"sources":["webpack:///./in-async.css"],"names":[],"mappings":"AAAA;AACA;AACA","file":"2.css","sourcesContent":[".in-async {\n background: green;\n}\n"],"sourceRoot":""} \ No newline at end of file diff --git a/test/cases/simple-async/expected/0.css b/test/cases/simple-async/expected/0.css new file mode 100644 index 00000000..67332816 --- /dev/null +++ b/test/cases/simple-async/expected/0.css @@ -0,0 +1,4 @@ +.async { + background: blue; +} + diff --git a/test/cases/simple-async/expected/1.css b/test/cases/simple-async/expected/1.css deleted file mode 100644 index 9dc882a1..00000000 --- a/test/cases/simple-async/expected/1.css +++ /dev/null @@ -1,4 +0,0 @@ -.in-async { - background: green; -} - diff --git a/test/cases/simple-async/expected/2.css b/test/cases/simple-async/expected/2.css index 67332816..9dc882a1 100644 --- a/test/cases/simple-async/expected/2.css +++ b/test/cases/simple-async/expected/2.css @@ -1,4 +1,4 @@ -.async { - background: blue; +.in-async { + background: green; } diff --git a/test/cases/simple/expected/main.js b/test/cases/simple/expected/main.js new file mode 100644 index 00000000..04cc387b --- /dev/null +++ b/test/cases/simple/expected/main.js @@ -0,0 +1,97 @@ +/******/ (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, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); + + + +/***/ }) +/******/ ]); \ No newline at end of file From 36c14568ad154b7eec85eab74b426dc5d157ca90 Mon Sep 17 00:00:00 2001 From: Ivan Rudoy Date: Sat, 1 Jun 2019 03:14:18 +1000 Subject: [PATCH 02/11] fix: use `for of` instead of `forEach` for `modulesIterable` --- src/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.js b/src/index.js index dd8ed469..774ac107 100644 --- a/src/index.js +++ b/src/index.js @@ -393,7 +393,7 @@ class MiniCssExtractPlugin { compilation.hooks.optimizeChunks.tap(pluginName, (chunks) => { for (const chunk of chunks) { - chunk.modulesIterable.forEach((module) => { + for (const module of chunk.modulesIterable) { // eslint-disable-next-line no-param-reassign module.dependencies = module.dependencies.filter((dep) => { const hasCssDep = @@ -417,7 +417,7 @@ class MiniCssExtractPlugin { ) { chunk.removeModule(module.issuer); } - }); + } } }); }); From 55c959a04d08b7d72e45477331ee8c5b095937df Mon Sep 17 00:00:00 2001 From: Ivan Rudoy Date: Sat, 1 Jun 2019 03:26:11 +1000 Subject: [PATCH 03/11] fix: add cjs test case --- .editorconfig | 2 +- test/cases/simple-require/expected/main.css | 2 + test/cases/simple-require/expected/main.js | 95 +++++++++++++++++++++ test/cases/simple-require/index.js | 1 + test/cases/simple-require/style.css | 1 + test/cases/simple-require/webpack.config.js | 18 ++++ 6 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 test/cases/simple-require/expected/main.css create mode 100644 test/cases/simple-require/expected/main.js create mode 100644 test/cases/simple-require/index.js create mode 100644 test/cases/simple-require/style.css create mode 100644 test/cases/simple-require/webpack.config.js diff --git a/.editorconfig b/.editorconfig index 903808f8..aa6d2b06 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,5 +12,5 @@ trim_trailing_whitespace = true insert_final_newline = true trim_trailing_whitespace = false -[test/cases/**/*] +[test/cases/**] insert_final_newline = false diff --git a/test/cases/simple-require/expected/main.css b/test/cases/simple-require/expected/main.css new file mode 100644 index 00000000..aea53e43 --- /dev/null +++ b/test/cases/simple-require/expected/main.css @@ -0,0 +1,2 @@ +body { background: red; } + diff --git a/test/cases/simple-require/expected/main.js b/test/cases/simple-require/expected/main.js new file mode 100644 index 00000000..3d12fef6 --- /dev/null +++ b/test/cases/simple-require/expected/main.js @@ -0,0 +1,95 @@ +/******/ (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, __webpack_require__) { + +__webpack_require__('./style.css'); + + +/***/ }) +/******/ ]); \ No newline at end of file diff --git a/test/cases/simple-require/index.js b/test/cases/simple-require/index.js new file mode 100644 index 00000000..7a44f4ff --- /dev/null +++ b/test/cases/simple-require/index.js @@ -0,0 +1 @@ +require('./style.css'); diff --git a/test/cases/simple-require/style.css b/test/cases/simple-require/style.css new file mode 100644 index 00000000..31fc5b8a --- /dev/null +++ b/test/cases/simple-require/style.css @@ -0,0 +1 @@ +body { background: red; } diff --git a/test/cases/simple-require/webpack.config.js b/test/cases/simple-require/webpack.config.js new file mode 100644 index 00000000..4e160f26 --- /dev/null +++ b/test/cases/simple-require/webpack.config.js @@ -0,0 +1,18 @@ +import Self from '../../../src'; + +module.exports = { + entry: './index.js', + module: { + rules: [ + { + test: /\.css$/, + use: [Self.loader, 'css-loader'], + }, + ], + }, + plugins: [ + new Self({ + filename: '[name].css', + }), + ], +}; From 218a135230078fd7b2468b320021ac6fb1d16622 Mon Sep 17 00:00:00 2001 From: Ivan Rudoy Date: Mon, 3 Jun 2019 18:58:00 +1000 Subject: [PATCH 04/11] test: add manual stress test --- .eslintignore | 2 + package.json | 1 + test/stress/.gitignore | 2 + test/stress/build.js | 75 +++++++++++++++++++++++++++++++++++ test/stress/src/index.js | 1 + test/stress/webpack.config.js | 30 ++++++++++++++ 6 files changed, 111 insertions(+) create mode 100644 test/stress/.gitignore create mode 100644 test/stress/build.js create mode 100644 test/stress/src/index.js create mode 100644 test/stress/webpack.config.js diff --git a/.eslintignore b/.eslintignore index 8477d2ab..9c3ba619 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,3 +4,5 @@ /test/fixtures /test/cases/*/expected /test/js +/test/stress/dist +/test/stress/src/tmp diff --git a/package.json b/package.json index 01a2cd4e..6003c725 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "test:watch": "cross-env NODE_ENV=test jest --watch", "test:coverage": "cross-env NODE_ENV=test jest --collectCoverageFrom=\"src/**/*.js\" --coverage", "test:manual": "npm run build && webpack-dev-server test/manual/src/index.js --open --config test/manual/webpack.config.js", + "test:stress": "node --max-old-space-size=8192 test/stress/build.js", "pretest": "npm run lint", "test": "cross-env NODE_ENV=test npm run test:coverage", "defaults": "webpack-defaults" diff --git a/test/stress/.gitignore b/test/stress/.gitignore new file mode 100644 index 00000000..4943e34a --- /dev/null +++ b/test/stress/.gitignore @@ -0,0 +1,2 @@ +dist +src/tmp diff --git a/test/stress/build.js b/test/stress/build.js new file mode 100644 index 00000000..2ca5cbb5 --- /dev/null +++ b/test/stress/build.js @@ -0,0 +1,75 @@ +/* eslint-disable no-console */ + +const pathlib = require('path'); +const fs = require('fs'); +const crypto = require('crypto'); + +const del = require('del'); + +const webpack = require('webpack'); + +const config = require('./webpack.config'); + +const randomId = () => crypto.randomBytes(16).toString('hex'); +const createModule = (deps = []) => { + const id = randomId(); + const cssPath = pathlib.resolve(__dirname, `src/tmp/${id}.css`); + const jsPath = pathlib.resolve(__dirname, `src/tmp/${id}.js`); + const cssContent = `.c-${id} { display: block; }`; + const jsContent = [...deps.filter(Boolean), cssPath] + .map((p) => `import '${p}';`) + .join('\n'); + fs.writeFileSync(cssPath, cssContent); + fs.writeFileSync(jsPath, jsContent); + return jsPath; +}; + +const distPath = pathlib.resolve(__dirname, 'dist'); +const tmpPath = pathlib.resolve(__dirname, 'src/tmp'); + +del.sync(distPath); +del.sync(tmpPath); +fs.mkdirSync(tmpPath); + +const SIZE = 100; +const DEPTH = 80; + +console.log(`Preparing ${SIZE}x${DEPTH} data...`); + +const indexSrc = Array.from({ length: SIZE }) + .map(() => + Array.from({ length: DEPTH }).reduce((prev) => createModule([prev]), null) + ) + .map((p) => `import '${p}';`) + .join('\n'); + +fs.writeFileSync(pathlib.resolve(__dirname, `src/tmp/index.js`), indexSrc); + +console.log(`Running Webpack...`); + +webpack(config).run((err, stats) => { + if (err) { + console.error(err); + process.exit(1); + } + console.log( + stats.toString({ + all: false, + colors: true, + builtAt: true, + env: true, + hash: true, + timings: true, + version: true, + modules: true, + maxModules: 0, + errors: true, + errorDetails: true, + warnings: true, + moduleTrace: true, + assets: true, + entrypoints: true, + performance: true, + }) + ); +}); diff --git a/test/stress/src/index.js b/test/stress/src/index.js new file mode 100644 index 00000000..78984d04 --- /dev/null +++ b/test/stress/src/index.js @@ -0,0 +1 @@ +import './tmp/index.js'; // eslint-disable-line import/no-unresolved, import/extensions diff --git a/test/stress/webpack.config.js b/test/stress/webpack.config.js new file mode 100644 index 00000000..f53b7b01 --- /dev/null +++ b/test/stress/webpack.config.js @@ -0,0 +1,30 @@ +const pathlib = require('path'); + +const Self = require('../../'); + +const config = (mode) => { + return { + mode, + name: mode, + entry: pathlib.resolve(__dirname, 'src/index.js'), + output: { + filename: `${mode}.js`, + path: pathlib.resolve(__dirname, 'dist'), + }, + module: { + rules: [ + { + test: /\.css$/, + use: [Self.loader, 'css-loader'], + }, + ], + }, + plugins: [ + new Self({ + filename: `[name]-${mode}.css`, + }), + ], + }; +}; + +module.exports = [config('development'), config('production')]; From bec4a2be41cb335c6d63f55135bb5fd8e0c97bee Mon Sep 17 00:00:00 2001 From: Ivan Rudoy Date: Mon, 3 Jun 2019 19:56:01 +1000 Subject: [PATCH 05/11] fix: avoid using issuer --- src/index.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/index.js b/src/index.js index 774ac107..255cc129 100644 --- a/src/index.js +++ b/src/index.js @@ -410,12 +410,12 @@ class MiniCssExtractPlugin { return true; }); - if ( - module.type === MODULE_TYPE && - module.issuer && - module.issuer.buildMeta.extracted - ) { - chunk.removeModule(module.issuer); + if (module.type === MODULE_TYPE && module.reasons) { + for (const reason of module.reasons) { + if (reason.module && reason.module.buildMeta.extracted) { + chunk.removeModule(reason.module); + } + } } } } From 1a26dcabc40148295aebd17183cfb1aef3d9067b Mon Sep 17 00:00:00 2001 From: Ivan Rudoy Date: Wed, 31 Jul 2019 19:32:40 +1000 Subject: [PATCH 06/11] fix: skip cjs when removing empty modules, refactor --- src/index.js | 58 +++++++++++++++------- test/cases/simple-require/expected/main.js | 8 ++- 2 files changed, 46 insertions(+), 20 deletions(-) diff --git a/src/index.js b/src/index.js index 255cc129..42b5e603 100644 --- a/src/index.js +++ b/src/index.js @@ -24,6 +24,15 @@ const REGEXP_NAME = /\[name\]/i; const REGEXP_PLACEHOLDERS = /\[(name|id|chunkhash)\]/g; const DEFAULT_FILENAME = '[name].css'; +class SetMap extends Map { + set(key, value) { + return super.set( + key, + this.has(key) ? this.get(key).add(value) : new Set([value]) + ); + } +} + class CssDependencyTemplate { apply() {} } @@ -392,33 +401,44 @@ class MiniCssExtractPlugin { ); compilation.hooks.optimizeChunks.tap(pluginName, (chunks) => { + const toRemoveMap = new SetMap(); + for (const chunk of chunks) { for (const module of chunk.modulesIterable) { - // eslint-disable-next-line no-param-reassign - module.dependencies = module.dependencies.filter((dep) => { - const hasCssDep = - dep.module && - dep.module.dependencies.find( - (d) => d.module && d.module.type === MODULE_TYPE - ); - - if (hasCssDep) { - dep.disconnect(); - return false; - } - - return true; - }); + if (module.type === MODULE_TYPE) { + for (const cssModuleReason of module.reasons) { + let isCjs = false; + + for (const reason of cssModuleReason.module.reasons) { + isCjs = /^cjs/.test(reason.dependency.type); + if (!isCjs) + toRemoveMap.set( + reason.module || cssModuleReason.module, + reason.dependency + ); + } - if (module.type === MODULE_TYPE && module.reasons) { - for (const reason of module.reasons) { - if (reason.module && reason.module.buildMeta.extracted) { - chunk.removeModule(reason.module); + if ( + !isCjs && + cssModuleReason.module && + cssModuleReason.module.buildMeta.extracted + ) { + chunk.removeModule(cssModuleReason.module); } } } } } + + for (const [module, set] of toRemoveMap) { + module.dependencies = module.dependencies.filter((d) => { + if (set.has(d)) { + d.disconnect(); + return false; + } + return true; + }); + } }); }); } diff --git a/test/cases/simple-require/expected/main.js b/test/cases/simple-require/expected/main.js index 3d12fef6..e32a34ee 100644 --- a/test/cases/simple-require/expected/main.js +++ b/test/cases/simple-require/expected/main.js @@ -88,8 +88,14 @@ /* 0 */ /***/ (function(module, exports, __webpack_require__) { -__webpack_require__('./style.css'); +__webpack_require__(1); +/***/ }), +/* 1 */ +/***/ (function(module, exports, __webpack_require__) { + +// extracted by mini-css-extract-plugin + /***/ }) /******/ ]); \ No newline at end of file From ba37daad4411dd5850b9551827fc36447a42e799 Mon Sep 17 00:00:00 2001 From: Ivan Rudoy Date: Thu, 1 Aug 2019 04:12:56 +1000 Subject: [PATCH 07/11] fix: add test/stress to .prettierignore --- .prettierignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.prettierignore b/.prettierignore index 41ff7724..750b7c06 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,4 +4,6 @@ /test/fixtures /test/cases/*/expected /test/js +/test/stress/dist +/test/stress/src/tmp CHANGELOG.md From 634784e75b9f292281e356047daf4c9fcc5b6479 Mon Sep 17 00:00:00 2001 From: Ivan Rudoy Date: Thu, 1 Aug 2019 04:19:51 +1000 Subject: [PATCH 08/11] fix: css formatting --- test/cases/simple-require/expected/main.css | 4 +++- test/cases/simple-require/style.css | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/test/cases/simple-require/expected/main.css b/test/cases/simple-require/expected/main.css index aea53e43..cebc5c1c 100644 --- a/test/cases/simple-require/expected/main.css +++ b/test/cases/simple-require/expected/main.css @@ -1,2 +1,4 @@ -body { background: red; } +body { + background: red; +} diff --git a/test/cases/simple-require/style.css b/test/cases/simple-require/style.css index 31fc5b8a..67ce83e4 100644 --- a/test/cases/simple-require/style.css +++ b/test/cases/simple-require/style.css @@ -1 +1,3 @@ -body { background: red; } +body { + background: red; +} From ee6be12346bca87f2286d4840a95ff7f0b42a982 Mon Sep 17 00:00:00 2001 From: Ivan Rudoy Date: Thu, 3 Oct 2019 19:32:14 +1000 Subject: [PATCH 09/11] fix: improve SetMap --- src/index.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/index.js b/src/index.js index 42b5e603..882c55f3 100644 --- a/src/index.js +++ b/src/index.js @@ -25,11 +25,14 @@ const REGEXP_PLACEHOLDERS = /\[(name|id|chunkhash)\]/g; const DEFAULT_FILENAME = '[name].css'; class SetMap extends Map { - set(key, value) { - return super.set( - key, - this.has(key) ? this.get(key).add(value) : new Set([value]) - ); + add(key, value) { + const set = this.get(key); + // eslint-disable-next-line no-undefined + if (set === undefined) { + super.set(key, new Set([value])); + } else { + set.add(value); + } } } @@ -412,7 +415,7 @@ class MiniCssExtractPlugin { for (const reason of cssModuleReason.module.reasons) { isCjs = /^cjs/.test(reason.dependency.type); if (!isCjs) - toRemoveMap.set( + toRemoveMap.add( reason.module || cssModuleReason.module, reason.dependency ); From c70f4e35b86cfc864d657f4a522005d84695b90c Mon Sep 17 00:00:00 2001 From: Ivan Rudoy Date: Fri, 4 Oct 2019 00:10:27 +1000 Subject: [PATCH 10/11] fix: check CommonJS --- src/index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/index.js b/src/index.js index 882c55f3..bc60c32d 100644 --- a/src/index.js +++ b/src/index.js @@ -413,12 +413,14 @@ class MiniCssExtractPlugin { let isCjs = false; for (const reason of cssModuleReason.module.reasons) { - isCjs = /^cjs/.test(reason.dependency.type); - if (!isCjs) + if (/^cjs/.test(reason.dependency.type)) { + isCjs = true; + } else { toRemoveMap.add( reason.module || cssModuleReason.module, reason.dependency ); + } } if ( From 396cba7804830141f8c7c5d85ca0b3d3baaa4419 Mon Sep 17 00:00:00 2001 From: Ivan Rudoy Date: Fri, 4 Oct 2019 00:45:39 +1000 Subject: [PATCH 11/11] fix: add allowlist for supported dependencies --- src/index.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/index.js b/src/index.js index bc60c32d..490da4f9 100644 --- a/src/index.js +++ b/src/index.js @@ -24,6 +24,12 @@ const REGEXP_NAME = /\[name\]/i; const REGEXP_PLACEHOLDERS = /\[(name|id|chunkhash)\]/g; const DEFAULT_FILENAME = '[name].css'; +const EMPTY_MODULES_CLEANUP_DEP_TYPES = new Set([ + 'harmony side effect evaluation', + 'import()', + 'single entry', +]); + class SetMap extends Map { add(key, value) { const set = this.get(key); @@ -410,21 +416,23 @@ class MiniCssExtractPlugin { for (const module of chunk.modulesIterable) { if (module.type === MODULE_TYPE) { for (const cssModuleReason of module.reasons) { - let isCjs = false; + let hasUnsupportedDependency = false; for (const reason of cssModuleReason.module.reasons) { - if (/^cjs/.test(reason.dependency.type)) { - isCjs = true; - } else { + if ( + EMPTY_MODULES_CLEANUP_DEP_TYPES.has(reason.dependency.type) + ) { toRemoveMap.add( reason.module || cssModuleReason.module, reason.dependency ); + } else { + hasUnsupportedDependency = true; } } if ( - !isCjs && + !hasUnsupportedDependency && cssModuleReason.module && cssModuleReason.module.buildMeta.extracted ) {