diff --git a/.changeset/010-concatenate-module-wrapping.md b/.changeset/010-concatenate-module-wrapping.md new file mode 100644 index 00000000000..3865e913017 --- /dev/null +++ b/.changeset/010-concatenate-module-wrapping.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Wrap concatenated modules in lazy `__webpack_require__.cw` accessors and inline `require()`, keeping a wrapped body's names and side effects intact. diff --git a/.changeset/011-performance-duplicate-packages.md b/.changeset/011-performance-duplicate-packages.md new file mode 100644 index 00000000000..2e50e628ac4 --- /dev/null +++ b/.changeset/011-performance-duplicate-packages.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Add `performance.duplicatePackages` to report packages included more than once. diff --git a/.changeset/012-rule-set-description-relative-path.md b/.changeset/012-rule-set-description-relative-path.md new file mode 100644 index 00000000000..5affce9a8b0 --- /dev/null +++ b/.changeset/012-rule-set-description-relative-path.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Add the `descriptionRelativePath` module rule condition. diff --git a/.changeset/012-runtime-in-large-chunk-hint.md b/.changeset/012-runtime-in-large-chunk-hint.md new file mode 100644 index 00000000000..3844e4ee4bf --- /dev/null +++ b/.changeset/012-runtime-in-large-chunk-hint.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Recommend `optimization.runtimeChunk` when a large entrypoint carries the runtime. diff --git a/.changeset/013-optimization-bailout-analysis.md b/.changeset/013-optimization-bailout-analysis.md new file mode 100644 index 00000000000..2e9a50a8c2a --- /dev/null +++ b/.changeset/013-optimization-bailout-analysis.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Report inner-graph, AMD and bare `module` bailouts in `optimizationBailout`. diff --git a/.changeset/013-performance-circular-dependencies.md b/.changeset/013-performance-circular-dependencies.md new file mode 100644 index 00000000000..baeaae2166b --- /dev/null +++ b/.changeset/013-performance-circular-dependencies.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Add `performance.circularDependencies` using the export-inlining cycle scan. diff --git a/.changeset/013-performance-hints-config-and-output.md b/.changeset/013-performance-hints-config-and-output.md new file mode 100644 index 00000000000..d56c9a52353 --- /dev/null +++ b/.changeset/013-performance-hints-config-and-output.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Add `performance.osDependentRules`, `performance.cacheEffectiveness`, `performance.duplicateModules` and `performance.redundantDynamicImports`, and name the largest modules in an oversized asset. diff --git a/.changeset/013-performance-hints-stats.md b/.changeset/013-performance-hints-stats.md new file mode 100644 index 00000000000..ee1c85dc9a2 --- /dev/null +++ b/.changeset/013-performance-hints-stats.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Add `performance.hints: "stats"` to report hints in stats only. diff --git a/.changeset/013-performance-optimization-hints.md b/.changeset/013-performance-optimization-hints.md new file mode 100644 index 00000000000..fa3bd64399e --- /dev/null +++ b/.changeset/013-performance-optimization-hints.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Add performance hints reporting why webpack skipped an optimization. diff --git a/.changeset/013-performance-unused-reexports.md b/.changeset/013-performance-unused-reexports.md new file mode 100644 index 00000000000..ee4a064c10e --- /dev/null +++ b/.changeset/013-performance-unused-reexports.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Add `performance.unusedReexports` to report modules a barrel drags in unused. diff --git a/.changeset/013-unused-rule-warning.md b/.changeset/013-unused-rule-warning.md new file mode 100644 index 00000000000..65e0bbcb6fd --- /dev/null +++ b/.changeset/013-unused-rule-warning.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Add `performance.unusedRules` to report rules that never matched a module. diff --git a/.changeset/014-externals-side-effects.md b/.changeset/014-externals-side-effects.md new file mode 100644 index 00000000000..6ef56d3543e --- /dev/null +++ b/.changeset/014-externals-side-effects.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Allow marking externals as side-effect-free with a `sideEffects` flag. diff --git a/.changeset/014-hint-report-order.md b/.changeset/014-hint-report-order.md new file mode 100644 index 00000000000..d24b6565c55 --- /dev/null +++ b/.changeset/014-hint-report-order.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Report performance hints in a stable order when entries tie. diff --git a/.changeset/015-externals-node-modules-preset.md b/.changeset/015-externals-node-modules-preset.md new file mode 100644 index 00000000000..d5f90283f25 --- /dev/null +++ b/.changeset/015-externals-node-modules-preset.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Add the `externalsPresets.nodeModules` preset with an `allowlist` option to externalize installed packages, replacing the `webpack-node-externals` plugin. diff --git a/.changeset/015-umd-sap-ui-define.md b/.changeset/015-umd-sap-ui-define.md new file mode 100644 index 00000000000..694f38d327d --- /dev/null +++ b/.changeset/015-umd-sap-ui-define.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Add `output.library.umdSapUiDefine` to emit a `sap.ui.define` branch in UMD. diff --git a/.changeset/016-memory-cache-etag-by-value.md b/.changeset/016-memory-cache-etag-by-value.md new file mode 100644 index 00000000000..3e73f1ab3d2 --- /dev/null +++ b/.changeset/016-memory-cache-etag-by-value.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Let a memory-cache etag mismatch fall through to the file cache instead of reporting a miss. diff --git a/.changeset/017-css-custom-media-constants.md b/.changeset/017-css-custom-media-constants.md new file mode 100644 index 00000000000..03c8ad69648 --- /dev/null +++ b/.changeset/017-css-custom-media-constants.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Resolve `@custom-media` values that are `true` / `false` or name another custom media. diff --git a/.changeset/018-css-server-styles.md b/.changeset/018-css-server-styles.md new file mode 100644 index 00000000000..d8b27910b96 --- /dev/null +++ b/.changeset/018-css-server-styles.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Add the `__webpack_css_server_styles__` module variable to read the CSS collected while rendering without a DOM, and keep that CSS in the order the styles were applied. diff --git a/.changeset/018-html-head-hmr-patch.md b/.changeset/018-html-head-hmr-patch.md new file mode 100644 index 00000000000..1fc2ac38c79 --- /dev/null +++ b/.changeset/018-html-head-hmr-patch.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Patch the HTML `` in place on hot update instead of forcing a full reload, including when a ` + + diff --git a/examples/asset-svg-data-uri/template.md b/examples/asset-svg-data-uri/template.md new file mode 100644 index 00000000000..03002aeaea9 --- /dev/null +++ b/examples/asset-svg-data-uri/template.md @@ -0,0 +1,29 @@ +This example shows the usage of the asset module type with asset generator options customization. + +Files can be imported similar to other modules without file-loader or url-loader. + +# example.js + +```javascript +_{{example.js}}_ +``` + +# webpack.config.js + +```javascript +_{{webpack.config.js}}_ +``` + +# js/output.js + +```javascript +_{{dist/output.js}}_ +``` + +# Info + +## webpack output + +``` +_{{stdout}}_ +``` diff --git a/examples/asset-svg-data-uri/webpack.config.js b/examples/asset-svg-data-uri/webpack.config.js new file mode 100644 index 00000000000..183b23a7017 --- /dev/null +++ b/examples/asset-svg-data-uri/webpack.config.js @@ -0,0 +1,37 @@ +"use strict"; + +const svgToMiniDataURI = require("mini-svg-data-uri"); + +/** @type {import("webpack").Configuration} */ +const config = { + output: { + assetModuleFilename: "images/[hash][ext]" + }, + module: { + rules: [ + { + test: /\.(png|jpg)$/, + type: "asset" + }, + { + test: /\.svg$/, + type: "asset", + generator: { + /** + * @param {string | Buffer} content the content + * @returns {string} data URI + */ + dataUrl: (content) => { + if (typeof content !== "string") { + content = content.toString(); + } + + return svgToMiniDataURI(content); + } + } + } + ] + } +}; + +module.exports = config; diff --git a/examples/asset/README.md b/examples/asset/README.md new file mode 100644 index 00000000000..403d9f30bec --- /dev/null +++ b/examples/asset/README.md @@ -0,0 +1,488 @@ +This is a very simple example that shows the usage of the asset module type. + +Files can be imported like other modules without file-loader. + +# example.js + +```javascript +// There are different ways to use files: + +// 1. Using `import something from "./file.ext";` + +// return URLs or Data URL, depends on your configuration +import png from "./images/file.png"; +import jpg from "./images/file.jpg"; +import svg from "./images/file.svg"; + +// 2. Using `import something from "./file.ext"; with { type: "text" }` or `import something from "./file.ext"; with { type: "bytes" }` +// You don't need extra options in your configuration for these imports, they work out of the box + +// returns the content as text +import text from "./content/file.text" with { type: "text" }; + +// returns the content as `Uint8Array` +import bytes from "./content/bytes.svg" with { type: "bytes" }; + +// 3. Using `new URL("./file.ext", import.meta.url);` +// You don't need extra options in your configuration for `new URL(...)` construction, they work out of the box +const url = new URL("./images/url.svg", import.meta.url); + +const container = document.createElement("div"); + +Object.assign(container.style, { + display: "flex", + flexWrap: "wrap", + justifyContent: "center" +}); +document.body.appendChild(container); + +function createImageElement(div, data) { + const img = document.createElement("img"); + img.setAttribute("src", data); + img.setAttribute("width", "150"); + div.appendChild(img); + + container.appendChild(div); +} + +function createTextElement(div, data) { + const context = document.createElement("div"); + context.textContent = data; + div.appendChild(context); + + container.appendChild(div); +} + +function createBlobElement(div, data) { + const blob = new Blob([data], { type: 'image/svg+xml' }); + const blobUrl = URL.createObjectURL(blob); + + const img = document.createElement("img"); + + img.setAttribute("src", blobUrl); + img.setAttribute("width", "150"); + div.appendChild(img); + + container.appendChild(div); + + img.addEventListener( + 'load', + () => { URL.revokeObjectURL(blobUrl) }, + { once: true } + ); +} + +const files = [ + { + title: "import png from \"./images/file.png\";", + data: png, + render: createImageElement, + }, + { + title: "import jpg from \"./images/file.jpg\";", + data: jpg, + render: createImageElement, + }, + { + title: "import svg from \"./images/file.svg\";", + data: svg, + render: createImageElement, + }, + { + title: "import text from \"./content/file.text\" with { type: \"text\" };", + data: text, + render: createTextElement, + }, + { + title: "import bytes from \"./content/file.text\" with { type: \"bytes\" };", + data: bytes, + render: createBlobElement, + }, + { + title: "new URL(\"./url.svg\", import.meta.url);", + data: url, + render: createImageElement, + }, +]; + + +function render(title, data, fn) { + const div = document.createElement("div"); + div.style.textAlign = "center"; + div.style.width = "50%"; + + const h2 = document.createElement("h2"); + h2.textContent = title; + div.appendChild(h2); + + fn(div, data) +} + +files.forEach(item => { + render(item.title, item.data, item.render); +}); +``` + +# webpack.config.js + +```javascript +"use strict"; + +/** @type {import("webpack").Configuration} */ +const config = { + output: { + assetModuleFilename: "images/[hash][ext]" + }, + module: { + rules: [ + { + test: /file\.(png|jpg|svg)$/, + type: "asset" + } + ] + } +}; + +module.exports = config; +``` + +# js/output.js + +```javascript +/******/ (() => { // webpackBootstrap +/******/ "use strict"; +/******/ var __webpack_modules__ = ([ +/* 0 */, +/* 1 */ +/*!*************************!*\ + !*** ./images/file.png ***! + \*************************/ +/*! default exports */ +/*! export default [not provided] [no usage info] [missing usage info prevents renaming] */ +/*! other exports [not provided] [no usage info] */ +/*! runtime requirements: __webpack_require__.p, module, __webpack_require__.* */ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +module.exports = __webpack_require__.p + "images/89a353e9c515885abd8e.png"; + +/***/ }), +/* 2 */ +/*!*************************!*\ + !*** ./images/file.jpg ***! + \*************************/ +/*! default exports */ +/*! export default [not provided] [no usage info] [missing usage info prevents renaming] */ +/*! other exports [not provided] [no usage info] */ +/*! runtime requirements: module */ +/***/ ((module) => { + +module.exports = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAASABIAA...4CD/9M//Z"; + +/***/ }), +/* 3 */ +/*!*************************!*\ + !*** ./images/file.svg ***! + \*************************/ +/*! default exports */ +/*! export default [not provided] [no usage info] [missing usage info prevents renaming] */ +/*! other exports [not provided] [no usage info] */ +/*! runtime requirements: module */ +/***/ ((module) => { + +module.exports = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDo...vc3ZnPgo="; + +/***/ }), +/* 4 */ +/*!************************!*\ + !*** ./images/url.svg ***! + \************************/ +/*! default exports */ +/*! exports [not provided] [no usage info] */ +/*! runtime requirements: __webpack_require__.p, module, __webpack_require__.* */ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +module.exports = __webpack_require__.p + "images/afc10c70ed4ce2b33593.svg"; + +/***/ }), +/* 5 */ +/*!***************************!*\ + !*** ./content/file.text ***! + \***************************/ +/*! default exports */ +/*! export default [not provided] [no usage info] [missing usage info prevents renaming] */ +/*! other exports [not provided] [no usage info] */ +/*! runtime requirements: module */ +/***/ ((module) => { + +module.exports = "a Ā 𐀀 文 🦄 Text\n"; + +/***/ }), +/* 6 */ +/*!***************************!*\ + !*** ./content/bytes.svg ***! + \***************************/ +/*! default exports */ +/*! export default [not provided] [no usage info] [missing usage info prevents renaming] */ +/*! other exports [not provided] [no usage info] */ +/*! runtime requirements: __webpack_require__.*, __webpack_require__.tb, module */ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +module.exports = __webpack_require__.tb("PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2MDAgNjAwIj48dGl0bGU+aWNvbi1zcXVhcmUtc21hbGw8L3RpdGxlPjxwYXRoIGZpbGw9IiNGRkYiIGQ9Ik0zMDAgLjFMNTY1IDE1MHYyOTkuOUwzMDAgNTk5LjggMzUgNDQ5LjlWMTUweiIvPjxwYXRoIGZpbGw9IiM4RUQ2RkIiIGQ9Ik01MTcuNyA0MzkuNUwzMDguOCA1NTcuOHYtOTJMNDM5IDM5NC4xbDc4LjcgNDUuNHptMTQuMy0xMi45VjE3OS40bC03Ni40IDQ0LjF2MTU5bDc2LjQgNDQuMXpNODEuNSA0MzkuNWwyMDguOSAxMTguMnYtOTJsLTEzMC4yLTcxLjYtNzguNyA0NS40em0tMTQuMy0xMi45VjE3OS40bDc2LjQgNDQuMXYxNTlsLTc2LjQgNDQuMXptOC45LTI2My4yTDI5MC40IDQyLjJ2ODlsLTEzNy4zIDc1LjUtMS4xLjYtNzUuOS00My45em00NDYuOSAwTDMwOC44IDQyLjJ2ODlMNDQ2IDIwNi44bDEuMS42IDc1LjktNDR6Ii8+PHBhdGggZmlsbD0iIzFDNzhDMCIgZD0iTTI5MC40IDQ0NC44TDE2MiAzNzQuMVYyMzQuMmwxMjguNCA3NC4xdjEzNi41em0xOC40IDBsMTI4LjQtNzAuNnYtMTQwbC0xMjguNCA3NC4xdjEzNi41ek0yOTkuNiAzMDN6bS0xMjktODVsMTI5LTcwLjlMNDI4LjUgMjE4bC0xMjguOSA3NC40LTEyOS03NC40eiIvPjwvc3ZnPgo="); + +/***/ }) +/******/ ]); +``` + +
/* webpack runtime code */ + +``` js +/************************************************************************/ +/******/ // The module cache +/******/ const __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ const 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; +/******/ } +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/make namespace object */ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = (exports) => { +/******/ if(Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/******/ /* webpack/runtime/to binary */ +/******/ (() => { +/******/ // define to binary helper +/******/ const toImmutableBytes = (value) => { +/******/ let {buffer} = value; +/******/ const throwErr = () => { +/******/ throw new TypeError('ArrayBuffer is immutable'); +/******/ }; +/******/ Object.defineProperties(buffer, { immutable: { value: true }, resize: { value: throwErr }, transfer: { value: throwErr }, transferToFixedLength: { value: throwErr } }); +/******/ Object.freeze(buffer); +/******/ return value; +/******/ } +/******/ __webpack_require__.tb = (() => { +/******/ const table = new Uint8Array(128); +/******/ for (var i = 0; i < 64; i++) table[i < 26 ? i + 65 : i < 52 ? i + 71 : i < 62 ? i - 4 : i * 4 - 205] = i; +/******/ return (base64) => { +/******/ const n = base64.length, bytes = new Uint8Array((n - (base64[n - 1] == '=') - (base64[n - 2] == '=')) * 3 / 4 | 0); +/******/ for (var i = 0, j = 0; i < n;) { +/******/ const c0 = table[base64.charCodeAt(i++)], c1 = table[base64.charCodeAt(i++)]; +/******/ const c2 = table[base64.charCodeAt(i++)], c3 = table[base64.charCodeAt(i++)]; +/******/ bytes[j++] = (c0 << 2) | (c1 >> 4); +/******/ bytes[j++] = (c1 << 4) | (c2 >> 2); +/******/ bytes[j++] = (c2 << 6) | c3; +/******/ } +/******/ return toImmutableBytes(bytes) +/******/ } +/******/ })(); +/******/ })(); +/******/ +/******/ /* webpack/runtime/publicPath */ +/******/ __webpack_require__.p = "dist/"; +/******/ +/******/ /* webpack/runtime/jsonp chunk loading */ +/******/ (() => { +/******/ __webpack_require__.b = (typeof document !== 'undefined' && document.baseURI) || self.location.href; +/******/ +/******/ // no installed chunks +/******/ +/******/ // no chunk on demand loading +/******/ +/******/ // no prefetching +/******/ +/******/ // no preloaded +/******/ +/******/ // no HMR +/******/ +/******/ // no HMR manifest +/******/ +/******/ // no on chunks loaded +/******/ +/******/ // no jsonp function +/******/ })(); +/******/ +/************************************************************************/ +``` + +
+ +``` js +let __webpack_exports__ = {}; +// This entry needs to be wrapped in an IIFE because it needs to be isolated against other modules in the chunk. +(() => { +/*!********************!*\ + !*** ./example.js ***! + \********************/ +/*! namespace exports */ +/*! exports [not provided] [no usage info] */ +/*! runtime requirements: __webpack_require__, __webpack_require__.b, __webpack_require__.r, __webpack_exports__, __webpack_require__.* */ +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var _images_file_png__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./images/file.png */ 1); +/* harmony import */ var _images_file_jpg__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./images/file.jpg */ 2); +/* harmony import */ var _images_file_svg__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./images/file.svg */ 3); +/* harmony import */ var _content_file_text__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./content/file.text */ 5); +/* harmony import */ var _content_bytes_svg__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./content/bytes.svg */ 6); +// There are different ways to use files: + +// 1. Using `import something from "./file.ext";` + +// return URLs or Data URL, depends on your configuration + + + + +// 2. Using `import something from "./file.ext"; with { type: "text" }` or `import something from "./file.ext"; with { type: "bytes" }` +// You don't need extra options in your configuration for these imports, they work out of the box + +// returns the content as text + + +// returns the content as `Uint8Array` + + +// 3. Using `new URL("./file.ext", import.meta.url);` +// You don't need extra options in your configuration for `new URL(...)` construction, they work out of the box +const url = new URL(/* asset import */ __webpack_require__(/*! ./images/url.svg */ 4), __webpack_require__.b); + +const container = document.createElement("div"); + +Object.assign(container.style, { + display: "flex", + flexWrap: "wrap", + justifyContent: "center" +}); +document.body.appendChild(container); + +function createImageElement(div, data) { + const img = document.createElement("img"); + img.setAttribute("src", data); + img.setAttribute("width", "150"); + div.appendChild(img); + + container.appendChild(div); +} + +function createTextElement(div, data) { + const context = document.createElement("div"); + context.textContent = data; + div.appendChild(context); + + container.appendChild(div); +} + +function createBlobElement(div, data) { + const blob = new Blob([data], { type: 'image/svg+xml' }); + const blobUrl = URL.createObjectURL(blob); + + const img = document.createElement("img"); + + img.setAttribute("src", blobUrl); + img.setAttribute("width", "150"); + div.appendChild(img); + + container.appendChild(div); + + img.addEventListener( + 'load', + () => { URL.revokeObjectURL(blobUrl) }, + { once: true } + ); +} + +const files = [ + { + title: "import png from \"./images/file.png\";", + data: _images_file_png__WEBPACK_IMPORTED_MODULE_0__, + render: createImageElement, + }, + { + title: "import jpg from \"./images/file.jpg\";", + data: _images_file_jpg__WEBPACK_IMPORTED_MODULE_1__, + render: createImageElement, + }, + { + title: "import svg from \"./images/file.svg\";", + data: _images_file_svg__WEBPACK_IMPORTED_MODULE_2__, + render: createImageElement, + }, + { + title: "import text from \"./content/file.text\" with { type: \"text\" };", + data: _content_file_text__WEBPACK_IMPORTED_MODULE_3__, + render: createTextElement, + }, + { + title: "import bytes from \"./content/file.text\" with { type: \"bytes\" };", + data: _content_bytes_svg__WEBPACK_IMPORTED_MODULE_4__, + render: createBlobElement, + }, + { + title: "new URL(\"./url.svg\", import.meta.url);", + data: url, + render: createImageElement, + }, +]; + + +function render(title, data, fn) { + const div = document.createElement("div"); + div.style.textAlign = "center"; + div.style.width = "50%"; + + const h2 = document.createElement("h2"); + h2.textContent = title; + div.appendChild(h2); + + fn(div, data) +} + +files.forEach(item => { + render(item.title, item.data, item.render); +}); + +})(); + +/******/ })() +; +``` + +# Info + +## webpack output + +``` +asset output.js 19.6 KiB [emitted] (name: main) +asset images/89a353e9c515885abd8e.png 14.6 KiB [emitted] [immutable] [from: images/file.png] (auxiliary name: main) +asset images/afc10c70ed4ce2b33593.svg 656 bytes [emitted] [immutable] [from: images/url.svg] (auxiliary name: main) +chunk (runtime: main) output.js (main) 12.4 KiB (javascript) 15.2 KiB (asset) 1.55 KiB (runtime) [entry] [rendered] + > ./example.js main + dependent modules 9.59 KiB (javascript) 15.2 KiB (asset) [dependent] 6 modules + runtime modules 1.55 KiB 4 modules + ./example.js 2.85 KiB [built] [code generated] + [no exports] + [used exports unknown] + entry ./example.js main +webpack X.X.X compiled successfully +``` diff --git a/examples/asset/build.js b/examples/asset/build.js new file mode 100644 index 00000000000..2e93fe5a3e1 --- /dev/null +++ b/examples/asset/build.js @@ -0,0 +1 @@ +require("../build-common"); diff --git a/examples/asset/content/bytes.svg b/examples/asset/content/bytes.svg new file mode 100644 index 00000000000..d7b7e40b4f8 --- /dev/null +++ b/examples/asset/content/bytes.svg @@ -0,0 +1 @@ +icon-square-small diff --git a/examples/asset/content/file.text b/examples/asset/content/file.text new file mode 100644 index 00000000000..3058aaa1fc0 --- /dev/null +++ b/examples/asset/content/file.text @@ -0,0 +1 @@ +a Ā 𐀀 文 🦄 Text diff --git a/examples/asset/example.js b/examples/asset/example.js new file mode 100644 index 00000000000..7746b705d0d --- /dev/null +++ b/examples/asset/example.js @@ -0,0 +1,116 @@ +// There are different ways to use files: + +// 1. Using `import something from "./file.ext";` + +// return URLs or Data URL, depends on your configuration +import png from "./images/file.png"; +import jpg from "./images/file.jpg"; +import svg from "./images/file.svg"; + +// 2. Using `import something from "./file.ext"; with { type: "text" }` or `import something from "./file.ext"; with { type: "bytes" }` +// You don't need extra options in your configuration for these imports, they work out of the box + +// returns the content as text +import text from "./content/file.text" with { type: "text" }; + +// returns the content as `Uint8Array` +import bytes from "./content/bytes.svg" with { type: "bytes" }; + +// 3. Using `new URL("./file.ext", import.meta.url);` +// You don't need extra options in your configuration for `new URL(...)` construction, they work out of the box +const url = new URL("./images/url.svg", import.meta.url); + +const container = document.createElement("div"); + +Object.assign(container.style, { + display: "flex", + flexWrap: "wrap", + justifyContent: "center" +}); +document.body.appendChild(container); + +function createImageElement(div, data) { + const img = document.createElement("img"); + img.setAttribute("src", data); + img.setAttribute("width", "150"); + div.appendChild(img); + + container.appendChild(div); +} + +function createTextElement(div, data) { + const context = document.createElement("div"); + context.textContent = data; + div.appendChild(context); + + container.appendChild(div); +} + +function createBlobElement(div, data) { + const blob = new Blob([data], { type: 'image/svg+xml' }); + const blobUrl = URL.createObjectURL(blob); + + const img = document.createElement("img"); + + img.setAttribute("src", blobUrl); + img.setAttribute("width", "150"); + div.appendChild(img); + + container.appendChild(div); + + img.addEventListener( + 'load', + () => { URL.revokeObjectURL(blobUrl) }, + { once: true } + ); +} + +const files = [ + { + title: "import png from \"./images/file.png\";", + data: png, + render: createImageElement, + }, + { + title: "import jpg from \"./images/file.jpg\";", + data: jpg, + render: createImageElement, + }, + { + title: "import svg from \"./images/file.svg\";", + data: svg, + render: createImageElement, + }, + { + title: "import text from \"./content/file.text\" with { type: \"text\" };", + data: text, + render: createTextElement, + }, + { + title: "import bytes from \"./content/file.text\" with { type: \"bytes\" };", + data: bytes, + render: createBlobElement, + }, + { + title: "new URL(\"./url.svg\", import.meta.url);", + data: url, + render: createImageElement, + }, +]; + + +function render(title, data, fn) { + const div = document.createElement("div"); + div.style.textAlign = "center"; + div.style.width = "50%"; + + const h2 = document.createElement("h2"); + h2.textContent = title; + div.appendChild(h2); + + fn(div, data) +} + +files.forEach(item => { + render(item.title, item.data, item.render); +}); diff --git a/examples/asset/images/file.jpg b/examples/asset/images/file.jpg new file mode 100644 index 00000000000..fe5c6eefa79 Binary files /dev/null and b/examples/asset/images/file.jpg differ diff --git a/examples/asset/images/file.png b/examples/asset/images/file.png new file mode 100644 index 00000000000..fb53b9dedd3 Binary files /dev/null and b/examples/asset/images/file.png differ diff --git a/examples/asset/images/file.svg b/examples/asset/images/file.svg new file mode 100644 index 00000000000..d7b7e40b4f8 --- /dev/null +++ b/examples/asset/images/file.svg @@ -0,0 +1 @@ +icon-square-small diff --git a/examples/asset/images/url.svg b/examples/asset/images/url.svg new file mode 100644 index 00000000000..d7b7e40b4f8 --- /dev/null +++ b/examples/asset/images/url.svg @@ -0,0 +1 @@ +icon-square-small diff --git a/examples/asset/index.html b/examples/asset/index.html new file mode 100644 index 00000000000..d1fb49339c9 --- /dev/null +++ b/examples/asset/index.html @@ -0,0 +1,5 @@ + + + + + diff --git a/examples/asset/template.md b/examples/asset/template.md new file mode 100644 index 00000000000..598823d5fa0 --- /dev/null +++ b/examples/asset/template.md @@ -0,0 +1,29 @@ +This is a very simple example that shows the usage of the asset module type. + +Files can be imported like other modules without file-loader. + +# example.js + +```javascript +_{{example.js}}_ +``` + +# webpack.config.js + +```javascript +_{{webpack.config.js}}_ +``` + +# js/output.js + +```javascript +_{{dist/output.js}}_ +``` + +# Info + +## webpack output + +``` +_{{stdout}}_ +``` diff --git a/examples/asset/webpack.config.js b/examples/asset/webpack.config.js new file mode 100644 index 00000000000..d0952d56cd1 --- /dev/null +++ b/examples/asset/webpack.config.js @@ -0,0 +1,18 @@ +"use strict"; + +/** @type {import("webpack").Configuration} */ +const config = { + output: { + assetModuleFilename: "images/[hash][ext]" + }, + module: { + rules: [ + { + test: /file\.(png|jpg|svg)$/, + type: "asset" + } + ] + } +}; + +module.exports = config; diff --git a/examples/build-common.js b/examples/build-common.js index afca1e4d7c4..a477edc7d46 100644 --- a/examples/build-common.js +++ b/examples/build-common.js @@ -5,47 +5,174 @@ "use strict"; const cp = require("child_process"); -const path = require("path"); -const tc = require("./template-common"); const fs = require("fs"); +const path = require("path"); const async = require("neo-async"); +const tc = require("./template-common"); const extraArgs = ""; -const targetArgs = global.NO_TARGET_ARGS ? "" : " ./example.js -o dist/output.js "; -const displayReasons = global.NO_REASONS ? "" : " --display-reasons --display-used-exports --display-provided-exports"; -const commonArgs = `--display-chunks --display-max-modules 99999 --display-origins --display-entrypoints --output-public-path "dist/" ${extraArgs} ${targetArgs}`; +/** + * @typedef {object} GlobalObj + * @property {boolean=} NO_TARGET_ARGS no target args flag + * @property {boolean=} NO_REASONS no stats reasons flag + * @property {boolean=} NO_STATS_OPTIONS no stats options flag + * @property {boolean=} NO_PUBLIC_PATH no public path flag + * @property {boolean=} STATS_COLORS no stats color flag + */ + +const globalObj = /** @type {typeof globalThis & GlobalObj} */ (global); + +const targetArgs = globalObj.NO_TARGET_ARGS + ? "" + : "--entry ./example.js --output-filename output.js"; +const displayReasons = globalObj.NO_REASONS + ? "" + : "--stats-reasons --stats-used-exports --stats-provided-exports"; +const statsArgs = globalObj.NO_STATS_OPTIONS + ? "" + : "--stats-chunks --stats-modules-space 99999 --stats-chunk-origins"; +const publicPathArgs = globalObj.NO_PUBLIC_PATH + ? "" + : '--output-public-path "dist/"'; +const statsColorsArg = globalObj.STATS_COLORS ? "" : "--no-color"; -let readme = fs.readFileSync(require("path").join(process.cwd(), "template.md"), "utf-8"); +const commonArgs = `${statsColorsArg} ${statsArgs} ${publicPathArgs} ${extraArgs} ${targetArgs}`; + +let readme = fs.readFileSync( + require("path").join(process.cwd(), "template.md"), + "utf8" +); + +/** + * @param {string} args args + * @param {string} prefix prefix + * @param {() => void} callback callback + */ const doCompileAndReplace = (args, prefix, callback) => { - if(!tc.needResults(readme, prefix)) { + if (!tc.needResults(readme, prefix)) { callback(); return; } - if(fs.existsSync("dist")) - for(const file of fs.readdirSync("dist")) - fs.unlinkSync(`dist/${file}`); - cp.exec(`node ${path.resolve(__dirname, "../bin/webpack.js")} ${args} ${displayReasons} ${commonArgs}`, (error, stdout, stderr) => { - if(stderr) - console.log(stderr); - if(error !== null) - console.log(error); - try { - readme = tc.replaceResults(readme, process.cwd(), stdout.replace(/[\r?\n]*$/, ""), prefix); - } catch(e) { - console.log(stderr); - throw e; + + /** + * @param {string} dir the directory for deleting + */ + const deleteFiles = (dir) => { + const targetDir = path.resolve("dist", dir); + + if (path.extname(targetDir) === "") { + for (const file of fs.readdirSync(targetDir)) { + deleteFiles(path.join(targetDir, file)); + } + } else { + fs.unlinkSync(targetDir); } - callback(); - }); + }; + + if (fs.existsSync("dist")) { + for (const dir of fs.readdirSync("dist")) { + deleteFiles(dir); + } + } + + try { + require.resolve("webpack-cli"); + } catch (err) { + throw new Error("Please install webpack-cli at root.", { cause: err }); + } + + /** + * @param {import("child_process").ChildProcess} subprocess a subprocess + */ + const connectIO = (subprocess) => { + const { stdin, stdout, stderr } = process; + const { stdin: _stdin, stdout: _stdout, stderr: _stderr } = subprocess; + /** @type {[NodeJS.ReadStream, import("stream").Writable][]} */ + const inputPair = [ + [stdin, /** @type {import("stream").Writable} */ (_stdin)] + ]; + /** @type {[NodeJS.WritableStream, import("stream").Readable][]} */ + const outputPair = [ + [stdout, /** @type {import("stream").Readable} */ (_stdout)], + [stderr, /** @type {import("stream").Readable} */ (_stderr)] + ]; + for (const pair of inputPair) { + pair[0].pipe(pair[1]); + } + for (const pair of outputPair) { + pair[1].pipe(pair[0]); + } + disconnectIO = () => { + for (const pair of inputPair) { + pair[0].unpipe(pair[1]); + } + for (const pair of outputPair) { + pair[1].unpipe(pair[0]); + } + }; + }; + /** @type {null | (() => void)} */ + let disconnectIO = null; + + const subprocess = cp.exec( + `node ${path.resolve(__dirname, "../bin/webpack.js")} ${args} ${displayReasons} ${commonArgs}`, + (error, stdout, stderr) => { + // eslint-disable-next-line no-unused-expressions + disconnectIO && disconnectIO(); + if (stderr) console.log(stderr); + if (error !== null) { + console.log(error); + throw error; + } + try { + readme = tc.replaceResults( + readme, + process.cwd(), + stdout + .replace(/[\r?\n]*$/, "") + .replace( + /\d\d\d\d-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])/g, + "XXXX-XX-XX" + ) + .replace(/([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]/g, "XXXX:XX:XX") + .replace(/webpack [0-9.]+/g, "webpack X.X.X"), + prefix + ); + } catch (err) { + console.log(stderr); + throw err; + } + callback(); + } + ); + connectIO(subprocess); }; -async.series([ - callback => doCompileAndReplace("--mode production", "production", callback), - callback => doCompileAndReplace("--mode development --devtool none", "development", callback), - callback => doCompileAndReplace("--mode none --output-pathinfo", "", callback) -], () => { - readme = tc.replaceBase(readme); - fs.writeFile("README.md", readme, "utf-8", function() {}); -}); +async.series( + [ + (callback) => + doCompileAndReplace( + "--mode production --env production", + "production", + callback + ), + (callback) => + doCompileAndReplace( + "--mode development --env development --devtool none", + "development", + callback + ), + (callback) => + doCompileAndReplace( + "--mode none --env none --output-pathinfo verbose", + "", + callback + ) + ], + () => { + readme = tc.replaceBase(readme); + fs.writeFile("README.md", readme, "utf8", () => {}); + } +); diff --git a/examples/build-http/README.md b/examples/build-http/README.md new file mode 100644 index 00000000000..716c075fbff --- /dev/null +++ b/examples/build-http/README.md @@ -0,0 +1,97 @@ +# example.js + +```javascript +import pMap1 from "https://cdn.skypack.dev/p-map"; +import pMap2 from "https://cdn.esm.sh/p-map"; +import pMap3 from "https://jspm.dev/p-map"; +import pMap4 from "https://unpkg.com/p-map-series?module"; // unpkg doesn't support p-map :( +console.log(pMap1); +console.log(pMap2); +console.log(pMap3); +console.log(pMap4); +``` + +# webpack.config.js + +```javascript +"use strict"; + +/** @type {import("webpack").Configuration} */ +const config = { + // enable debug logging to see network requests! + // stats: { + // loggingDebug: /HttpUriPlugin/ + // }, + experiments: { + buildHttp: [ + "https://cdn.esm.sh/", + "https://cdn.skypack.dev/", + "https://jspm.dev/", + /^https:\/\/unpkg\.com\/.+\?module$/ + ] + } +}; + +module.exports = config; +``` + +# Info + +## Unoptimized + +``` +asset output.js 82.9 KiB [emitted] (name: main) +runtime modules 1.08 KiB 3 modules +modules by path https:// 30 KiB + modules by path https://jspm.dev/ 16.1 KiB 12 modules + modules by path https://cdn.esm.sh/ 6.15 KiB + https://cdn.esm.sh/p-map 173 bytes [built] [code generated] + [exports: default, pMapSkip] + [used exports unknown] + harmony side effect evaluation https://cdn.esm.sh/p-map ./example.js 2:0-45 + harmony import specifier https://cdn.esm.sh/p-map ./example.js 6:12-17 + https://cdn.esm.sh/v53/p-map@5.1.0/es2015/p-map.js 1.18 KiB [built] [code generated] + [exports: default, pMapSkip] + [used exports unknown] + harmony side effect evaluation https://cdn.esm.sh/v53/p-map@5.1.0/es2015/p-map.js https://cdn.esm.sh/p-map 2:0-67 + harmony export imported specifier https://cdn.esm.sh/v53/p-map@5.1.0/es2015/p-map.js https://cdn.esm.sh/p-map 2:0-67 + harmony side effect evaluation https://cdn.esm.sh/v53/p-map@5.1.0/es2015/p-map.js https://cdn.esm.sh/p-map 3:0-77 + harmony export imported specifier https://cdn.esm.sh/v53/p-map@5.1.0/es2015/p-map.js https://cdn.esm.sh/p-map 3:0-77 + + 5 modules + modules by path https://cdn.skypack.dev/ 7.46 KiB + https://cdn.skypack.dev/p-map 757 bytes [built] [code generated] + [exports: default, pMapSkip] + [used exports unknown] + harmony side effect evaluation https://cdn.skypack.dev/p-map ./example.js 1:0-50 + harmony import specifier https://cdn.skypack.dev/p-map ./example.js 5:12-17 + https://cdn.skypack.dev/-/p-map@v5.1.0-7ixXvZxXPKKt9unR9LT0/dist=es2020,mode=imp...(truncated) 2.29 KiB [built] [code generated] + [exports: default, pMapSkip] + [used exports unknown] + harmony side effect evaluation /-/p-map@v5.1.0-7ixXvZxXPKKt9unR9LT0/dist=es2020,mode=imports/optimized/p-map.js https://cdn.skypack.dev/p-map 15:0-97 + harmony export imported specifier /-/p-map@v5.1.0-7ixXvZxXPKKt9unR9LT0/dist=es2020,mode=imports/optimized/p-map.js https://cdn.skypack.dev/p-map 15:0-97 + harmony side effect evaluation /-/p-map@v5.1.0-7ixXvZxXPKKt9unR9LT0/dist=es2020,mode=imports/optimized/p-map.js https://cdn.skypack.dev/p-map 16:0-105 + harmony export imported specifier /-/p-map@v5.1.0-7ixXvZxXPKKt9unR9LT0/dist=es2020,mode=imports/optimized/p-map.js https://cdn.skypack.dev/p-map 16:0-105 + + 4 modules + https://unpkg.com/p-map-series?module 263 bytes [built] [code generated] + [exports: default] + [used exports unknown] + harmony side effect evaluation https://unpkg.com/p-map-series?module ./example.js 4:0-58 + harmony import specifier https://unpkg.com/p-map-series?module ./example.js 8:12-17 +./example.js 314 bytes [built] [code generated] + [no exports] + [used exports unknown] + entry ./example.js main +webpack X.X.X compiled successfully +``` + +## Production mode + +``` +asset output.js 12.4 KiB [emitted] [minimized] (name: main) +orphan modules 30 KiB [orphan] 26 modules +./example.js + 25 modules 30.2 KiB [built] [code generated] + [no exports] + [no exports used] + entry ./example.js main +webpack X.X.X compiled successfully +``` diff --git a/examples/build-http/build.js b/examples/build-http/build.js new file mode 100644 index 00000000000..1d8b07db18b --- /dev/null +++ b/examples/build-http/build.js @@ -0,0 +1,2 @@ +global.NO_STATS_OPTIONS = true; +require("../build-common"); diff --git a/examples/build-http/example.js b/examples/build-http/example.js new file mode 100644 index 00000000000..4dd7204b019 --- /dev/null +++ b/examples/build-http/example.js @@ -0,0 +1,8 @@ +import pMap1 from "https://cdn.skypack.dev/p-map"; +import pMap2 from "https://cdn.esm.sh/p-map"; +import pMap3 from "https://jspm.dev/p-map"; +import pMap4 from "https://unpkg.com/p-map-series?module"; // unpkg doesn't support p-map :( +console.log(pMap1); +console.log(pMap2); +console.log(pMap3); +console.log(pMap4); diff --git a/examples/build-http/template.md b/examples/build-http/template.md new file mode 100644 index 00000000000..2df3585bde6 --- /dev/null +++ b/examples/build-http/template.md @@ -0,0 +1,25 @@ +# example.js + +```javascript +_{{example.js}}_ +``` + +# webpack.config.js + +```javascript +_{{webpack.config.js}}_ +``` + +# Info + +## Unoptimized + +``` +_{{stdout}}_ +``` + +## Production mode + +``` +_{{production:stdout}}_ +``` diff --git a/examples/build-http/webpack.config.js b/examples/build-http/webpack.config.js new file mode 100644 index 00000000000..8c143f2cbde --- /dev/null +++ b/examples/build-http/webpack.config.js @@ -0,0 +1,19 @@ +"use strict"; + +/** @type {import("webpack").Configuration} */ +const config = { + // enable debug logging to see network requests! + // stats: { + // loggingDebug: /HttpUriPlugin/ + // }, + experiments: { + buildHttp: [ + "https://cdn.esm.sh/", + "https://cdn.skypack.dev/", + "https://jspm.dev/", + /^https:\/\/unpkg\.com\/.+\?module$/ + ] + } +}; + +module.exports = config; diff --git a/examples/build-http/webpack.lock b/examples/build-http/webpack.lock new file mode 100644 index 00000000000..f696523fd3e --- /dev/null +++ b/examples/build-http/webpack.lock @@ -0,0 +1,29 @@ +{ + "https://cdn.esm.sh/p-map": { "integrity": "sha512-TfztRxlC5elIRa7x3oz4bfhtxJr5hIhoa+bliQkroNj8haEMPp1mv/eAsfzBt032G1oK6JT6y3135FP0vRh13Q==", "contentType": "application/javascript; charset=utf-8" }, + "https://cdn.esm.sh/v53/aggregate-error@4.0.0/es2015/aggregate-error.js": { "integrity": "sha512-4iHvwySJO0Dn0aenl2XY1XCGEoMZFaJ+PkuO8Op0BRVNwHiZaKrCuMnPZqUblPhvAG2o8SEA4JdB/fhS3IQZLg==", "contentType": "application/javascript" }, + "https://cdn.esm.sh/v53/clean-stack@4.1.0/es2015/clean-stack.js": { "integrity": "sha512-VzcwF50IxKsmW4O2DpY8WB6TmYh9caBctTqA2EkE3p9K8JjITMD/qBNqfVmUKAlmq4CFgI3c0xegzMf1BRWbyQ==", "contentType": "application/javascript" }, + "https://cdn.esm.sh/v53/escape-string-regexp@5.0.0/es2015/escape-string-regexp.js": { "integrity": "sha512-vst7rz+jFlvZMjo5GUzNBSq7QvFoaqOQ+hDq0m40ZJYGts6ptt+QKLZOMDWgoEq3Fabnhiy+hsoIfaHMmVdbSQ==", "contentType": "application/javascript" }, + "https://cdn.esm.sh/v53/indent-string@5.0.0/es2015/indent-string.js": { "integrity": "sha512-o1hDF1EyRTCiDpcxD2i0XpIuHCMFrc9XkKrkMISIaiWpJdKU7HBRhtqXfBcpVfJF1uNAFJ7/1v40vpPH2r7X8w==", "contentType": "application/javascript" }, + "https://cdn.esm.sh/v53/os-browserify@0.3.0/es2015/browser.js": { "integrity": "sha512-8JOZWkDGX6WNFtXIk/aOawVo35LZSIgCdbMrleK4QL8kHcYti2oTjfqfn99AJm6SOUsTt0uY5K808uHAvVe3eA==", "contentType": "application/javascript" }, + "https://cdn.esm.sh/v53/p-map@5.1.0/es2015/p-map.js": { "integrity": "sha512-3kEIICBOLKnEn6SNNixOBy+VGgwh0DYtn07yxHfagwiSJV8om7q/37RdHVbQ2pol8B/6oVMHo7Y6YYhmpYKDUA==", "contentType": "application/javascript" }, + "https://cdn.skypack.dev/-/aggregate-error@v4.0.0-rCH8s5R9g4kQQ807o58j/dist=es2020,mode=imports/optimized/aggregate-error.js": { "integrity": "sha512-E5rN3mgPTqyfHSovQ++ZyZWQkMUniuyjbeHHX+E4G3MStEx6TfObScB8tfHeIyuawSp86nVsFfMZjCruD61rdg==", "contentType": "application/javascript; charset=utf-8" }, + "https://cdn.skypack.dev/-/clean-stack@v4.1.0-DgWUKXHVzThBBZtsHXhC/dist=es2020,mode=imports/optimized/clean-stack.js": { "integrity": "sha512-1nEMT4Vc2YLu3EbeBnck7Traj0/D6G9MMSGraGpsoQIMKVuhQjq4gP76X6RxUn5GoiHv90KfrFMSWlbBn26Dhw==", "contentType": "application/javascript; charset=utf-8" }, + "https://cdn.skypack.dev/-/escape-string-regexp@v5.0.0-SUDdAhYOdAgXIYndxZss/dist=es2020,mode=imports/optimized/escape-string-regexp.js": { "integrity": "sha512-54oHYow5obgsKb0twQZMNLvCH2tV5MCOY4YHB0LQH+zVonIAn7JYZseUPWhC3MMkJFK5EkeNWDAX7P2camp27g==", "contentType": "application/javascript; charset=utf-8" }, + "https://cdn.skypack.dev/-/indent-string@v5.0.0-VgKPSgi4hUX5NbF4n3aC/dist=es2020,mode=imports/optimized/indent-string.js": { "integrity": "sha512-lSZAs06jEHkVlPMEeMtKbygGhrSmJUMVmpB6/2ChdG2F0694vRU1v6N12bUyqR5uGbbteTJ7atP5PmPtTVmlcw==", "contentType": "application/javascript; charset=utf-8" }, + "https://cdn.skypack.dev/-/p-map@v5.1.0-7ixXvZxXPKKt9unR9LT0/dist=es2020,mode=imports/optimized/p-map.js": { "integrity": "sha512-mZyhNJe8VlqEqafSkUGTooFrKcQPSwVjB3UxAAPqywSFD+age77uTRP6ul8uAMEQ3lllmengXX1q45igRxRcDw==", "contentType": "application/javascript; charset=utf-8" }, + "https://cdn.skypack.dev/p-map": { "integrity": "sha512-FFu6R9j8mrGqTvw8WL37XsWhI9P65XdPD9Jfs/47jiYNdex12f0XJNsIy+fI81PbOkCuEQRgm2nf0P76ieBlag==", "contentType": "application/javascript; charset=utf-8" }, + "https://jspm.dev/npm:@jspm/core@2.0.0-beta.11/nodelibs/os": { "integrity": "sha512-Jsg9UMzfNTnlPDu6FeftYzdp6XULJwLDI7xFSzULhMqjQUoOIHJhkAToEgr3NnEKCkLZQMIPuBvHAn0ud6gT+w==", "contentType": "application/javascript; charset=utf-8" }, + "https://jspm.dev/npm:@jspm/core@2.0.0-beta.11/nodelibs/process": { "integrity": "sha512-KIYEmkrnT7TL5EKA5coPbbdoqfL2twHFBVXKTZS+PU5aZFX90yELxZHrm4DhxSQ33FLAWo51/nQLQmqGekWNMw==", "contentType": "application/javascript; charset=utf-8" }, + "https://jspm.dev/npm:@jspm/core@2/nodelibs/os": { "integrity": "sha512-g2ppEW1AVdbIpc486D0ZmLIR5CtzMITkBwqoBgxvhiIq5/qHP4/unZ7Czk3q8A1UwdTI4wbGzRWndXAUa4/Q0Q==", "contentType": "application/javascript; charset=utf-8" }, + "https://jspm.dev/npm:aggregate-error@4": { "integrity": "sha512-XfXd6EZ09/SKLmWFFvjPCSkqv0E08IxKc8mFm9mePyLIiEiGyAKokeFt1wql+kG8ikGmI7YqKBsDf07/I31VvA==", "contentType": "application/javascript; charset=utf-8" }, + "https://jspm.dev/npm:aggregate-error@4.0.0": { "integrity": "sha512-HEobsVYXVCp5H4Z+6qAlKno8XAJwHQrfF4ivR4PHrp4ttM0Yg0zDfOcsjqJOnTP5hEnKR1K6OzQdPfR2r9of4g==", "contentType": "application/javascript; charset=utf-8" }, + "https://jspm.dev/npm:clean-stack@4": { "integrity": "sha512-3wh/QTJY4tw/GInIcn5I+0hsHSirJi8Tf3kmH85hzQsuwB5k2lghBFZyKZPO7/Ql3muvZeDgN02pYkZap59Qrw==", "contentType": "application/javascript; charset=utf-8" }, + "https://jspm.dev/npm:clean-stack@4.1.0": { "integrity": "sha512-VgNMH/ju9thH4YuxxA5trzs0u66nzRZhMa43jkhk8q6jxlEBhd7G6ZZxswy2a0ZXiXjPQVhzXfFkAIkY/pxTOg==", "contentType": "application/javascript; charset=utf-8" }, + "https://jspm.dev/npm:escape-string-regexp@5.0.0": { "integrity": "sha512-Hz7n4npzwf0UgkdjQvLN2HxudnAzllTEM9AzJPlnzf9ktGhkwlFltPQBjEM3xyDHeTj1xI1nYpBSRVQmMCl6bw==", "contentType": "application/javascript; charset=utf-8" }, + "https://jspm.dev/npm:indent-string@5": { "integrity": "sha512-hjMQ8+LX0q8xe2sCp/DEBJW2MrVFbiDv20pK0PWwENkYCkRlyP5L4t5AUiXLEXfJLUhTVrUfZtf+hmrnGJB/zA==", "contentType": "application/javascript; charset=utf-8" }, + "https://jspm.dev/npm:indent-string@5.0.0": { "integrity": "sha512-1KRJ7I1gDWWBAXz+NpwQnlJXDiSpaxaftugln1zHywLbqhA/akcZYM6+nTdfSSuQ7wiVong69R5X9l/QKWqO7g==", "contentType": "application/javascript; charset=utf-8" }, + "https://jspm.dev/npm:p-map@5.1.0": { "integrity": "sha512-Ml4ozElyzZEvq3G61nmeDVjEPVbjNzhWwIfvVcEr0OsUu58yT/ieSJWr6VSSHbNGY8B1IYjJCEO2zFrgIT9plQ==", "contentType": "application/javascript; charset=utf-8" }, + "https://jspm.dev/p-map": { "integrity": "sha512-Ztuu37YpSElOGm1OnAmLzhgTuTSyeDXCudBO94yRDDicb2zwUTIDEaVnHMJ6Gb7AVnKk26uubHB+Hw0XxKRnrw==", "contentType": "application/javascript; charset=utf-8" }, + "https://unpkg.com/p-map-series?module": { "resolved": "https://unpkg.com/p-map-series@3.0.0/index.js?module", "integrity": "sha512-e68FFGx6Hb3/2x4o16EWcd6rdmyiov0OLjPnj2bmc60JrrNowav76umw0Gc5TmT+UOjaJo9Xk2lTGQT1/Y6Jug==", "contentType": "application/javascript; charset=utf-8" }, + "version": 1 +} diff --git a/examples/build-http/webpack.lock.data/https_cdn.esm.sh/p-map_9dd32c023fd5f3d3e7f2 b/examples/build-http/webpack.lock.data/https_cdn.esm.sh/p-map_9dd32c023fd5f3d3e7f2 new file mode 100644 index 00000000000..5034fb3895a --- /dev/null +++ b/examples/build-http/webpack.lock.data/https_cdn.esm.sh/p-map_9dd32c023fd5f3d3e7f2 @@ -0,0 +1,3 @@ +/* esm.sh - p-map@5.1.0 */ +export * from "https://cdn.esm.sh/v53/p-map@5.1.0/es2015/p-map.js"; +export { default } from "https://cdn.esm.sh/v53/p-map@5.1.0/es2015/p-map.js"; diff --git a/examples/build-http/webpack.lock.data/https_cdn.esm.sh/v53_aggregate-error_4.0.0_es2015_aggregate-error_ff6bcc1ba33bf3b1810a.js b/examples/build-http/webpack.lock.data/https_cdn.esm.sh/v53_aggregate-error_4.0.0_es2015_aggregate-error_ff6bcc1ba33bf3b1810a.js new file mode 100644 index 00000000000..7d5f29fd065 --- /dev/null +++ b/examples/build-http/webpack.lock.data/https_cdn.esm.sh/v53_aggregate-error_4.0.0_es2015_aggregate-error_ff6bcc1ba33bf3b1810a.js @@ -0,0 +1,4 @@ +/* esm.sh - esbuild bundle(aggregate-error@4.0.0) es2015 production */ +var l=Object.defineProperty;var f=(n,t,e)=>t in n?l(n,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):n[t]=e;var s=(n,t,e)=>(f(n,typeof t!="symbol"?t+"":t,e),e),i=(n,t,e)=>{if(!t.has(n))throw TypeError("Cannot "+e)};var c=(n,t,e)=>(i(n,t,"read from private field"),e?e.call(n):t.get(n)),g=(n,t,e)=>{if(t.has(n))throw TypeError("Cannot add the same private member more than once");t instanceof WeakSet?t.add(n):t.set(n,e)},o=(n,t,e,a)=>(i(n,t,"write to private field"),a?a.call(n,e):t.set(n,e),e);import u from"/v53/indent-string@5.0.0/es2015/indent-string.js";import m from"/v53/clean-stack@4.1.0/es2015/clean-stack.js";var d=n=>n.replace(/\s+at .*aggregate-error\/index.js:\d+:\d+\)?/g,""),r,p=class extends Error{constructor(t){if(!Array.isArray(t))throw new TypeError(`Expected input to be an Array, got ${typeof t}`);t=t.map(a=>a instanceof Error?a:a!==null&&typeof a=="object"?Object.assign(new Error(a.message),a):new Error(a));let e=t.map(a=>typeof a.stack=="string"?d(m(a.stack)):String(a)).join(` +`);e=` +`+u(e,4);super(e);g(this,r,void 0);s(this,"name","AggregateError");o(this,r,t)}get errors(){return c(this,r).slice()}};r=new WeakMap;export{p as default}; diff --git a/examples/build-http/webpack.lock.data/https_cdn.esm.sh/v53_clean-stack_4.1.0_es2015_clean-stack_87b32b37ae264a8e8a1c.js b/examples/build-http/webpack.lock.data/https_cdn.esm.sh/v53_clean-stack_4.1.0_es2015_clean-stack_87b32b37ae264a8e8a1c.js new file mode 100644 index 00000000000..a3c644a1fb2 --- /dev/null +++ b/examples/build-http/webpack.lock.data/https_cdn.esm.sh/v53_clean-stack_4.1.0_es2015_clean-stack_87b32b37ae264a8e8a1c.js @@ -0,0 +1,4 @@ +/* esm.sh - esbuild bundle(clean-stack@4.1.0) es2015 production */ +import s from"/v53/os-browserify@0.3.0/es2015/browser.js";import i from"/v53/escape-string-regexp@5.0.0/es2015/escape-string-regexp.js";var p=/\s+at.*[(\s](.*)\)?/,l=/^(?:(?:(?:node|node:[\w/]+|(?:(?:node:)?internal\/[\w/]*|.*node_modules\/(?:babel-polyfill|pirates)\/.*)?\w+)(?:\.js)?:\d+:\d+)|native)/,f=typeof s.homedir=="undefined"?"":s.homedir().replace(/\\/g,"/");function u(n,{pretty:c=!1,basePath:a}={}){let o=a&&new RegExp(`(at | \\()${i(a.replace(/\\/g,"/"))}`,"g");if(typeof n=="string")return n.replace(/\\/g,"/").split(` +`).filter(e=>{let r=e.match(p);if(r===null||!r[1])return!0;let t=r[1];return t.includes(".app/Contents/Resources/electron.asar")||t.includes(".app/Contents/Resources/default_app.asar")?!1:!l.test(t)}).filter(e=>e.trim()!=="").map(e=>(o&&(e=e.replace(o,"$1")),c&&(e=e.replace(p,(r,t)=>r.replace(t,t.replace(f,"~")))),e)).join(` +`)}export{u as default}; diff --git a/examples/build-http/webpack.lock.data/https_cdn.esm.sh/v53_escape-string-regexp_5.0.0_es2015_escape-string-regexp_2c814e466860133eca86.js b/examples/build-http/webpack.lock.data/https_cdn.esm.sh/v53_escape-string-regexp_5.0.0_es2015_escape-string-regexp_2c814e466860133eca86.js new file mode 100644 index 00000000000..a70aa3b9a9e --- /dev/null +++ b/examples/build-http/webpack.lock.data/https_cdn.esm.sh/v53_escape-string-regexp_5.0.0_es2015_escape-string-regexp_2c814e466860133eca86.js @@ -0,0 +1,2 @@ +/* esm.sh - esbuild bundle(escape-string-regexp@5.0.0) es2015 production */ +function r(e){if(typeof e!="string")throw new TypeError("Expected a string");return e.replace(/[|\\{}()[\]^$+*?.]/g,"\\$&").replace(/-/g,"\\x2d")}export{r as default}; diff --git a/examples/build-http/webpack.lock.data/https_cdn.esm.sh/v53_indent-string_5.0.0_es2015_indent-string_171b2b5ba89965a085b6.js b/examples/build-http/webpack.lock.data/https_cdn.esm.sh/v53_indent-string_5.0.0_es2015_indent-string_171b2b5ba89965a085b6.js new file mode 100644 index 00000000000..758f021c33e --- /dev/null +++ b/examples/build-http/webpack.lock.data/https_cdn.esm.sh/v53_indent-string_5.0.0_es2015_indent-string_171b2b5ba89965a085b6.js @@ -0,0 +1,2 @@ +/* esm.sh - esbuild bundle(indent-string@5.0.0) es2015 production */ +function i(t,e=1,o={}){let{indent:r=" ",includeEmptyLines:n=!1}=o;if(typeof t!="string")throw new TypeError(`Expected \`input\` to be a \`string\`, got \`${typeof t}\``);if(typeof e!="number")throw new TypeError(`Expected \`count\` to be a \`number\`, got \`${typeof e}\``);if(e<0)throw new RangeError(`Expected \`count\` to be at least 0, got \`${e}\``);if(typeof r!="string")throw new TypeError(`Expected \`options.indent\` to be a \`string\`, got \`${typeof r}\``);if(e===0)return t;let p=n?/^/gm:/^(?!\s*$)/gm;return t.replace(p,r.repeat(e))}export{i as default}; diff --git a/examples/build-http/webpack.lock.data/https_cdn.esm.sh/v53_os-browserify_0.3.0_es2015_browser_476a088316baaea08336.js b/examples/build-http/webpack.lock.data/https_cdn.esm.sh/v53_os-browserify_0.3.0_es2015_browser_476a088316baaea08336.js new file mode 100644 index 00000000000..951e12edff7 --- /dev/null +++ b/examples/build-http/webpack.lock.data/https_cdn.esm.sh/v53_os-browserify_0.3.0_es2015_browser_476a088316baaea08336.js @@ -0,0 +1,3 @@ +/* esm.sh - esbuild bundle(os-browserify@0.3.0/browser) es2015 production */ +var f=Object.create;var o=Object.defineProperty;var s=Object.getOwnPropertyDescriptor;var m=Object.getOwnPropertyNames;var c=Object.getPrototypeOf,p=Object.prototype.hasOwnProperty;var d=e=>o(e,"__esModule",{value:!0});var l=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var w=(e,t,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of m(t))!p.call(e,n)&&n!=="default"&&o(e,n,{get:()=>t[n],enumerable:!(i=s(t,n))||i.enumerable});return e},a=e=>w(d(o(e!=null?f(c(e)):{},"default",e&&e.__esModule&&"default"in e?{get:()=>e.default,enumerable:!0}:{value:e,enumerable:!0})),e);var u=l(r=>{r.endianness=function(){return"LE"};r.hostname=function(){return typeof location!="undefined"?location.hostname:""};r.loadavg=function(){return[]};r.uptime=function(){return 0};r.freemem=function(){return Number.MAX_VALUE};r.totalmem=function(){return Number.MAX_VALUE};r.cpus=function(){return[]};r.type=function(){return"Browser"};r.release=function(){return typeof navigator!="undefined"?navigator.appVersion:""};r.networkInterfaces=r.getNetworkInterfaces=function(){return{}};r.arch=function(){return"javascript"};r.platform=function(){return"browser"};r.tmpdir=r.tmpDir=function(){return"/tmp"};r.EOL=` +`;r.homedir=function(){return"/"}});var b=a(u()),h=a(u()),{endianness:v,hostname:E,loadavg:L,uptime:k,freemem:A,totalmem:I,cpus:N,type:_,release:V,networkInterfaces:x,getNetworkInterfaces:D,arch:M,platform:O,tmpdir:U,tmpDir:X,EOL:j,homedir:B}=b;var export_default=h.default;export{j as EOL,M as arch,N as cpus,export_default as default,v as endianness,A as freemem,D as getNetworkInterfaces,B as homedir,E as hostname,L as loadavg,x as networkInterfaces,O as platform,V as release,X as tmpDir,U as tmpdir,I as totalmem,_ as type,k as uptime}; diff --git a/examples/build-http/webpack.lock.data/https_cdn.esm.sh/v53_p-map_5.1.0_es2015_p-map_cd0c09542673ea9d78f0.js b/examples/build-http/webpack.lock.data/https_cdn.esm.sh/v53_p-map_5.1.0_es2015_p-map_cd0c09542673ea9d78f0.js new file mode 100644 index 00000000000..8baf6a8521d --- /dev/null +++ b/examples/build-http/webpack.lock.data/https_cdn.esm.sh/v53_p-map_5.1.0_es2015_p-map_cd0c09542673ea9d78f0.js @@ -0,0 +1,2 @@ +/* esm.sh - esbuild bundle(p-map@5.1.0) es2015 production */ +var g=(l,s,e)=>new Promise((f,x)=>{var N=t=>{try{n(e.next(t))}catch(r){x(r)}},p=t=>{try{n(e.throw(t))}catch(r){x(r)}},n=t=>t.done?f(t.value):Promise.resolve(t.value).then(N,p);n((e=e.apply(l,s)).next())});import y from"/v53/aggregate-error@4.0.0/es2015/aggregate-error.js";function S(x,N){return g(this,arguments,function*(l,s,{concurrency:e=Number.POSITIVE_INFINITY,stopOnError:f=!0}={}){return new Promise((p,n)=>{if(typeof s!="function")throw new TypeError("Mapper function is required");if(!((Number.isSafeInteger(e)||e===Number.POSITIVE_INFINITY)&&e>=1))throw new TypeError(`Expected \`concurrency\` to be an integer from 1 and up or \`Infinity\`, got \`${e}\` (${typeof e})`);let t=[],r=[],m=[],h=l[Symbol.iterator](),u=!1,c=!1,a=0,b=0,I=()=>{if(u)return;let i=h.next(),d=b;if(b++,i.done){if(c=!0,a===0)if(!f&&r.length>0)n(new y(r));else{for(let o of m)t.splice(o,1);p(t)}return}a++,(()=>g(this,null,function*(){try{let o=yield i.value;if(u)return;let w=yield s(o,d);w===T?m.push(d):t[d]=w,a--,I()}catch(o){f?(u=!0,n(o)):(r.push(o),a--,I())}}))()};for(let i=0;i { + if (typeof key !== "symbol") + key += ""; + if (key in obj) + return __defProp(obj, key, {enumerable: true, configurable: true, writable: true, value}); + return obj[key] = value; +}; +var __accessCheck = (obj, member, msg) => { + if (!member.has(obj)) + throw TypeError("Cannot " + msg); +}; +var __privateGet = (obj, member, getter) => { + __accessCheck(obj, member, "read from private field"); + return getter ? getter.call(obj) : member.get(obj); +}; +var __privateSet = (obj, member, value, setter) => { + __accessCheck(obj, member, "write to private field"); + setter ? setter.call(obj, value) : member.set(obj, value); + return value; +}; +var _errors; +import indentString from "/-/indent-string@v5.0.0-VgKPSgi4hUX5NbF4n3aC/dist=es2020,mode=imports/optimized/indent-string.js"; +import cleanStack from "/-/clean-stack@v4.1.0-DgWUKXHVzThBBZtsHXhC/dist=es2020,mode=imports/optimized/clean-stack.js"; +const cleanInternalStack = (stack) => stack.replace(/\s+at .*aggregate-error\/index.js:\d+:\d+\)?/g, ""); +class AggregateError extends Error { + constructor(errors) { + _errors.set(this, void 0); + __publicField(this, "name", "AggregateError"); + if (!Array.isArray(errors)) { + throw new TypeError(`Expected input to be an Array, got ${typeof errors}`); + } + errors = errors.map((error) => { + if (error instanceof Error) { + return error; + } + if (error !== null && typeof error === "object") { + return Object.assign(new Error(error.message), error); + } + return new Error(error); + }); + let message = errors.map((error) => { + return typeof error.stack === "string" ? cleanInternalStack(cleanStack(error.stack)) : String(error); + }).join("\n"); + message = "\n" + indentString(message, 4); + super(message); + __privateSet(this, _errors, errors); + } + get errors() { + return __privateGet(this, _errors).slice(); + } +} +_errors = new WeakMap(); +export default AggregateError; diff --git a/examples/build-http/webpack.lock.data/https_cdn.skypack.dev/clean-stack_v4.1.0-DgWUKXHVzThBBZtsHXhC_dist_es2020_mode_imports_optimized_clean-stack_25e0e8c6773c790b5bc1.js b/examples/build-http/webpack.lock.data/https_cdn.skypack.dev/clean-stack_v4.1.0-DgWUKXHVzThBBZtsHXhC_dist_es2020_mode_imports_optimized_clean-stack_25e0e8c6773c790b5bc1.js new file mode 100644 index 00000000000..d8afc7bdf1c --- /dev/null +++ b/examples/build-http/webpack.lock.data/https_cdn.skypack.dev/clean-stack_v4.1.0-DgWUKXHVzThBBZtsHXhC_dist_es2020_mode_imports_optimized_clean-stack_25e0e8c6773c790b5bc1.js @@ -0,0 +1,31 @@ +import escapeStringRegexp from "/-/escape-string-regexp@v5.0.0-SUDdAhYOdAgXIYndxZss/dist=es2020,mode=imports/optimized/escape-string-regexp.js"; +var os = {}; +const extractPathRegex = /\s+at.*[(\s](.*)\)?/; +const pathRegex = /^(?:(?:(?:node|node:[\w/]+|(?:(?:node:)?internal\/[\w/]*|.*node_modules\/(?:babel-polyfill|pirates)\/.*)?\w+)(?:\.js)?:\d+:\d+)|native)/; +const homeDir = typeof os.homedir === "undefined" ? "" : os.homedir().replace(/\\/g, "/"); +function cleanStack(stack, {pretty = false, basePath} = {}) { + const basePathRegex = basePath && new RegExp(`(at | \\()${escapeStringRegexp(basePath.replace(/\\/g, "/"))}`, "g"); + if (typeof stack !== "string") { + return void 0; + } + return stack.replace(/\\/g, "/").split("\n").filter((line) => { + const pathMatches = line.match(extractPathRegex); + if (pathMatches === null || !pathMatches[1]) { + return true; + } + const match = pathMatches[1]; + if (match.includes(".app/Contents/Resources/electron.asar") || match.includes(".app/Contents/Resources/default_app.asar")) { + return false; + } + return !pathRegex.test(match); + }).filter((line) => line.trim() !== "").map((line) => { + if (basePathRegex) { + line = line.replace(basePathRegex, "$1"); + } + if (pretty) { + line = line.replace(extractPathRegex, (m, p1) => m.replace(p1, p1.replace(homeDir, "~"))); + } + return line; + }).join("\n"); +} +export default cleanStack; diff --git a/examples/build-http/webpack.lock.data/https_cdn.skypack.dev/escape-string-regexp_v5.0.0-SUDdAhYOdAgXIYndxZss_dist_es2020_mode_imports_optimized_escape-string-regexp_95a4ae8a862c0536f335.js b/examples/build-http/webpack.lock.data/https_cdn.skypack.dev/escape-string-regexp_v5.0.0-SUDdAhYOdAgXIYndxZss_dist_es2020_mode_imports_optimized_escape-string-regexp_95a4ae8a862c0536f335.js new file mode 100644 index 00000000000..d0aaf2eea76 --- /dev/null +++ b/examples/build-http/webpack.lock.data/https_cdn.skypack.dev/escape-string-regexp_v5.0.0-SUDdAhYOdAgXIYndxZss_dist_es2020_mode_imports_optimized_escape-string-regexp_95a4ae8a862c0536f335.js @@ -0,0 +1,7 @@ +function escapeStringRegexp(string) { + if (typeof string !== "string") { + throw new TypeError("Expected a string"); + } + return string.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&").replace(/-/g, "\\x2d"); +} +export default escapeStringRegexp; diff --git a/examples/build-http/webpack.lock.data/https_cdn.skypack.dev/indent-string_v5.0.0-VgKPSgi4hUX5NbF4n3aC_dist_es2020_mode_imports_optimized_indent-string_c9ee21b059896b4e6290.js b/examples/build-http/webpack.lock.data/https_cdn.skypack.dev/indent-string_v5.0.0-VgKPSgi4hUX5NbF4n3aC_dist_es2020_mode_imports_optimized_indent-string_c9ee21b059896b4e6290.js new file mode 100644 index 00000000000..307e1901ff0 --- /dev/null +++ b/examples/build-http/webpack.lock.data/https_cdn.skypack.dev/indent-string_v5.0.0-VgKPSgi4hUX5NbF4n3aC_dist_es2020_mode_imports_optimized_indent-string_c9ee21b059896b4e6290.js @@ -0,0 +1,24 @@ +function indentString(string, count = 1, options = {}) { + const { + indent = " ", + includeEmptyLines = false + } = options; + if (typeof string !== "string") { + throw new TypeError(`Expected \`input\` to be a \`string\`, got \`${typeof string}\``); + } + if (typeof count !== "number") { + throw new TypeError(`Expected \`count\` to be a \`number\`, got \`${typeof count}\``); + } + if (count < 0) { + throw new RangeError(`Expected \`count\` to be at least 0, got \`${count}\``); + } + if (typeof indent !== "string") { + throw new TypeError(`Expected \`options.indent\` to be a \`string\`, got \`${typeof indent}\``); + } + if (count === 0) { + return string; + } + const regex = includeEmptyLines ? /^/gm : /^(?!\s*$)/gm; + return string.replace(regex, indent.repeat(count)); +} +export default indentString; diff --git a/examples/build-http/webpack.lock.data/https_cdn.skypack.dev/p-map_85ed609042d47e169edd b/examples/build-http/webpack.lock.data/https_cdn.skypack.dev/p-map_85ed609042d47e169edd new file mode 100644 index 00000000000..aca926092c6 --- /dev/null +++ b/examples/build-http/webpack.lock.data/https_cdn.skypack.dev/p-map_85ed609042d47e169edd @@ -0,0 +1,16 @@ +/* + * Skypack CDN - p-map@5.1.0 + * + * Learn more: + * 📙 Package Documentation: https://www.skypack.dev/view/p-map + * 📘 Skypack Documentation: https://www.skypack.dev/docs + * + * Pinned URL: (Optimized for Production) + * ▶️ Normal: https://cdn.skypack.dev/pin/p-map@v5.1.0-7ixXvZxXPKKt9unR9LT0/mode=imports/optimized/p-map.js + * ⏩ Minified: https://cdn.skypack.dev/pin/p-map@v5.1.0-7ixXvZxXPKKt9unR9LT0/mode=imports,min/optimized/p-map.js + * + */ + +// Browser-Optimized Imports (Don't directly import the URLs below in your application!) +export * from '/-/p-map@v5.1.0-7ixXvZxXPKKt9unR9LT0/dist=es2020,mode=imports/optimized/p-map.js'; +export {default} from '/-/p-map@v5.1.0-7ixXvZxXPKKt9unR9LT0/dist=es2020,mode=imports/optimized/p-map.js'; diff --git a/examples/build-http/webpack.lock.data/https_cdn.skypack.dev/p-map_v5.1.0-7ixXvZxXPKKt9unR9LT0_dist_es2020_mode_imports_optimized_p-map_ddf2a76b117954d701e6.js b/examples/build-http/webpack.lock.data/https_cdn.skypack.dev/p-map_v5.1.0-7ixXvZxXPKKt9unR9LT0_dist_es2020_mode_imports_optimized_p-map_ddf2a76b117954d701e6.js new file mode 100644 index 00000000000..921f352df03 --- /dev/null +++ b/examples/build-http/webpack.lock.data/https_cdn.skypack.dev/p-map_v5.1.0-7ixXvZxXPKKt9unR9LT0_dist_es2020_mode_imports_optimized_p-map_ddf2a76b117954d701e6.js @@ -0,0 +1,79 @@ +import AggregateError from "/-/aggregate-error@v4.0.0-rCH8s5R9g4kQQ807o58j/dist=es2020,mode=imports/optimized/aggregate-error.js"; +async function pMap(iterable, mapper, { + concurrency = Number.POSITIVE_INFINITY, + stopOnError = true +} = {}) { + return new Promise((resolve, reject) => { + if (typeof mapper !== "function") { + throw new TypeError("Mapper function is required"); + } + if (!((Number.isSafeInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency >= 1)) { + throw new TypeError(`Expected \`concurrency\` to be an integer from 1 and up or \`Infinity\`, got \`${concurrency}\` (${typeof concurrency})`); + } + const result = []; + const errors = []; + const skippedIndexes = []; + const iterator = iterable[Symbol.iterator](); + let isRejected = false; + let isIterableDone = false; + let resolvingCount = 0; + let currentIndex = 0; + const next = () => { + if (isRejected) { + return; + } + const nextItem = iterator.next(); + const index = currentIndex; + currentIndex++; + if (nextItem.done) { + isIterableDone = true; + if (resolvingCount === 0) { + if (!stopOnError && errors.length > 0) { + reject(new AggregateError(errors)); + } else { + for (const skippedIndex of skippedIndexes) { + result.splice(skippedIndex, 1); + } + resolve(result); + } + } + return; + } + resolvingCount++; + (async () => { + try { + const element = await nextItem.value; + if (isRejected) { + return; + } + const value = await mapper(element, index); + if (value === pMapSkip) { + skippedIndexes.push(index); + } else { + result[index] = value; + } + resolvingCount--; + next(); + } catch (error) { + if (stopOnError) { + isRejected = true; + reject(error); + } else { + errors.push(error); + resolvingCount--; + next(); + } + } + })(); + }; + for (let index = 0; index < concurrency; index++) { + next(); + if (isIterableDone) { + break; + } + } + }); +} +const pMapSkip = Symbol("skip"); +export default pMap; +export {pMapSkip}; diff --git a/examples/build-http/webpack.lock.data/https_jspm.dev/npm_aggregate-error_4.0_50f751f77af91e405af4.0 b/examples/build-http/webpack.lock.data/https_jspm.dev/npm_aggregate-error_4.0_50f751f77af91e405af4.0 new file mode 100644 index 00000000000..65063d10575 --- /dev/null +++ b/examples/build-http/webpack.lock.data/https_jspm.dev/npm_aggregate-error_4.0_50f751f77af91e405af4.0 @@ -0,0 +1,48 @@ +import indentString from './npm:indent-string@5'; +import cleanStack from './npm:clean-stack@4'; + +const cleanInternalStack = stack => stack.replace(/\s+at .*aggregate-error\/index.js:\d+:\d+\)?/g, ''); + +class AggregateError extends Error { + #errors; + + name = 'AggregateError'; + + constructor(errors) { + if (!Array.isArray(errors)) { + throw new TypeError(`Expected input to be an Array, got ${typeof errors}`); + } + + errors = errors.map(error => { + if (error instanceof Error) { + return error; + } + + if (error !== null && typeof error === 'object') { + // Handle plain error objects with message property and/or possibly other metadata + return Object.assign(new Error(error.message), error); + } + + return new Error(error); + }); + + let message = errors + .map(error => { + // The `stack` property is not standardized, so we can't assume it exists + return typeof error.stack === 'string' ? cleanInternalStack(cleanStack(error.stack)) : String(error); + }) + .join('\n'); + message = '\n' + indentString(message, 4); + super(message); + + this.#errors = errors; + } + + get errors() { + return this.#errors.slice(); + } +} + +export default AggregateError; + +//# sourceMappingURL=npm:aggregate-error@4.0.0.map \ No newline at end of file diff --git a/examples/build-http/webpack.lock.data/https_jspm.dev/npm_aggregate-error_4_a354b9220c6e41b430f0 b/examples/build-http/webpack.lock.data/https_jspm.dev/npm_aggregate-error_4_a354b9220c6e41b430f0 new file mode 100644 index 00000000000..511f78a97ed --- /dev/null +++ b/examples/build-http/webpack.lock.data/https_jspm.dev/npm_aggregate-error_4_a354b9220c6e41b430f0 @@ -0,0 +1,3 @@ +import "/npm:indent-string@5"; +import "/npm:clean-stack@4"; +export { default } from "/npm:aggregate-error@4.0.0"; diff --git a/examples/build-http/webpack.lock.data/https_jspm.dev/npm_clean-stack_4.1_b2805ba009abd32b0160.0 b/examples/build-http/webpack.lock.data/https_jspm.dev/npm_clean-stack_4.1_b2805ba009abd32b0160.0 new file mode 100644 index 00000000000..8d14e04d2a0 --- /dev/null +++ b/examples/build-http/webpack.lock.data/https_jspm.dev/npm_clean-stack_4.1_b2805ba009abd32b0160.0 @@ -0,0 +1,52 @@ +import os from './npm:@jspm/core@2/nodelibs/os'; +import escapeStringRegexp from './npm:escape-string-regexp@5.0.0'; + +const extractPathRegex = /\s+at.*[(\s](.*)\)?/; +const pathRegex = /^(?:(?:(?:node|node:[\w/]+|(?:(?:node:)?internal\/[\w/]*|.*node_modules\/(?:babel-polyfill|pirates)\/.*)?\w+)(?:\.js)?:\d+:\d+)|native)/; +const homeDir = typeof os.homedir === 'undefined' ? '' : os.homedir().replace(/\\/g, '/'); + +function cleanStack(stack, {pretty = false, basePath} = {}) { + const basePathRegex = basePath && new RegExp(`(at | \\()${escapeStringRegexp(basePath.replace(/\\/g, '/'))}`, 'g'); + + if (typeof stack !== 'string') { + return undefined; + } + + return stack.replace(/\\/g, '/') + .split('\n') + .filter(line => { + const pathMatches = line.match(extractPathRegex); + if (pathMatches === null || !pathMatches[1]) { + return true; + } + + const match = pathMatches[1]; + + // Electron + if ( + match.includes('.app/Contents/Resources/electron.asar') || + match.includes('.app/Contents/Resources/default_app.asar') + ) { + return false; + } + + return !pathRegex.test(match); + }) + .filter(line => line.trim() !== '') + .map(line => { + if (basePathRegex) { + line = line.replace(basePathRegex, '$1'); + } + + if (pretty) { + line = line.replace(extractPathRegex, (m, p1) => m.replace(p1, p1.replace(homeDir, '~'))); + } + + return line; + }) + .join('\n'); +} + +export default cleanStack; + +//# sourceMappingURL=npm:clean-stack@4.1.0.map \ No newline at end of file diff --git a/examples/build-http/webpack.lock.data/https_jspm.dev/npm_clean-stack_4_760ca83301f78911741b b/examples/build-http/webpack.lock.data/https_jspm.dev/npm_clean-stack_4_760ca83301f78911741b new file mode 100644 index 00000000000..256472ccdd7 --- /dev/null +++ b/examples/build-http/webpack.lock.data/https_jspm.dev/npm_clean-stack_4_760ca83301f78911741b @@ -0,0 +1,3 @@ +import "/npm:@jspm/core@2/nodelibs/os"; +import "/npm:escape-string-regexp@5.0.0"; +export { default } from "/npm:clean-stack@4.1.0"; diff --git a/examples/build-http/webpack.lock.data/https_jspm.dev/npm_escape-string-regexp_5.0_703470061c4748c30ba2.0 b/examples/build-http/webpack.lock.data/https_jspm.dev/npm_escape-string-regexp_5.0_703470061c4748c30ba2.0 new file mode 100644 index 00000000000..3e1c303b111 --- /dev/null +++ b/examples/build-http/webpack.lock.data/https_jspm.dev/npm_escape-string-regexp_5.0_703470061c4748c30ba2.0 @@ -0,0 +1,15 @@ +function escapeStringRegexp(string) { + if (typeof string !== 'string') { + throw new TypeError('Expected a string'); + } + + // Escape characters with special meaning either inside or outside character sets. + // Use a simple backslash escape when it’s always valid, and a `\xnn` escape when the simpler form would be disallowed by Unicode patterns’ stricter grammar. + return string + .replace(/[|\\{}()[\]^$+*?.]/g, '\\$&') + .replace(/-/g, '\\x2d'); +} + +export default escapeStringRegexp; + +//# sourceMappingURL=npm:escape-string-regexp@5.0.0.map \ No newline at end of file diff --git a/examples/build-http/webpack.lock.data/https_jspm.dev/npm_indent-string_5.0_39c50c3c56a92bbf73ba.0 b/examples/build-http/webpack.lock.data/https_jspm.dev/npm_indent-string_5.0_39c50c3c56a92bbf73ba.0 new file mode 100644 index 00000000000..f4ccda81d23 --- /dev/null +++ b/examples/build-http/webpack.lock.data/https_jspm.dev/npm_indent-string_5.0_39c50c3c56a92bbf73ba.0 @@ -0,0 +1,42 @@ +function indentString(string, count = 1, options = {}) { + const { + indent = ' ', + includeEmptyLines = false + } = options; + + if (typeof string !== 'string') { + throw new TypeError( + `Expected \`input\` to be a \`string\`, got \`${typeof string}\`` + ); + } + + if (typeof count !== 'number') { + throw new TypeError( + `Expected \`count\` to be a \`number\`, got \`${typeof count}\`` + ); + } + + if (count < 0) { + throw new RangeError( + `Expected \`count\` to be at least 0, got \`${count}\`` + ); + } + + if (typeof indent !== 'string') { + throw new TypeError( + `Expected \`options.indent\` to be a \`string\`, got \`${typeof indent}\`` + ); + } + + if (count === 0) { + return string; + } + + const regex = includeEmptyLines ? /^/gm : /^(?!\s*$)/gm; + + return string.replace(regex, indent.repeat(count)); +} + +export default indentString; + +//# sourceMappingURL=npm:indent-string@5.0.0.map \ No newline at end of file diff --git a/examples/build-http/webpack.lock.data/https_jspm.dev/npm_indent-string_5_01a4f4bd5c5dc36ce1b7 b/examples/build-http/webpack.lock.data/https_jspm.dev/npm_indent-string_5_01a4f4bd5c5dc36ce1b7 new file mode 100644 index 00000000000..f8b9348076a --- /dev/null +++ b/examples/build-http/webpack.lock.data/https_jspm.dev/npm_indent-string_5_01a4f4bd5c5dc36ce1b7 @@ -0,0 +1 @@ +export { default } from "/npm:indent-string@5.0.0"; diff --git a/examples/build-http/webpack.lock.data/https_jspm.dev/npm_jspm_core_2.0.0-beta_12b8110471722e74fcb6.11_nodelibs_process b/examples/build-http/webpack.lock.data/https_jspm.dev/npm_jspm_core_2.0.0-beta_12b8110471722e74fcb6.11_nodelibs_process new file mode 100644 index 00000000000..203f79bb446 --- /dev/null +++ b/examples/build-http/webpack.lock.data/https_jspm.dev/npm_jspm_core_2.0.0-beta_12b8110471722e74fcb6.11_nodelibs_process @@ -0,0 +1,277 @@ +function unimplemented(name) { + throw new Error('Node.js process ' + name + ' is not supported by JSPM core outside of Node.js'); +} + +var queue = []; +var draining = false; +var currentQueue; +var queueIndex = -1; + +function cleanUpNextTick() { + if (!draining || !currentQueue) + return; + draining = false; + if (currentQueue.length) { + queue = currentQueue.concat(queue); + } + else { + queueIndex = -1; + } + if (queue.length) + drainQueue(); +} + +function drainQueue() { + if (draining) + return; + var timeout = setTimeout(cleanUpNextTick, 0); + draining = true; + + var len = queue.length; + while(len) { + currentQueue = queue; + queue = []; + while (++queueIndex < len) { + if (currentQueue) + currentQueue[queueIndex].run(); + } + queueIndex = -1; + len = queue.length; + } + currentQueue = null; + draining = false; + clearTimeout(timeout); +} + +function nextTick (fun) { + var args = new Array(arguments.length - 1); + if (arguments.length > 1) { + for (var i = 1; i < arguments.length; i++) + args[i - 1] = arguments[i]; + } + queue.push(new Item(fun, args)); + if (queue.length === 1 && !draining) + setTimeout(drainQueue, 0); +} +// v8 likes predictible objects +function Item(fun, array) { + this.fun = fun; + this.array = array; +} +Item.prototype.run = function () { + this.fun.apply(null, this.array); +}; + +var title = 'browser'; +var arch = 'x64'; +var platform = 'browser'; +var env = { + PATH: '/usr/bin', + LANG: navigator.language + '.UTF-8', + PWD: '/', + HOME: '/home', + TMP: '/tmp', +}; +var argv = ['/usr/bin/node']; +var execArgv = []; +var version = 'v16.8.0'; +var versions = { node: '16.8.0' }; + +var emitWarning = function(message, type) { + console.warn((type ? (type + ': ') : '') + message); +}; + +var binding = function(name) { unimplemented('binding'); }; + +var umask = function(mask) { return 0; }; + +var cwd = function() { return '/'; }; +var chdir = function(dir) {}; + +var release = { + name: 'node', + sourceUrl: '', + headersUrl: '', + libUrl: '', +}; + +function noop() {} + +var _rawDebug = noop; +var moduleLoadList = []; +function _linkedBinding(name) { unimplemented('_linkedBinding'); } +var domain = {}; +var _exiting = false; +var config = {}; +function dlopen(name) { unimplemented('dlopen'); } +function _getActiveRequests() { return []; } +function _getActiveHandles() { return []; } +var reallyExit = noop; +var _kill = noop; +var cpuUsage = function() { return {}; }; +var resourceUsage = cpuUsage; +var memoryUsage = cpuUsage; +var kill = noop; +var exit = noop; +var openStdin = noop; +var allowedNodeEnvironmentFlags = {}; +function assert(condition, message) { + if (!condition) throw new Error(message || 'assertion error'); +} +var features = { + inspector: false, + debug: false, + uv: false, + ipv6: false, + tls_alpn: false, + tls_sni: false, + tls_ocsp: false, + tls: false, + cached_builtins: true, +}; +var _fatalExceptions = noop; +var setUncaughtExceptionCaptureCallback = noop; +function hasUncaughtExceptionCaptureCallback() { return false; }var _tickCallback = noop; +var _debugProcess = noop; +var _debugEnd = noop; +var _startProfilerIdleNotifier = noop; +var _stopProfilerIdleNotifier = noop; +var stdout = undefined; +var stderr = undefined; +var stdin = undefined; +var abort = noop; +var pid = 2; +var ppid = 1; +var execPath = '/bin/usr/node'; +var debugPort = 9229; +var argv0 = 'node'; +var _preload_modules = []; +var setSourceMapsEnabled = noop; + +var _performance = { + now: typeof performance !== 'undefined' ? performance.now.bind(performance) : undefined, + timing: typeof performance !== 'undefined' ? performance.timing : undefined, +}; +if (_performance.now === undefined) { + var nowOffset = Date.now(); + + if (_performance.timing && _performance.timing.navigationStart) { + nowOffset = _performance.timing.navigationStart; + } + _performance.now = () => Date.now() - nowOffset; +} + +function uptime() { + return _performance.now() / 1000; +} + +var nanoPerSec = 1000000000; +function hrtime(previousTimestamp) { + var baseNow = Math.floor((Date.now() - _performance.now()) * 1e-3); + var clocktime = _performance.now() * 1e-3; + var seconds = Math.floor(clocktime) + baseNow; + var nanoseconds = Math.floor((clocktime % 1) * 1e9); + if (previousTimestamp) { + seconds = seconds - previousTimestamp[0]; + nanoseconds = nanoseconds - previousTimestamp[1]; + if (nanoseconds < 0) { + seconds--; + nanoseconds += nanoPerSec; + } + } + return [seconds, nanoseconds]; +}hrtime.bigint = function(time) { + var diff = hrtime(time); + if (typeof BigInt === 'undefined') { + return diff[0] * nanoPerSec + diff[1]; + } + return BigInt(diff[0] * nanoPerSec) + BigInt(diff[1]); +}; + +var _maxListeners = 10; +var _events = {}; +var _eventsCount = 0; +function on () { return process }var addListener = on; +var once = on; +var off = on; +var removeListener = on; +var removeAllListeners = on; +var emit = noop; +var prependListener = on; +var prependOnceListener = on; +function listeners (name) { return []; } +var process = { + version, + versions, + arch, + platform, + release, + _rawDebug, + moduleLoadList, + binding, + _linkedBinding, + _events, + _eventsCount, + _maxListeners, + on, + addListener, + once, + off, + removeListener, + removeAllListeners, + emit, + prependListener, + prependOnceListener, + listeners, + domain, + _exiting, + config, + dlopen, + uptime, + _getActiveRequests, + _getActiveHandles, + reallyExit, + _kill, + cpuUsage, + resourceUsage, + memoryUsage, + kill, + exit, + openStdin, + allowedNodeEnvironmentFlags, + assert, + features, + _fatalExceptions, + setUncaughtExceptionCaptureCallback, + hasUncaughtExceptionCaptureCallback, + emitWarning, + nextTick, + _tickCallback, + _debugProcess, + _debugEnd, + _startProfilerIdleNotifier, + _stopProfilerIdleNotifier, + stdout, + stdin, + stderr, + abort, + umask, + chdir, + cwd, + env, + title, + argv, + execArgv, + pid, + ppid, + execPath, + debugPort, + hrtime, + argv0, + _preload_modules, + setSourceMapsEnabled, +}; + +export { _debugEnd, _debugProcess, _events, _eventsCount, _exiting, _fatalExceptions, _getActiveHandles, _getActiveRequests, _kill, _linkedBinding, _maxListeners, _preload_modules, _rawDebug, _startProfilerIdleNotifier, _stopProfilerIdleNotifier, _tickCallback, abort, addListener, allowedNodeEnvironmentFlags, arch, argv, argv0, assert, binding, chdir, config, cpuUsage, cwd, debugPort, process as default, dlopen, domain, emit, emitWarning, env, execArgv, execPath, exit, features, hasUncaughtExceptionCaptureCallback, hrtime, kill, listeners, memoryUsage, moduleLoadList, nextTick, off, on, once, openStdin, pid, platform, ppid, prependListener, prependOnceListener, reallyExit, release, removeAllListeners, removeListener, resourceUsage, setSourceMapsEnabled, setUncaughtExceptionCaptureCallback, stderr, stdin, stdout, title, umask, uptime, version, versions }; + +//# sourceMappingURL=process.map \ No newline at end of file diff --git a/examples/build-http/webpack.lock.data/https_jspm.dev/npm_jspm_core_2.0.0-beta_1620e8f9e144fe702a06.11_nodelibs_os b/examples/build-http/webpack.lock.data/https_jspm.dev/npm_jspm_core_2.0.0-beta_1620e8f9e144fe702a06.11_nodelibs_os new file mode 100644 index 00000000000..65ca57a8711 --- /dev/null +++ b/examples/build-http/webpack.lock.data/https_jspm.dev/npm_jspm_core_2.0.0-beta_1620e8f9e144fe702a06.11_nodelibs_os @@ -0,0 +1,113 @@ +import { uptime } from './process'; +export { uptime } from './process'; + +var exports = {}, + _dewExec = false; +function dew() { + if (_dewExec) return exports; + _dewExec = true; + + exports.endianness = function () { + return "LE"; + }; + + exports.hostname = function () { + if (typeof location !== "undefined") { + return location.hostname; + } else return ""; + }; + + exports.loadavg = function () { + return []; + }; + + exports.uptime = function () { + return 0; + }; + + exports.freemem = function () { + return Number.MAX_VALUE; + }; + + exports.totalmem = function () { + return Number.MAX_VALUE; + }; + + exports.cpus = function () { + return []; + }; + + exports.type = function () { + return "Browser"; + }; + + exports.release = function () { + if (typeof navigator !== "undefined") { + return navigator.appVersion; + } + + return ""; + }; + + exports.networkInterfaces = exports.getNetworkInterfaces = function () { + return {}; + }; + + exports.arch = function () { + return "javascript"; + }; + + exports.platform = function () { + return "browser"; + }; + + exports.tmpdir = exports.tmpDir = function () { + return "/tmp"; + }; + + exports.EOL = "\n"; + + exports.homedir = function () { + return "/"; + }; + + return exports; +} + +var os = dew(); + +var _endianness = new Uint8Array(new Uint16Array([1]).buffer)[0] === 1 ? 'LE' : 'BE'; +os.endianness = function() { return _endianness; }; +os.homedir = function() { return '/home'; }; +os.version = function() { return ''; }; +os.arch = function() { return 'x64'; }; +os.totalmem = function() { + return navigator.deviceMemory !== undefined ? navigator.deviceMemory * (1 << 30) : 2 * (1 << 30); +}; +os.cpus = function () { + return Array(navigator.hardwareConcurrency || 0).fill({ model: '', times: {} }); +}; +os.uptime = uptime; +os.constants = {}; +var version = os.version; +var constants = os.constants; +var EOL = os.EOL; +var arch = os.arch; +var cpus = os.cpus; +var endianness = os.endianness; +var freemem = os.freemem; +var getNetworkInterfaces = os.getNetworkInterfaces; +var homedir = os.homedir; +var hostname = os.hostname; +var loadavg = os.loadavg; +var networkInterfaces = os.networkInterfaces; +var platform = os.platform; +var release = os.release; +var tmpDir = os.tmpDir; +var tmpdir = os.tmpdir; +var totalmem = os.totalmem; +var type = os.type; + +export { EOL, arch, constants, cpus, os as default, endianness, freemem, getNetworkInterfaces, homedir, hostname, loadavg, networkInterfaces, platform, release, tmpDir, tmpdir, totalmem, type, version }; + +//# sourceMappingURL=os.map \ No newline at end of file diff --git a/examples/build-http/webpack.lock.data/https_jspm.dev/npm_jspm_core_2_nodelibs_os_3fe9447e10c5fed754bb b/examples/build-http/webpack.lock.data/https_jspm.dev/npm_jspm_core_2_nodelibs_os_3fe9447e10c5fed754bb new file mode 100644 index 00000000000..4accb6487ef --- /dev/null +++ b/examples/build-http/webpack.lock.data/https_jspm.dev/npm_jspm_core_2_nodelibs_os_3fe9447e10c5fed754bb @@ -0,0 +1,3 @@ +import "/npm:@jspm/core@2.0.0-beta.11/nodelibs/process"; +export * from "/npm:@jspm/core@2.0.0-beta.11/nodelibs/os"; +export { default } from "/npm:@jspm/core@2.0.0-beta.11/nodelibs/os"; diff --git a/examples/build-http/webpack.lock.data/https_jspm.dev/npm_p-map_5.1_9895e1a83d37d06ab277.0 b/examples/build-http/webpack.lock.data/https_jspm.dev/npm_p-map_5.1_9895e1a83d37d06ab277.0 new file mode 100644 index 00000000000..5166d74476e --- /dev/null +++ b/examples/build-http/webpack.lock.data/https_jspm.dev/npm_p-map_5.1_9895e1a83d37d06ab277.0 @@ -0,0 +1,103 @@ +import AggregateError from './npm:aggregate-error@4'; + +async function pMap( + iterable, + mapper, + { + concurrency = Number.POSITIVE_INFINITY, + stopOnError = true + } = {} +) { + return new Promise((resolve, reject) => { + if (typeof mapper !== 'function') { + throw new TypeError('Mapper function is required'); + } + + if (!((Number.isSafeInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency >= 1)) { + throw new TypeError(`Expected \`concurrency\` to be an integer from 1 and up or \`Infinity\`, got \`${concurrency}\` (${typeof concurrency})`); + } + + const result = []; + const errors = []; + const skippedIndexes = []; + const iterator = iterable[Symbol.iterator](); + let isRejected = false; + let isIterableDone = false; + let resolvingCount = 0; + let currentIndex = 0; + + const next = () => { + if (isRejected) { + return; + } + + const nextItem = iterator.next(); + const index = currentIndex; + currentIndex++; + + if (nextItem.done) { + isIterableDone = true; + + if (resolvingCount === 0) { + if (!stopOnError && errors.length > 0) { + reject(new AggregateError(errors)); + } else { + for (const skippedIndex of skippedIndexes) { + result.splice(skippedIndex, 1); + } + + resolve(result); + } + } + + return; + } + + resolvingCount++; + + (async () => { + try { + const element = await nextItem.value; + + if (isRejected) { + return; + } + + const value = await mapper(element, index); + if (value === pMapSkip) { + skippedIndexes.push(index); + } else { + result[index] = value; + } + + resolvingCount--; + next(); + } catch (error) { + if (stopOnError) { + isRejected = true; + reject(error); + } else { + errors.push(error); + resolvingCount--; + next(); + } + } + })(); + }; + + for (let index = 0; index < concurrency; index++) { + next(); + + if (isIterableDone) { + break; + } + } + }); +} + +const pMapSkip = Symbol('skip'); + +export default pMap; +export { pMapSkip }; + +//# sourceMappingURL=npm:p-map@5.1.0.map \ No newline at end of file diff --git a/examples/build-http/webpack.lock.data/https_jspm.dev/p-map_875efed0b6bd20646dd2 b/examples/build-http/webpack.lock.data/https_jspm.dev/p-map_875efed0b6bd20646dd2 new file mode 100644 index 00000000000..95b490c4578 --- /dev/null +++ b/examples/build-http/webpack.lock.data/https_jspm.dev/p-map_875efed0b6bd20646dd2 @@ -0,0 +1,3 @@ +import "/npm:aggregate-error@4"; +export * from "/npm:p-map@5.1.0"; +export { default } from "/npm:p-map@5.1.0"; diff --git a/examples/build-http/webpack.lock.data/https_unpkg.com/p-map-series_3.0.0_index_module_cb329557880410b778cf.js b/examples/build-http/webpack.lock.data/https_unpkg.com/p-map-series_3.0.0_index_module_cb329557880410b778cf.js new file mode 100644 index 00000000000..f9ee01a45ae --- /dev/null +++ b/examples/build-http/webpack.lock.data/https_unpkg.com/p-map-series_3.0.0_index_module_cb329557880410b778cf.js @@ -0,0 +1,11 @@ +export default async function pMapSeries(iterable, mapper) { + const result = []; + let index = 0; + + for (const value of iterable) { + // eslint-disable-next-line no-await-in-loop + result.push((await mapper((await value), index++))); + } + + return result; +} \ No newline at end of file diff --git a/examples/buildAll.js b/examples/buildAll.js index 7ff3a9e38f2..0f05b4c5c7f 100644 --- a/examples/buildAll.js +++ b/examples/buildAll.js @@ -3,21 +3,27 @@ const cp = require("child_process"); const examples = require("./examples"); -const cmds = examples.map(function(dirname) { - return "cd " + dirname + " && node build.js"; -}); +const commands = [ + ...examples, + examples.filter((dirname) => dirname.includes("persistent-caching")) +].map((dirname) => `cd ${dirname} && node build.js`); let failed = 0; let i = 0; -for(const cmd of cmds) { - console.log(`[${++i}/${cmds.length}] ${cmd}`); + +for (const cmd of commands) { + console.log(`[${++i}/${commands.length}] ${cmd}`); + try { - cp.execSync(cmd, { encoding: "utf-8" }); - } catch(e) { + cp.execSync(cmd, { encoding: "utf8" }); + } catch (err) { failed++; - console.log(e); + console.log(err); } } + console.log("done"); -if(failed > 0) - console.log(`${failed} failed`); + +if (failed > 0) { + throw new Error(`${failed} examples failed`); +} diff --git a/examples/chunkhash/README.md b/examples/chunkhash/README.md index e02b81676dc..306cfc54efe 100644 --- a/examples/chunkhash/README.md +++ b/examples/chunkhash/README.md @@ -1,15 +1,15 @@ -A common challenge with combining `[chunkhash]` and Code Splitting is that the entry chunk includes the webpack runtime and with it the chunkhash mappings. This means it's always updated and the `[chunkhash]` is pretty useless, because this chunk won't be cached. +A common challenge with combining `[chunkhash]` and Code Splitting is that the entry chunk includes the webpack runtime and with it the chunkhash mappings. This means it's always updated and the `[chunkhash]` is pretty useless because this chunk won't be cached. -A very simple solution to this problem is to create another chunk which contains only the webpack runtime (including chunkhash map). This can be achieved with the `optimization.runtimeChunk` options. To avoid the additional request for another chunk, this pretty small chunk can be inlined into the HTML page. +A very simple solution to this problem is to create another chunk that contains only the webpack runtime (including chunkhash map). This can be achieved with `optimization.runtimeChunk` options. To avoid the additional request for another chunk, this pretty small chunk can be inlined into the HTML page. The configuration required for this is: -* use `[chunkhash]` in `output.filename` (Note that this example doesn't do this because of the example generator infrastructure, but you should) -* use `[chunkhash]` in `output.chunkFilename` (Note that this example doesn't do this because of the example generator infrastructure, but you should) +- use `[chunkhash]` in `output.filename` (Note that this example doesn't do this because of the example generator infrastructure, but you should) +- use `[chunkhash]` in `output.chunkFilename` (Note that this example doesn't do this because of the example generator infrastructure, but you should) # example.js -``` javascript +```javascript // some module import("./async1"); import("./async2"); @@ -17,10 +17,14 @@ import("./async2"); # webpack.config.js -``` javascript -var path = require("path"); -module.exports = { - // mode: "development || "production", +```javascript +"use strict"; + +const path = require("path"); + +/** @type {import("webpack").Configuration} */ +const config = { + // mode: "development" || "production", entry: { main: "./example" }, @@ -33,259 +37,358 @@ module.exports = { chunkFilename: "[name].[chunkhash].js" } }; + +module.exports = config; ``` # index.html -``` html +```html - - - - - - + + + + - - - + + ``` # dist/runtime~main.[chunkhash].js -
/******/ (function(modules) { /* webpackBootstrap */ }) +```javascript +/******/ (() => { // webpackBootstrap +/******/ "use strict"; +/******/ var __webpack_modules__ = ({}); +``` -``` javascript -/******/ (function(modules) { // webpackBootstrap -/******/ // install a JSONP callback for chunk loading -/******/ function webpackJsonpCallback(data) { -/******/ var chunkIds = data[0]; -/******/ var moreModules = data[1]; -/******/ var executeModules = data[2]; -/******/ // add "moreModules" to the modules object, -/******/ // then flag all "chunkIds" as loaded and fire callback -/******/ var moduleId, chunkId, i = 0, resolves = []; -/******/ for(;i < chunkIds.length; i++) { -/******/ chunkId = chunkIds[i]; -/******/ if(installedChunks[chunkId]) { -/******/ resolves.push(installedChunks[chunkId][0]); -/******/ } -/******/ installedChunks[chunkId] = 0; -/******/ } -/******/ for(moduleId in moreModules) { -/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { -/******/ modules[moduleId] = moreModules[moduleId]; -/******/ } -/******/ } -/******/ if(parentJsonpFunction) parentJsonpFunction(data); -/******/ while(resolves.length) { -/******/ resolves.shift()(); -/******/ } -/******/ -/******/ // add entry modules from loaded chunk to deferred list -/******/ deferredModules.push.apply(deferredModules, executeModules || []); -/******/ -/******/ // run deferred modules when all chunks ready -/******/ return checkDeferredModules(); -/******/ }; -/******/ function checkDeferredModules() { -/******/ var result; -/******/ for(var i = 0; i < deferredModules.length; i++) { -/******/ var deferredModule = deferredModules[i]; -/******/ var fulfilled = true; -/******/ for(var j = 1; j < deferredModule.length; j++) { -/******/ var depId = deferredModule[j]; -/******/ if(installedChunks[depId] !== 0) fulfilled = false; -/******/ } -/******/ if(fulfilled) { -/******/ deferredModules.splice(i--, 1); -/******/ result = __webpack_require__(__webpack_require__.s = deferredModule[0]); -/******/ } -/******/ } -/******/ return result; -/******/ } -/******/ +
/* webpack runtime code */ + +``` js +/************************************************************************/ /******/ // The module cache -/******/ var installedModules = {}; -/******/ -/******/ // object to store loaded and loading chunks -/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched -/******/ // Promise = chunk loading, 0 = chunk loaded -/******/ var installedChunks = { -/******/ 3: 0 -/******/ }; -/******/ -/******/ var deferredModules = []; -/******/ -/******/ // script path function -/******/ function jsonpScriptSrc(chunkId) { -/******/ return __webpack_require__.p + "" + ({}[chunkId]||chunkId) + ".[chunkhash].js" -/******/ } -/******/ +/******/ const __webpack_module_cache__ = {}; +/******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { -/******/ /******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) { -/******/ return installedModules[moduleId].exports; +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, +/******/ const module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed /******/ exports: {} /******/ }; -/******/ +/******/ /******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } -/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = __webpack_modules__; +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/chunk loaded */ +/******/ (() => { +/******/ const deferred = []; +/******/ __webpack_require__.O = (result, chunkIds, fn, priority) => { +/******/ if(chunkIds) { +/******/ priority = priority || 0; +/******/ for(var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1]; +/******/ deferred[i] = [chunkIds, fn, priority]; +/******/ return; +/******/ } +/******/ let notFulfilled = Infinity; +/******/ for (var i = 0; i < deferred.length; i++) { +/******/ let [chunkIds, fn, priority] = deferred[i]; +/******/ let fulfilled = true; +/******/ for (var j = 0; j < chunkIds.length; j++) { +/******/ if ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every((key) => (__webpack_require__.O[key](chunkIds[j])))) { +/******/ chunkIds.splice(j--, 1); +/******/ } else { +/******/ fulfilled = false; +/******/ if(priority < notFulfilled) notFulfilled = priority; +/******/ } +/******/ } +/******/ if(fulfilled) { +/******/ deferred.splice(i--, 1) +/******/ const r = fn(); +/******/ if (r !== undefined) result = r; +/******/ } +/******/ } +/******/ return result; +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/create fake namespace object */ +/******/ (() => { +/******/ const getProto = Object.getPrototypeOf ? (obj) => (Object.getPrototypeOf(obj)) : (obj) => (obj.__proto__); +/******/ let leafPrototypes; +/******/ // 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 & 16: return value when it's Promise-like +/******/ // mode & 8|1: behave like require +/******/ __webpack_require__.t = function(value, mode) { +/******/ if(mode & 1) value = this(value); +/******/ if(mode & 8) return value; +/******/ if(typeof value === 'object' && value) { +/******/ if((mode & 4) && value.__esModule) return value; +/******/ if((mode & 16) && typeof value.then === 'function') return value; +/******/ } +/******/ const ns = Object.create(null); +/******/ __webpack_require__.r(ns); +/******/ const def = {}; +/******/ leafPrototypes = leafPrototypes || [null, getProto({}), getProto([]), getProto(getProto)]; +/******/ for(var current = mode & 2 && value; (typeof current == 'object' || typeof current == 'function') && !~leafPrototypes.indexOf(current); current = getProto(current)) { +/******/ Object.getOwnPropertyNames(current).forEach((key) => (def[key] = () => (value[key]))); +/******/ } +/******/ def['default'] = () => (value); +/******/ __webpack_require__.d(ns, def); +/******/ return ns; +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/define property getters */ +/******/ // define getter/value functions for harmony exports +/******/ __webpack_require__.d = (exports, definition) => { +/******/ if(Array.isArray(definition)) { +/******/ var i = 0; +/******/ while(i < definition.length) { +/******/ var key = definition[i++]; +/******/ var binding = definition[i++]; +/******/ if(!__webpack_require__.o(exports, key)) { +/******/ if(binding === 0) { +/******/ Object.defineProperty(exports, key, { enumerable: true, value: definition[i++] }); +/******/ } else { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: binding }); +/******/ } +/******/ } else if(binding === 0) { i++; } +/******/ } +/******/ } else { +/******/ for(var key in definition) { +/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); +/******/ } +/******/ } +/******/ } +/******/ }; +/******/ +/******/ /* webpack/runtime/ensure chunk */ +/******/ __webpack_require__.f = {}; /******/ // This file contains only the entry chunk. /******/ // The chunk loading function for additional chunks -/******/ __webpack_require__.e = function requireEnsure(chunkId) { -/******/ var promises = []; -/******/ -/******/ -/******/ // JSONP chunk loading for javascript -/******/ -/******/ var installedChunkData = installedChunks[chunkId]; -/******/ if(installedChunkData !== 0) { // 0 means "already installed". -/******/ -/******/ // a Promise means "currently loading". -/******/ if(installedChunkData) { -/******/ promises.push(installedChunkData[2]); -/******/ } else { -/******/ // setup Promise in chunk cache -/******/ var promise = new Promise(function(resolve, reject) { -/******/ installedChunkData = installedChunks[chunkId] = [resolve, reject]; -/******/ }); -/******/ promises.push(installedChunkData[2] = promise); -/******/ -/******/ // start chunk loading -/******/ var head = document.getElementsByTagName('head')[0]; -/******/ var script = document.createElement('script'); -/******/ +/******/ __webpack_require__.e = (chunkId) => { +/******/ return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => { +/******/ __webpack_require__.f[key](chunkId, promises); +/******/ return promises; +/******/ }, [])); +/******/ }; +/******/ +/******/ /* webpack/runtime/get javascript chunk filename */ +/******/ // This function allow to reference async chunks +/******/ __webpack_require__.u = (chunkId) => { +/******/ // return url for filenames based on template +/******/ return "" + chunkId + ".[chunkhash].js"; +/******/ }; +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)); +/******/ +/******/ /* webpack/runtime/load script */ +/******/ (() => { +/******/ const inProgress = {}; +/******/ // data-webpack is not used as build has no uniqueName +/******/ // loadScript function to load a script via script tag +/******/ __webpack_require__.l = (url, done, key, chunkId) => { +/******/ if(inProgress[url]) { inProgress[url].push(done); return; } +/******/ let script, needAttach; +/******/ if(key !== undefined) { +/******/ const scripts = document.getElementsByTagName("script"); +/******/ for(var i = 0; i < scripts.length; i++) { +/******/ const s = scripts[i]; +/******/ if(s.getAttribute("src") == url) { script = s; break; } +/******/ } +/******/ } +/******/ if(!script) { +/******/ needAttach = true; +/******/ script = document.createElement('script'); +/******/ /******/ script.charset = 'utf-8'; -/******/ script.timeout = 120; -/******/ /******/ if (__webpack_require__.nc) { /******/ script.setAttribute("nonce", __webpack_require__.nc); /******/ } -/******/ script.src = jsonpScriptSrc(chunkId); -/******/ var timeout = setTimeout(function(){ -/******/ onScriptComplete({ type: 'timeout', target: script }); -/******/ }, 120000); -/******/ script.onerror = script.onload = onScriptComplete; -/******/ function onScriptComplete(event) { -/******/ // avoid mem leaks in IE. -/******/ script.onerror = script.onload = null; -/******/ clearTimeout(timeout); -/******/ var chunk = installedChunks[chunkId]; -/******/ if(chunk !== 0) { -/******/ if(chunk) { -/******/ var errorType = event && (event.type === 'load' ? 'missing' : event.type); -/******/ var realSrc = event && event.target && event.target.src; -/******/ var error = new Error('Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'); -/******/ error.type = errorType; -/******/ error.request = realSrc; -/******/ chunk[1](error); -/******/ } -/******/ installedChunks[chunkId] = undefined; -/******/ } -/******/ }; -/******/ head.appendChild(script); +/******/ +/******/ +/******/ script.src = url; /******/ } -/******/ } -/******/ return Promise.all(promises); -/******/ }; -/******/ -/******/ // 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, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); -/******/ } -/******/ }; -/******/ +/******/ inProgress[url] = [done]; +/******/ const onScriptComplete = (prev, event) => { +/******/ // avoid mem leaks in IE. +/******/ script.onerror = script.onload = null; +/******/ clearTimeout(timeout); +/******/ const doneFns = inProgress[url]; +/******/ delete inProgress[url]; +/******/ script.parentNode?.removeChild(script); +/******/ doneFns?.forEach((fn) => (fn(event))); +/******/ if(prev) return prev(event); +/******/ } +/******/ const timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000); +/******/ script.onerror = onScriptComplete.bind(null, script.onerror); +/******/ script.onload = onScriptComplete.bind(null, script.onload); +/******/ needAttach && document.head.appendChild(script); +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/make namespace object */ /******/ // define __esModule on exports -/******/ __webpack_require__.r = function(exports) { +/******/ __webpack_require__.r = (exports) => { +/******/ if(Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } /******/ Object.defineProperty(exports, '__esModule', { value: true }); /******/ }; -/******/ -/******/ // 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/runtime/publicPath */ /******/ __webpack_require__.p = "dist/"; -/******/ -/******/ // on error function for async loading -/******/ __webpack_require__.oe = function(err) { console.error(err); throw err; }; -/******/ -/******/ var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || []; -/******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); -/******/ jsonpArray.push = webpackJsonpCallback; -/******/ jsonpArray = jsonpArray.slice(); -/******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]); -/******/ var parentJsonpFunction = oldJsonpFunction; -/******/ -/******/ -/******/ // run deferred modules from other chunks -/******/ checkDeferredModules(); -/******/ }) +/******/ +/******/ /* webpack/runtime/jsonp chunk loading */ +/******/ (() => { +/******/ // no baseURI +/******/ +/******/ // object to store loaded and loading chunks +/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched +/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded +/******/ const installedChunks = { +/******/ 1: 0 +/******/ }; +/******/ +/******/ __webpack_require__.f.j = (chunkId, promises) => { +/******/ // JSONP chunk loading for javascript +/******/ let installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined; +/******/ if(installedChunkData !== 0) { // 0 means "already installed". +/******/ +/******/ // a Promise means "currently loading". +/******/ if(installedChunkData) { +/******/ promises.push(installedChunkData[2]); +/******/ } else { +/******/ if(1 != chunkId) { +/******/ // setup Promise in chunk cache +/******/ const promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject])); +/******/ promises.push(installedChunkData[2] = promise); +/******/ +/******/ // start chunk loading +/******/ const url = __webpack_require__.p + __webpack_require__.u(chunkId); +/******/ // create error before stack unwound to get useful stacktrace later +/******/ const error = new Error(); +/******/ const loadingEnded = (event) => { +/******/ if(__webpack_require__.o(installedChunks, chunkId)) { +/******/ installedChunkData = installedChunks[chunkId]; +/******/ if(installedChunkData !== 0) installedChunks[chunkId] = undefined; +/******/ if(installedChunkData) { +/******/ const errorType = event && (event.type === 'load' ? 'missing' : event.type); +/******/ const realSrc = event && event.target && event.target.src; +/******/ error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'; +/******/ error.name = 'ChunkLoadError'; +/******/ error.type = errorType; +/******/ error.request = realSrc; +/******/ error.event = event; +/******/ installedChunkData[1](error); +/******/ } +/******/ } +/******/ }; +/******/ __webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId); +/******/ } else installedChunks[chunkId] = 0; +/******/ } +/******/ } +/******/ }; +/******/ +/******/ // no prefetching +/******/ +/******/ // no preloaded +/******/ +/******/ // no HMR +/******/ +/******/ // no HMR manifest +/******/ +/******/ __webpack_require__.O.j = (chunkId) => (installedChunks[chunkId] === 0); +/******/ +/******/ // install a JSONP callback for chunk loading +/******/ const webpackJsonpCallback = (parentChunkLoadingFunction, data) => { +/******/ let [chunkIds, moreModules, runtime] = data; +/******/ // add "moreModules" to the modules object, +/******/ // then flag all "chunkIds" as loaded and fire callback +/******/ var moduleId, chunkId, i = 0; +/******/ if(chunkIds.some((id) => (installedChunks[id] !== 0))) { +/******/ for(moduleId in moreModules) { +/******/ if(__webpack_require__.o(moreModules, moduleId)) { +/******/ __webpack_require__.m[moduleId] = moreModules[moduleId]; +/******/ } +/******/ } +/******/ if(runtime) var result = runtime(__webpack_require__); +/******/ } +/******/ if(parentChunkLoadingFunction) parentChunkLoadingFunction(data); +/******/ for(;i < chunkIds.length; i++) { +/******/ chunkId = chunkIds[i]; +/******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) { +/******/ installedChunks[chunkId][0](); +/******/ } +/******/ installedChunks[chunkId] = 0; +/******/ } +/******/ return __webpack_require__.O(result); +/******/ } +/******/ +/******/ const chunkLoadingGlobal = self["webpackChunk"] = self["webpackChunk"] || []; +/******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0)); +/******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal)); +/******/ })(); +/******/ /************************************************************************/ ```
-``` javascript -/******/ ([]); +``` js +/******/ +/******/ +/******/ })() +; ``` # dist/main.[chunkhash].js -``` javascript -(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0],[ +```javascript +(self["webpackChunk"] = self["webpackChunk"] || []).push([[0],[ /* 0 */ /*!********************!*\ !*** ./example.js ***! \********************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: __webpack_require__.e, __webpack_require__.t, __webpack_require__.* */ +/***/ ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => { // some module -__webpack_require__.e(/*! import() */ 1).then(function() { var module = __webpack_require__(/*! ./async1 */ 1); return typeof module === "object" && module && module.__esModule ? module : Object.assign({/* fake namespace object */}, typeof module === "object" && module, { "default": module }); }); -__webpack_require__.e(/*! import() */ 2).then(function() { var module = __webpack_require__(/*! ./async2 */ 2); return typeof module === "object" && module && module.__esModule ? module : Object.assign({/* fake namespace object */}, typeof module === "object" && module, { "default": module }); }); +__webpack_require__.e(/*! import() */ 2).then(__webpack_require__.t.bind(__webpack_require__, /*! ./async1 */ 1, 23)); +__webpack_require__.e(/*! import() */ 3).then(__webpack_require__.t.bind(__webpack_require__, /*! ./async2 */ 2, 23)); /***/ }) -],[[0,3]]]); +], +/******/ __webpack_require__ => { // webpackRuntimeModules +/******/ var __webpack_exec__ = (moduleId) => (__webpack_require__(__webpack_require__.s = moduleId)) +/******/ var __webpack_exports__ = (__webpack_exec__(0)); +/******/ } +]); ``` # Info @@ -293,53 +396,57 @@ __webpack_require__.e(/*! import() */ 2).then(function() { var module = __webpac ## Unoptimized ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 - Asset Size Chunks Chunk Names - main.[chunkhash].js 877 bytes 0 [emitted] main - 1.[chunkhash].js 270 bytes 1 [emitted] - 2.[chunkhash].js 264 bytes 2 [emitted] -runtime~main.[chunkhash].js 7.75 KiB 3 [emitted] runtime~main -Entrypoint main = runtime~main.[chunkhash].js main.[chunkhash].js -chunk {0} main.[chunkhash].js (main) 55 bytes ={3}= >{1}< >{2}< [initial] [rendered] - > ./example main - [0] ./example.js 55 bytes {0} [built] - single entry ./example main -chunk {1} 1.[chunkhash].js 29 bytes <{0}> <{3}> [rendered] - > ./async1 [0] ./example.js 2:0-18 - [1] ./async1.js 29 bytes {1} [built] - import() ./async1 [0] ./example.js 2:0-18 -chunk {2} 2.[chunkhash].js 29 bytes <{0}> <{3}> [rendered] - > ./async2 [0] ./example.js 3:0-18 - [2] ./async2.js 29 bytes {2} [built] - import() ./async2 [0] ./example.js 3:0-18 -chunk {3} runtime~main.[chunkhash].js (runtime~main) 0 bytes ={0}= >{1}< >{2}< [entry] [rendered] - > ./example main +asset runtime~main.[chunkhash].js 12.6 KiB [emitted] (name: runtime~main) +asset main.[chunkhash].js 873 bytes [emitted] (name: main) +asset 2.[chunkhash].js 285 bytes [emitted] +asset 3.[chunkhash].js 267 bytes [emitted] +Entrypoint main 13.4 KiB = runtime~main.[chunkhash].js 12.6 KiB main.[chunkhash].js 873 bytes +chunk (runtime: runtime~main) main.[chunkhash].js (main) 55 bytes [initial] [rendered] + > ./example main + ./example.js 55 bytes [built] [code generated] + [used exports unknown] + entry ./example main +chunk (runtime: runtime~main) runtime~main.[chunkhash].js (runtime~main) 8.06 KiB [entry] [rendered] + > ./example main + runtime modules 8.06 KiB 10 modules +chunk (runtime: runtime~main) 2.[chunkhash].js 28 bytes [rendered] + > ./async1 ./example.js 2:0-18 + ./async1.js 28 bytes [built] [code generated] + [used exports unknown] + import() ./async1 ./example.js 2:0-18 +chunk (runtime: runtime~main) 3.[chunkhash].js 28 bytes [rendered] + > ./async2 ./example.js 3:0-18 + ./async2.js 28 bytes [built] [code generated] + [used exports unknown] + import() ./async2 ./example.js 3:0-18 +webpack X.X.X compiled successfully ``` ## Production mode ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 - Asset Size Chunks Chunk Names - 0.[chunkhash].js 77 bytes 0 [emitted] - 1.[chunkhash].js 78 bytes 1 [emitted] -runtime~main.[chunkhash].js 1.79 KiB 2 [emitted] runtime~main - main.[chunkhash].js 349 bytes 3 [emitted] main -Entrypoint main = runtime~main.[chunkhash].js main.[chunkhash].js -chunk {0} 0.[chunkhash].js 29 bytes <{2}> <{3}> [rendered] - > ./async2 [0] ./example.js 3:0-18 - [1] ./async2.js 29 bytes {0} [built] - import() ./async2 [0] ./example.js 3:0-18 -chunk {1} 1.[chunkhash].js 29 bytes <{2}> <{3}> [rendered] - > ./async1 [0] ./example.js 2:0-18 - [2] ./async1.js 29 bytes {1} [built] - import() ./async1 [0] ./example.js 2:0-18 -chunk {2} runtime~main.[chunkhash].js (runtime~main) 0 bytes ={3}= >{0}< >{1}< [entry] [rendered] - > ./example main -chunk {3} main.[chunkhash].js (main) 55 bytes ={2}= >{0}< >{1}< [initial] [rendered] - > ./example main - [0] ./example.js 55 bytes {3} [built] - single entry ./example main +asset runtime~main.[chunkhash].js 3 KiB [emitted] [minimized] (name: runtime~main) +asset main.[chunkhash].js 152 bytes [emitted] [minimized] (name: main) +asset 471.[chunkhash].js 66 bytes [emitted] [minimized] +asset 18.[chunkhash].js 64 bytes [emitted] [minimized] +Entrypoint main 3.15 KiB = runtime~main.[chunkhash].js 3 KiB main.[chunkhash].js 152 bytes +chunk (runtime: runtime~main) 18.[chunkhash].js 28 bytes [rendered] + > ./async1 ./example.js 2:0-18 + ./async1.js 28 bytes [built] [code generated] + [used exports unknown] + import() ./async1 ./example.js 2:0-18 +chunk (runtime: runtime~main) runtime~main.[chunkhash].js (runtime~main) 8.06 KiB [entry] [rendered] + > ./example main + runtime modules 8.06 KiB 10 modules +chunk (runtime: runtime~main) 471.[chunkhash].js 28 bytes [rendered] + > ./async2 ./example.js 3:0-18 + ./async2.js 28 bytes [built] [code generated] + [used exports unknown] + import() ./async2 ./example.js 3:0-18 +chunk (runtime: runtime~main) main.[chunkhash].js (main) 55 bytes [initial] [rendered] + > ./example main + ./example.js 55 bytes [built] [code generated] + [no exports used] + entry ./example main +webpack X.X.X compiled successfully ``` diff --git a/examples/chunkhash/template.md b/examples/chunkhash/template.md index b7df1a7272b..91cf1c69b9a 100644 --- a/examples/chunkhash/template.md +++ b/examples/chunkhash/template.md @@ -1,53 +1,50 @@ -A common challenge with combining `[chunkhash]` and Code Splitting is that the entry chunk includes the webpack runtime and with it the chunkhash mappings. This means it's always updated and the `[chunkhash]` is pretty useless, because this chunk won't be cached. +A common challenge with combining `[chunkhash]` and Code Splitting is that the entry chunk includes the webpack runtime and with it the chunkhash mappings. This means it's always updated and the `[chunkhash]` is pretty useless because this chunk won't be cached. -A very simple solution to this problem is to create another chunk which contains only the webpack runtime (including chunkhash map). This can be achieved with the `optimization.runtimeChunk` options. To avoid the additional request for another chunk, this pretty small chunk can be inlined into the HTML page. +A very simple solution to this problem is to create another chunk that contains only the webpack runtime (including chunkhash map). This can be achieved with `optimization.runtimeChunk` options. To avoid the additional request for another chunk, this pretty small chunk can be inlined into the HTML page. The configuration required for this is: -* use `[chunkhash]` in `output.filename` (Note that this example doesn't do this because of the example generator infrastructure, but you should) -* use `[chunkhash]` in `output.chunkFilename` (Note that this example doesn't do this because of the example generator infrastructure, but you should) +- use `[chunkhash]` in `output.filename` (Note that this example doesn't do this because of the example generator infrastructure, but you should) +- use `[chunkhash]` in `output.chunkFilename` (Note that this example doesn't do this because of the example generator infrastructure, but you should) # example.js -``` javascript -{{example.js}} +```javascript +_{{example.js}}_ ``` # webpack.config.js -``` javascript -{{webpack.config.js}} +```javascript +_{{webpack.config.js}}_ ``` # index.html -``` html +```html - - - - - - - - - - + + + + + + + ``` # dist/runtime~main.[chunkhash].js -``` javascript -{{dist/runtime~main.chunkhash.js}} +```javascript +_{{dist/runtime~main.chunkhash.js}}_ ``` # dist/main.[chunkhash].js -``` javascript -{{dist/main.chunkhash.js}} +```javascript +_{{dist/main.chunkhash.js}}_ ``` # Info @@ -55,11 +52,11 @@ The configuration required for this is: ## Unoptimized ``` -{{stdout}} +_{{stdout}}_ ``` ## Production mode ``` -{{production:stdout}} +_{{production:stdout}}_ ``` diff --git a/examples/chunkhash/webpack.config.js b/examples/chunkhash/webpack.config.js index cc34d5591f9..d8034190f54 100644 --- a/examples/chunkhash/webpack.config.js +++ b/examples/chunkhash/webpack.config.js @@ -1,6 +1,10 @@ -var path = require("path"); -module.exports = { - // mode: "development || "production", +"use strict"; + +const path = require("path"); + +/** @type {import("webpack").Configuration} */ +const config = { + // mode: "development" || "production", entry: { main: "./example" }, @@ -13,3 +17,5 @@ module.exports = { chunkFilename: "[name].chunkhash.js" } }; + +module.exports = config; diff --git a/examples/cjs-tree-shaking/README.md b/examples/cjs-tree-shaking/README.md new file mode 100644 index 00000000000..237e32e3adf --- /dev/null +++ b/examples/cjs-tree-shaking/README.md @@ -0,0 +1,248 @@ +This example demonstrates how Webpack performs tree shaking for CommonJS modules. + +# example.js + +```javascript +// Property access pattern +const inc = require("./increment").increment; +var a = 1; +inc(a); // 2 + +// Destructuring assignment pattern +const { add } = require("./math"); +add(a, 2); // 3 + +// Aliased destructuring +const { increment: inc2 } = require("./increment"); +inc2(a); // 2 +``` + +# increment.js + +```javascript +const add = require("./math").add; +exports.increment = function increment(val) { + return add(val, 1); +}; +exports.incrementBy2 = function incrementBy2(val) { + return add(val, 2); +}; +exports.decrement = function decrement(val) { + return add(val, 1); +}; +``` + +# math.js + +```javascript +exports.add = function add() { + var sum = 0, + i = 0, + args = arguments, + l = args.length; + while (i < l) { + sum += args[i++]; + } + return sum; +}; + +exports.multiply = function multiply() { + var product = 0, + i = 0, + args = arguments, + l = args.length; + while (i < l) { + sum *= args[i++]; + } + return sum; +}; +``` + +# dist/output.js + +```javascript +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ([ +/* 0 */ +/*!*****************!*\ + !*** ./math.js ***! + \*****************/ +/*! default exports */ +/*! export add [provided] [used in main] [usage prevents renaming] */ +/*! export multiply [provided] [unused] [renamed to l] */ +/*! runtime requirements: __webpack_exports__ */ +/***/ ((__unused_webpack_module, exports) => { + +var __webpack_unused_export__; +exports.add = function add() { + var sum = 0, + i = 0, + args = arguments, + l = args.length; + while (i < l) { + sum += args[i++]; + } + return sum; +}; + +__webpack_unused_export__ = function multiply() { + var product = 0, + i = 0, + args = arguments, + l = args.length; + while (i < l) { + sum *= args[i++]; + } + return sum; +}; + + +/***/ }), +/* 1 */ +/*!**********************!*\ + !*** ./increment.js ***! + \**********************/ +/*! default exports */ +/*! export decrement [provided] [unused] [renamed to K] */ +/*! export increment [provided] [used in main] [usage prevents renaming] */ +/*! export incrementBy2 [provided] [unused] [renamed to B] */ +/*! runtime requirements: __webpack_require__, __webpack_exports__ */ +/***/ ((__unused_webpack_module, exports, __webpack_require__) => { + +var __webpack_unused_export__; +const add = (__webpack_require__(/*! ./math */ 0).add); +exports.increment = function increment(val) { + return add(val, 1); +}; +__webpack_unused_export__ = function incrementBy2(val) { + return add(val, 2); +}; +__webpack_unused_export__ = function decrement(val) { + return add(val, 1); +}; + + +/***/ }) +/******/ ]); +``` + +
/* webpack runtime code */ + +``` js +/************************************************************************/ +/******/ // The module cache +/******/ const __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ const 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; +/******/ } +/******/ +/************************************************************************/ +``` + +
+ +``` js +let __webpack_exports__ = {}; +// This entry needs to be wrapped in an IIFE because it needs to be isolated against other modules in the chunk. +(() => { +/*!********************!*\ + !*** ./example.js ***! + \********************/ +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: __webpack_require__ */ +// Property access pattern +const inc = (__webpack_require__(/*! ./increment */ 1).increment); +var a = 1; +inc(a); // 2 + +// Destructuring assignment pattern +const { add } = __webpack_require__(/*! ./math */ 0); +add(a, 2); // 3 + +// Aliased destructuring +const { increment: inc2 } = __webpack_require__(/*! ./increment */ 1); +inc2(a); // 2 + +})(); + +/******/ })() +; +``` + +# dist/output.js (production) + +```javascript +/*! For license information please see output.js.LICENSE.txt */ +(()=>{var n=[(n,t)=>{t.add=function(){for(var n=0,t=0,r=arguments,e=r.length;t{const e=r(0).add;t.increment=function(n){return e(n,1)}}];const t={};function r(e){const o=t[e];if(void 0!==o)return o.exports;const c=t[e]={exports:{}};return n[e](c,c.exports,r),c.exports}(0,r(1).increment)(1);const{add:e}=r(0);e(1,2);const{increment:o}=r(1);o(1)})(); +``` + +# dist/without.js (same without tree shaking) + +```javascript +/*! For license information please see without.js.LICENSE.txt */ +(()=>{var n=[(n,t)=>{t.add=function(){for(var n=0,t=0,r=arguments,e=r.length;t{const e=r(0).add;t.increment=function(n){return e(n,1)},t.incrementBy2=function(n){return e(n,2)},t.decrement=function(n){return e(n,1)}}];const t={};function r(e){const o=t[e];if(void 0!==o)return o.exports;const c=t[e]={exports:{}};return n[e](c,c.exports,r),c.exports}(0,r(1).increment)(1);const{add:e}=r(0);e(1,2);const{increment:o}=r(1);o(1)})(); +``` + +# Info + +## Unoptimized + +``` +asset output.js 3.18 KiB [emitted] (name: main) +chunk (runtime: main) output.js (main) 841 bytes [entry] [rendered] + > ./example.js main + dependent modules 564 bytes [dependent] 2 modules + ./example.js 277 bytes [built] [code generated] + [no exports used] + entry ./example.js main +webpack X.X.X compiled successfully + +asset without.js 3.32 KiB [emitted] (name: main) +chunk (runtime: main) without.js (main) 841 bytes [entry] [rendered] + > ./example.js main + dependent modules 564 bytes [dependent] 2 modules + ./example.js 277 bytes [built] [code generated] + [used exports unknown] + entry ./example.js main +webpack X.X.X compiled successfully +``` + +## Production mode + +``` +asset output.js 447 bytes [emitted] [minimized] (name: main) 1 related asset +chunk (runtime: main) output.js (main) 841 bytes [entry] [rendered] + > ./example.js main + dependent modules 564 bytes [dependent] 2 modules + ./example.js 277 bytes [built] [code generated] + [no exports used] + entry ./example.js main +webpack X.X.X compiled successfully + +asset without.js 615 bytes [emitted] [minimized] (name: main) 1 related asset +chunk (runtime: main) without.js (main) 841 bytes [entry] [rendered] + > ./example.js main + dependent modules 564 bytes [dependent] 2 modules + ./example.js 277 bytes [built] [code generated] + [used exports unknown] + entry ./example.js main +webpack X.X.X compiled successfully +``` diff --git a/examples/cjs-tree-shaking/build.js b/examples/cjs-tree-shaking/build.js new file mode 100644 index 00000000000..7492e9f9f71 --- /dev/null +++ b/examples/cjs-tree-shaking/build.js @@ -0,0 +1,2 @@ +global.NO_TARGET_ARGS = true; +require("../build-common"); diff --git a/examples/cjs-tree-shaking/cases.txt b/examples/cjs-tree-shaking/cases.txt new file mode 100644 index 00000000000..34bc275e076 --- /dev/null +++ b/examples/cjs-tree-shaking/cases.txt @@ -0,0 +1,58 @@ +BAD: + +module.exports = abc; module.exports.xxx = abc; abc.xxx; +exports = abc; +module.exports +exports +this +function f() { return this; } module.exports = { f }; module.exports.xxx = abc; + + +EXPORTS: + +exports.xxx = abc; + +module.exports.xxx = abc; +this.xxx = abc +Object.defineProperty(exports, "xxx", { ... }) +Object.defineProperty(module.exports, "xxx", { ... }) +Object.defineProperty(this, "xxx", { ... }) +module.exports.xxx +exports.xxx +this.xxx +module.exports = function() {}; module.exports.xxx = abc; +module.exports = { ... }; module.exports.xxx = abc; + +OBJECTS: + +module.exports = { xxx: abc }; + +IMPORT: + +require(x).xxx +var { xxx } = require(x); +var x = require(x); x.xxx; + +REEXPORT: + +module.exports.xxx = require(x); +module.exports.xxx = require(x).xxx; +exports.xxx = require(x); +exports.xxx = require(x).xxx; +module.exports = { xxx2: require(x) }; +module.exports = { xxx2: require(x).xxx }; +var xxx = require(x); exports.xxx = xxx; +var xxx = require(x); exports.xxx = xxx.xxx; +var xxx = require(x); module.exports = { xxx }; +var xxx = require(x); module.exports = { xxx: xxx.xxx }; + +TRANSPILED: + +TypeScript: +function __export(m) { for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; } +__export(require(x)); + +Babel: +var xxx = _interopRequireDefault(require(x)); +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +xxx.xxx; diff --git a/examples/cjs-tree-shaking/example.js b/examples/cjs-tree-shaking/example.js new file mode 100644 index 00000000000..0cf44c60cb3 --- /dev/null +++ b/examples/cjs-tree-shaking/example.js @@ -0,0 +1,12 @@ +// Property access pattern +const inc = require("./increment").increment; +var a = 1; +inc(a); // 2 + +// Destructuring assignment pattern +const { add } = require("./math"); +add(a, 2); // 3 + +// Aliased destructuring +const { increment: inc2 } = require("./increment"); +inc2(a); // 2 diff --git a/examples/cjs-tree-shaking/increment.js b/examples/cjs-tree-shaking/increment.js new file mode 100644 index 00000000000..df54369b913 --- /dev/null +++ b/examples/cjs-tree-shaking/increment.js @@ -0,0 +1,10 @@ +const add = require("./math").add; +exports.increment = function increment(val) { + return add(val, 1); +}; +exports.incrementBy2 = function incrementBy2(val) { + return add(val, 2); +}; +exports.decrement = function decrement(val) { + return add(val, 1); +}; diff --git a/examples/cjs-tree-shaking/math.js b/examples/cjs-tree-shaking/math.js new file mode 100644 index 00000000000..97a0ac866ab --- /dev/null +++ b/examples/cjs-tree-shaking/math.js @@ -0,0 +1,21 @@ +exports.add = function add() { + var sum = 0, + i = 0, + args = arguments, + l = args.length; + while (i < l) { + sum += args[i++]; + } + return sum; +}; + +exports.multiply = function multiply() { + var product = 0, + i = 0, + args = arguments, + l = args.length; + while (i < l) { + sum *= args[i++]; + } + return sum; +}; diff --git a/examples/cjs-tree-shaking/template.md b/examples/cjs-tree-shaking/template.md new file mode 100644 index 00000000000..1ce85164d87 --- /dev/null +++ b/examples/cjs-tree-shaking/template.md @@ -0,0 +1,51 @@ +This example demonstrates how Webpack performs tree shaking for CommonJS modules. + +# example.js + +```javascript +_{{example.js}}_ +``` + +# increment.js + +```javascript +_{{increment.js}}_ +``` + +# math.js + +```javascript +_{{math.js}}_ +``` + +# dist/output.js + +```javascript +_{{dist/output.js}}_ +``` + +# dist/output.js (production) + +```javascript +_{{production:dist/output.js}}_ +``` + +# dist/without.js (same without tree shaking) + +```javascript +_{{production:dist/without.js}}_ +``` + +# Info + +## Unoptimized + +``` +_{{stdout}}_ +``` + +## Production mode + +``` +_{{production:stdout}}_ +``` diff --git a/examples/cjs-tree-shaking/webpack.config.js b/examples/cjs-tree-shaking/webpack.config.js new file mode 100644 index 00000000000..18a3423cf60 --- /dev/null +++ b/examples/cjs-tree-shaking/webpack.config.js @@ -0,0 +1,31 @@ +"use strict"; + +/** @type {import("webpack").Configuration[]} */ +const config = [ + { + entry: "./example.js", + output: { + pathinfo: true, + filename: "output.js" + }, + optimization: { + moduleIds: "size", + usedExports: true, + mangleExports: true + } + }, + { + entry: "./example.js", + output: { + pathinfo: true, + filename: "without.js" + }, + optimization: { + moduleIds: "size", + usedExports: false, + mangleExports: false + } + } +]; + +module.exports = config; diff --git a/examples/code-splitted-css-bundle/README.md b/examples/code-splitted-css-bundle/README.md deleted file mode 100644 index 1552a655f1d..00000000000 --- a/examples/code-splitted-css-bundle/README.md +++ /dev/null @@ -1,140 +0,0 @@ - -# example.js - -``` javascript -require("./style.css"); -require(["./chunk"]); -``` - -# style.css - -``` css -body { - background: url(image.png); -} -``` - -# chunk.js - -``` javascript -require("./style2.css"); -``` - -# style2.css - -``` css -.xyz { - background: url(image2.png); -} -``` - -# webpack.config.js - -``` javascript -var ExtractTextPlugin = require("extract-text-webpack-plugin"); -module.exports = { - module: { - loaders: [ - { - test: /\.css$/, - use: ExtractTextPlugin.extract({ - fallback: "style-loader", - use: "css-loader" - }) - }, - { test: /\.png$/, loader: "file-loader" } - ] - }, - plugins: [ - new ExtractTextPlugin({ - filename: "style.css" - }) - ] -}; -``` - -# js/style.css - -``` javascript -body { - background: url(js/ce21cbdd9b894e6af794813eb3fdaf60.png); -} -``` - -# Info - -## Uncompressed - -``` -Hash: 5be34b0d3c624e61c616 -Version: webpack 3.11.0 - Asset Size Chunks Chunk Names -ce21cbdd9b894e6af794813eb3fdaf60.png 119 bytes [emitted] - 0.output.js 2.44 kB 0 [emitted] - output.js 21.2 kB 1 [emitted] main - style.css 71 bytes 1 [emitted] main -Entrypoint main = output.js style.css -chunk {0} 0.output.js 1.36 kB {1} [rendered] - > [0] ./example.js 2:0-20 - [5] ./chunk.js 26 bytes {0} [built] - amd require ./chunk [0] ./example.js 2:0-20 - [6] ./style2.css 1.01 kB {0} [built] - cjs require ./style2.css [5] ./chunk.js 1:0-23 - [7] (webpack)/node_modules/css-loader!./style2.css 236 bytes {0} [built] - cjs require !!../../node_modules/css-loader/index.js!./style2.css [6] ./style2.css 4:14-78 - [8] ./image2.png 82 bytes {0} [built] - cjs require ./image2.png [7] (webpack)/node_modules/css-loader!./style2.css 6:58-81 -chunk {1} output.js, style.css (main) 14.1 kB [entry] [rendered] - > main [0] ./example.js - [0] ./example.js 48 bytes {1} [built] - [1] ./style.css 41 bytes {1} [built] - cjs require ./style.css [0] ./example.js 1:0-22 - + 3 hidden modules -Child extract-text-webpack-plugin ../../node_modules/extract-text-webpack-plugin/dist ../../node_modules/css-loader/index.js!style.css: - 1 asset - Entrypoint undefined = extract-text-webpack-plugin-output-filename - chunk {0} extract-text-webpack-plugin-output-filename 2.58 kB [entry] [rendered] - > [0] (webpack)/node_modules/css-loader!./style.css - [0] (webpack)/node_modules/css-loader!./style.css 235 bytes {0} [built] - [2] ./image.png 82 bytes {0} [built] - cjs require ./image.png [0] (webpack)/node_modules/css-loader!./style.css 6:58-80 - + 1 hidden module -``` - -## Minimized (uglify-js, no zip) - -``` -Hash: edbe0e91ba86d814d855 -Version: webpack 3.11.0 - Asset Size Chunks Chunk Names -ce21cbdd9b894e6af794813eb3fdaf60.png 119 bytes [emitted] - 0.output.js 343 bytes 0 [emitted] - output.js 6.58 kB 1 [emitted] main - style.css 61 bytes 1 [emitted] main -Entrypoint main = output.js style.css -chunk {0} 0.output.js 1.34 kB {1} [rendered] - > [0] ./example.js 2:0-20 - [5] ./chunk.js 26 bytes {0} [built] - amd require ./chunk [0] ./example.js 2:0-20 - [6] ./style2.css 1.01 kB {0} [built] - cjs require ./style2.css [5] ./chunk.js 1:0-23 - [7] (webpack)/node_modules/css-loader!./style2.css 219 bytes {0} [built] - cjs require !!../../node_modules/css-loader/index.js!./style2.css [6] ./style2.css 4:14-78 - [8] ./image2.png 82 bytes {0} [built] - cjs require ./image2.png [7] (webpack)/node_modules/css-loader!./style2.css 6:50-73 -chunk {1} output.js, style.css (main) 14.1 kB [entry] [rendered] - > main [0] ./example.js - [0] ./example.js 48 bytes {1} [built] - [1] ./style.css 41 bytes {1} [built] - cjs require ./style.css [0] ./example.js 1:0-22 - + 3 hidden modules -Child extract-text-webpack-plugin ../../node_modules/extract-text-webpack-plugin/dist ../../node_modules/css-loader/index.js!style.css: - 1 asset - Entrypoint undefined = extract-text-webpack-plugin-output-filename - chunk {0} extract-text-webpack-plugin-output-filename 2.56 kB [entry] [rendered] - > [0] (webpack)/node_modules/css-loader!./style.css - [0] (webpack)/node_modules/css-loader!./style.css 218 bytes {0} [built] - [2] ./image.png 82 bytes {0} [built] - cjs require ./image.png [0] (webpack)/node_modules/css-loader!./style.css 6:50-72 - + 1 hidden module -``` diff --git a/examples/code-splitted-require.context-amd/README.md b/examples/code-splitted-require.context-amd/README.md index 07a7855e2d8..32aaaea1675 100644 --- a/examples/code-splitted-require.context-amd/README.md +++ b/examples/code-splitted-require.context-amd/README.md @@ -16,207 +16,218 @@ getTemplate("b", function(b) { # dist/output.js -
/******/ (function(modules) { /* webpackBootstrap */ }) - ``` javascript -/******/ (function(modules) { // webpackBootstrap -/******/ // install a JSONP callback for chunk loading -/******/ function webpackJsonpCallback(data) { -/******/ var chunkIds = data[0]; -/******/ var moreModules = data[1]; -/******/ -/******/ // add "moreModules" to the modules object, -/******/ // then flag all "chunkIds" as loaded and fire callback -/******/ var moduleId, chunkId, i = 0, resolves = []; -/******/ for(;i < chunkIds.length; i++) { -/******/ chunkId = chunkIds[i]; -/******/ if(installedChunks[chunkId]) { -/******/ resolves.push(installedChunks[chunkId][0]); -/******/ } -/******/ installedChunks[chunkId] = 0; -/******/ } -/******/ for(moduleId in moreModules) { -/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { -/******/ modules[moduleId] = moreModules[moduleId]; -/******/ } -/******/ } -/******/ if(parentJsonpFunction) parentJsonpFunction(data); -/******/ while(resolves.length) { -/******/ resolves.shift()(); -/******/ } -/******/ -/******/ }; -/******/ -/******/ +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ({}); +``` + +
/* webpack runtime code */ + +``` js +/************************************************************************/ /******/ // The module cache -/******/ var installedModules = {}; -/******/ -/******/ // object to store loaded and loading chunks -/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched -/******/ // Promise = chunk loading, 0 = chunk loaded -/******/ var installedChunks = { -/******/ 1: 0 -/******/ }; -/******/ -/******/ -/******/ -/******/ // script path function -/******/ function jsonpScriptSrc(chunkId) { -/******/ return __webpack_require__.p + "" + chunkId + ".output.js" -/******/ } -/******/ +/******/ const __webpack_module_cache__ = {}; +/******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { -/******/ /******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) { -/******/ return installedModules[moduleId].exports; +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, +/******/ const module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed /******/ exports: {} /******/ }; -/******/ +/******/ /******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } -/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = __webpack_modules__; +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/ensure chunk */ +/******/ __webpack_require__.f = {}; /******/ // This file contains only the entry chunk. /******/ // The chunk loading function for additional chunks -/******/ __webpack_require__.e = function requireEnsure(chunkId) { -/******/ var promises = []; -/******/ -/******/ -/******/ // JSONP chunk loading for javascript -/******/ -/******/ var installedChunkData = installedChunks[chunkId]; -/******/ if(installedChunkData !== 0) { // 0 means "already installed". -/******/ -/******/ // a Promise means "currently loading". -/******/ if(installedChunkData) { -/******/ promises.push(installedChunkData[2]); -/******/ } else { -/******/ // setup Promise in chunk cache -/******/ var promise = new Promise(function(resolve, reject) { -/******/ installedChunkData = installedChunks[chunkId] = [resolve, reject]; -/******/ }); -/******/ promises.push(installedChunkData[2] = promise); -/******/ -/******/ // start chunk loading -/******/ var head = document.getElementsByTagName('head')[0]; -/******/ var script = document.createElement('script'); -/******/ +/******/ __webpack_require__.e = (chunkId) => { +/******/ return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => { +/******/ __webpack_require__.f[key](chunkId, promises); +/******/ return promises; +/******/ }, [])); +/******/ }; +/******/ +/******/ /* webpack/runtime/get javascript chunk filename */ +/******/ // This function allow to reference async chunks +/******/ __webpack_require__.u = (chunkId) => { +/******/ // return url for filenames based on template +/******/ return "" + chunkId + ".output.js"; +/******/ }; +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)); +/******/ +/******/ /* webpack/runtime/load script */ +/******/ (() => { +/******/ const inProgress = {}; +/******/ // data-webpack is not used as build has no uniqueName +/******/ // loadScript function to load a script via script tag +/******/ __webpack_require__.l = (url, done, key, chunkId) => { +/******/ if(inProgress[url]) { inProgress[url].push(done); return; } +/******/ let script, needAttach; +/******/ if(key !== undefined) { +/******/ const scripts = document.getElementsByTagName("script"); +/******/ for(var i = 0; i < scripts.length; i++) { +/******/ const s = scripts[i]; +/******/ if(s.getAttribute("src") == url) { script = s; break; } +/******/ } +/******/ } +/******/ if(!script) { +/******/ needAttach = true; +/******/ script = document.createElement('script'); +/******/ /******/ script.charset = 'utf-8'; -/******/ script.timeout = 120; -/******/ /******/ if (__webpack_require__.nc) { /******/ script.setAttribute("nonce", __webpack_require__.nc); /******/ } -/******/ script.src = jsonpScriptSrc(chunkId); -/******/ var timeout = setTimeout(function(){ -/******/ onScriptComplete({ type: 'timeout', target: script }); -/******/ }, 120000); -/******/ script.onerror = script.onload = onScriptComplete; -/******/ function onScriptComplete(event) { -/******/ // avoid mem leaks in IE. -/******/ script.onerror = script.onload = null; -/******/ clearTimeout(timeout); -/******/ var chunk = installedChunks[chunkId]; -/******/ if(chunk !== 0) { -/******/ if(chunk) { -/******/ var errorType = event && (event.type === 'load' ? 'missing' : event.type); -/******/ var realSrc = event && event.target && event.target.src; -/******/ var error = new Error('Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'); -/******/ error.type = errorType; -/******/ error.request = realSrc; -/******/ chunk[1](error); +/******/ +/******/ +/******/ script.src = url; +/******/ } +/******/ inProgress[url] = [done]; +/******/ const onScriptComplete = (prev, event) => { +/******/ // avoid mem leaks in IE. +/******/ script.onerror = script.onload = null; +/******/ clearTimeout(timeout); +/******/ const doneFns = inProgress[url]; +/******/ delete inProgress[url]; +/******/ script.parentNode?.removeChild(script); +/******/ doneFns?.forEach((fn) => (fn(event))); +/******/ if(prev) return prev(event); +/******/ } +/******/ const timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000); +/******/ script.onerror = onScriptComplete.bind(null, script.onerror); +/******/ script.onload = onScriptComplete.bind(null, script.onload); +/******/ needAttach && document.head.appendChild(script); +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/publicPath */ +/******/ __webpack_require__.p = "dist/"; +/******/ +/******/ /* webpack/runtime/jsonp chunk loading */ +/******/ (() => { +/******/ // no baseURI +/******/ +/******/ // object to store loaded and loading chunks +/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched +/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded +/******/ const installedChunks = { +/******/ "main": 0 +/******/ }; +/******/ +/******/ __webpack_require__.f.j = (chunkId, promises) => { +/******/ // JSONP chunk loading for javascript +/******/ let installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined; +/******/ if(installedChunkData !== 0) { // 0 means "already installed". +/******/ +/******/ // a Promise means "currently loading". +/******/ if(installedChunkData) { +/******/ promises.push(installedChunkData[2]); +/******/ } else { +/******/ if(true) { // all chunks have JS +/******/ // setup Promise in chunk cache +/******/ const promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject])); +/******/ promises.push(installedChunkData[2] = promise); +/******/ +/******/ // start chunk loading +/******/ const url = __webpack_require__.p + __webpack_require__.u(chunkId); +/******/ // create error before stack unwound to get useful stacktrace later +/******/ const error = new Error(); +/******/ const loadingEnded = (event) => { +/******/ if(__webpack_require__.o(installedChunks, chunkId)) { +/******/ installedChunkData = installedChunks[chunkId]; +/******/ if(installedChunkData !== 0) installedChunks[chunkId] = undefined; +/******/ if(installedChunkData) { +/******/ const errorType = event && (event.type === 'load' ? 'missing' : event.type); +/******/ const realSrc = event && event.target && event.target.src; +/******/ error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'; +/******/ error.name = 'ChunkLoadError'; +/******/ error.type = errorType; +/******/ error.request = realSrc; +/******/ error.event = event; +/******/ installedChunkData[1](error); +/******/ } +/******/ } +/******/ }; +/******/ __webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId); /******/ } -/******/ installedChunks[chunkId] = undefined; /******/ } -/******/ }; -/******/ head.appendChild(script); +/******/ } +/******/ }; +/******/ +/******/ // no prefetching +/******/ +/******/ // no preloaded +/******/ +/******/ // no HMR +/******/ +/******/ // no HMR manifest +/******/ +/******/ // no on chunks loaded +/******/ +/******/ // install a JSONP callback for chunk loading +/******/ const webpackJsonpCallback = (parentChunkLoadingFunction, data) => { +/******/ let [chunkIds, moreModules, runtime] = data; +/******/ // add "moreModules" to the modules object, +/******/ // then flag all "chunkIds" as loaded and fire callback +/******/ var moduleId, chunkId, i = 0; +/******/ if(chunkIds.some((id) => (installedChunks[id] !== 0))) { +/******/ for(moduleId in moreModules) { +/******/ if(__webpack_require__.o(moreModules, moduleId)) { +/******/ __webpack_require__.m[moduleId] = moreModules[moduleId]; +/******/ } +/******/ } +/******/ if(runtime) var result = runtime(__webpack_require__); /******/ } +/******/ if(parentChunkLoadingFunction) parentChunkLoadingFunction(data); +/******/ for(;i < chunkIds.length; i++) { +/******/ chunkId = chunkIds[i]; +/******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) { +/******/ installedChunks[chunkId][0](); +/******/ } +/******/ installedChunks[chunkId] = 0; +/******/ } +/******/ /******/ } -/******/ return Promise.all(promises); -/******/ }; -/******/ -/******/ // 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, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); -/******/ } -/******/ }; -/******/ -/******/ // define __esModule on exports -/******/ __webpack_require__.r = function(exports) { -/******/ Object.defineProperty(exports, '__esModule', { value: true }); -/******/ }; -/******/ -/******/ // 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 = "dist/"; -/******/ -/******/ // on error function for async loading -/******/ __webpack_require__.oe = function(err) { console.error(err); throw err; }; -/******/ -/******/ var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || []; -/******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); -/******/ jsonpArray.push = webpackJsonpCallback; -/******/ jsonpArray = jsonpArray.slice(); -/******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]); -/******/ var parentJsonpFunction = oldJsonpFunction; -/******/ -/******/ -/******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 0); -/******/ }) +/******/ +/******/ const chunkLoadingGlobal = self["webpackChunk"] = self["webpackChunk"] || []; +/******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0)); +/******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal)); +/******/ })(); +/******/ /************************************************************************/ ```
-``` javascript -/******/ ([ -/* 0 */ +``` js /*!********************!*\ !*** ./example.js ***! \********************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: __webpack_require__.e, __webpack_require__.oe, __webpack_require__, __webpack_require__.* */ function getTemplate(templateName, callback) { - __webpack_require__.e(/*! AMD require */ 0).then(function() { var __WEBPACK_AMD_REQUIRE_ARRAY__ = [__webpack_require__(1)("./"+templateName)]; (function(tmpl) { + __webpack_require__.e(/*! AMD require */ "require_context_templates_sync_recursive_").then(function() { var __WEBPACK_AMD_REQUIRE_ARRAY__ = [__webpack_require__(1)("./"+templateName)]; (function(tmpl) { callback(tmpl()); - }).apply(null, __WEBPACK_AMD_REQUIRE_ARRAY__);}).catch(__webpack_require__.oe); + }).apply(null, __WEBPACK_AMD_REQUIRE_ARRAY__);})['catch'](__webpack_require__.oe); } getTemplate("a", function(a) { console.log(a); @@ -224,46 +235,45 @@ getTemplate("a", function(a) { getTemplate("b", function(b) { console.log(b); }); - -/***/ }) -/******/ ]); +/******/ })() +; ``` -# dist/0.output.js +# dist/require_context_templates_sync_recursive_.output.js ``` javascript -(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0],[ +(self["webpackChunk"] = self["webpackChunk"] || []).push([["require_context_templates_sync_recursive_"],[ /* 0 */, /* 1 */ -/*!**************************************************!*\ - !*** ../require.context/templates sync ^\.\/.*$ ***! - \**************************************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*!***************************************************!*\ + !*** ../require.context/templates/ sync ^\.\/.*$ ***! + \***************************************************/ +/*! default exports */ +/*! exports [not provided] [no usage info] */ +/*! runtime requirements: module, __webpack_require__.o, __webpack_require__ */ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { -var map = { - "./a": 4, - "./a.js": 4, +const map = { + "./a": 2, + "./a.js": 2, "./b": 3, "./b.js": 3, - "./c": 2, - "./c.js": 2 + "./c": 4, + "./c.js": 4 }; function webpackContext(req) { - var id = webpackContextResolve(req); - var module = __webpack_require__(id); - return module; + const id = webpackContextResolve(req); + return __webpack_require__(id); } function webpackContextResolve(req) { - var id = map[req]; - if(!(id + 1)) { // check for number or string - var e = new Error("Cannot find module '" + req + "'"); + if(!__webpack_require__.o(map, req)) { + const e = new Error("Cannot find module '" + req + "'"); e.code = 'MODULE_NOT_FOUND'; throw e; } - return id; + return map[req]; } webpackContext.keys = function webpackContextKeys() { return Object.keys(map); @@ -275,13 +285,15 @@ webpackContext.id = 1; /***/ }), /* 2 */ /*!*****************************************!*\ - !*** ../require.context/templates/c.js ***! + !*** ../require.context/templates/a.js ***! \*****************************************/ -/*! no static exports found */ -/***/ (function(module, exports) { +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: module */ +/*! CommonJS bailout: module.exports is used directly at 1:0-14 */ +/***/ ((module) => { module.exports = function() { - return "This text was generated by template C"; + return "This text was generated by template A"; } /***/ }), @@ -289,8 +301,10 @@ module.exports = function() { /*!*****************************************!*\ !*** ../require.context/templates/b.js ***! \*****************************************/ -/*! no static exports found */ -/***/ (function(module, exports) { +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: module */ +/*! CommonJS bailout: module.exports is used directly at 1:0-14 */ +/***/ ((module) => { module.exports = function() { return "This text was generated by template B"; @@ -299,13 +313,15 @@ module.exports = function() { /***/ }), /* 4 */ /*!*****************************************!*\ - !*** ../require.context/templates/a.js ***! + !*** ../require.context/templates/c.js ***! \*****************************************/ -/*! no static exports found */ -/***/ (function(module, exports) { +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: module */ +/*! CommonJS bailout: module.exports is used directly at 1:0-14 */ +/***/ ((module) => { module.exports = function() { - return "This text was generated by template A"; + return "This text was generated by template C"; } /***/ }) @@ -317,55 +333,40 @@ module.exports = function() { ## Unoptimized ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 - Asset Size Chunks Chunk Names -0.output.js 1.86 KiB 0 [emitted] - output.js 7.46 KiB 1 [emitted] main -Entrypoint main = output.js -chunk {0} 0.output.js 463 bytes <{1}> [rendered] - > [0] ./example.js 2:1-4:3 - [1] ../require.context/templates sync ^\.\/.*$ 217 bytes {0} [built] - amd require context ../require.context/templates [0] ./example.js 2:1-4:3 - [2] ../require.context/templates/c.js 82 bytes {0} [optional] [built] - context element ./c.js [1] ../require.context/templates sync ^\.\/.*$ ./c.js - context element ./c [1] ../require.context/templates sync ^\.\/.*$ ./c - [3] ../require.context/templates/b.js 82 bytes {0} [optional] [built] - context element ./b.js [1] ../require.context/templates sync ^\.\/.*$ ./b.js - context element ./b [1] ../require.context/templates sync ^\.\/.*$ ./b - [4] ../require.context/templates/a.js 82 bytes {0} [optional] [built] - context element ./a.js [1] ../require.context/templates sync ^\.\/.*$ ./a.js - context element ./a [1] ../require.context/templates sync ^\.\/.*$ ./a -chunk {1} output.js (main) 261 bytes >{0}< [entry] [rendered] - > .\example.js main - [0] ./example.js 261 bytes {1} [built] - single entry .\example.js main +asset output.js 8.88 KiB [emitted] (name: main) +asset require_context_templates_sync_recursive_.output.js 2.28 KiB [emitted] +chunk (runtime: main) output.js (main) 251 bytes (javascript) 4.95 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 4.95 KiB 6 modules + ./example.js 251 bytes [built] [code generated] + [used exports unknown] + entry ./example.js main +chunk (runtime: main) require_context_templates_sync_recursive_.output.js 457 bytes [rendered] + > ./example.js 2:1-4:3 + dependent modules 240 bytes [dependent] 3 modules + ../require.context/templates/ sync ^\.\/.*$ 217 bytes [built] [code generated] + [no exports] + [used exports unknown] + amd require context ./example.js 2:1-4:3 +webpack X.X.X compiled successfully ``` ## Production mode ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 - Asset Size Chunks Chunk Names -0.output.js 627 bytes 0 [emitted] - output.js 1.78 KiB 1 [emitted] main -Entrypoint main = output.js -chunk {0} 0.output.js 463 bytes <{1}> [rendered] - > [0] ./example.js 2:1-4:3 - [1] ../require.context/templates sync ^\.\/.*$ 217 bytes {0} [built] - amd require context ../require.context/templates [0] ./example.js 2:1-4:3 - [2] ../require.context/templates/c.js 82 bytes {0} [optional] [built] - context element ./c.js [1] ../require.context/templates sync ^\.\/.*$ ./c.js - context element ./c [1] ../require.context/templates sync ^\.\/.*$ ./c - [3] ../require.context/templates/b.js 82 bytes {0} [optional] [built] - context element ./b.js [1] ../require.context/templates sync ^\.\/.*$ ./b.js - context element ./b [1] ../require.context/templates sync ^\.\/.*$ ./b - [4] ../require.context/templates/a.js 82 bytes {0} [optional] [built] - context element ./a.js [1] ../require.context/templates sync ^\.\/.*$ ./a.js - context element ./a [1] ../require.context/templates sync ^\.\/.*$ ./a -chunk {1} output.js (main) 261 bytes >{0}< [entry] [rendered] - > .\example.js main - [0] ./example.js 261 bytes {1} [built] - single entry .\example.js main +asset output.js 1.89 KiB [emitted] [minimized] (name: main) +asset require_context_templates_sync_recursive_.output.js 652 bytes [emitted] [minimized] +chunk (runtime: main) output.js (main) 251 bytes (javascript) 4.95 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 4.95 KiB 6 modules + ./example.js 251 bytes [built] [code generated] + [no exports used] + entry ./example.js main +chunk (runtime: main) require_context_templates_sync_recursive_.output.js 457 bytes [rendered] + > ./example.js 2:1-4:3 + dependent modules 240 bytes [dependent] 3 modules + ../require.context/templates/ sync ^\.\/.*$ 217 bytes [built] [code generated] + [no exports] + amd require context ./example.js 2:1-4:3 +webpack X.X.X compiled successfully ``` diff --git a/examples/code-splitted-require.context-amd/template.md b/examples/code-splitted-require.context-amd/template.md index 0b7b60cfd4f..5c134294f17 100644 --- a/examples/code-splitted-require.context-amd/template.md +++ b/examples/code-splitted-require.context-amd/template.md @@ -1,19 +1,19 @@ # example.js ``` javascript -{{example.js}} +_{{example.js}}_ ``` # dist/output.js ``` javascript -{{dist/output.js}} +_{{dist/output.js}}_ ``` -# dist/0.output.js +# dist/require_context_templates_sync_recursive_.output.js ``` javascript -{{dist/0.output.js}} +_{{dist/require_context_templates_sync_recursive_.output.js}}_ ``` # Info @@ -21,11 +21,11 @@ ## Unoptimized ``` -{{stdout}} +_{{stdout}}_ ``` ## Production mode ``` -{{production:stdout}} +_{{production:stdout}}_ ``` diff --git a/examples/code-splitted-require.context-amd/webpack.config.js b/examples/code-splitted-require.context-amd/webpack.config.js index 0d554bf62ea..3a2046aae21 100644 --- a/examples/code-splitted-require.context-amd/webpack.config.js +++ b/examples/code-splitted-require.context-amd/webpack.config.js @@ -1,5 +1,10 @@ -module.exports = { +"use strict"; + +/** @type {import("webpack").Configuration} */ +const config = { optimization: { - occurrenceOrder: true // To keep filename consistent between different modes (for example building only) + chunkIds: "named" // To keep filename consistent between different modes (for example building only) } }; + +module.exports = config; diff --git a/examples/code-splitted-require.context/README.md b/examples/code-splitted-require.context/README.md index c1251d65308..a85e39c30f5 100644 --- a/examples/code-splitted-require.context/README.md +++ b/examples/code-splitted-require.context/README.md @@ -16,207 +16,218 @@ getTemplate("b", function(b) { # dist/output.js -
/******/ (function(modules) { /* webpackBootstrap */ }) - ``` javascript -/******/ (function(modules) { // webpackBootstrap -/******/ // install a JSONP callback for chunk loading -/******/ function webpackJsonpCallback(data) { -/******/ var chunkIds = data[0]; -/******/ var moreModules = data[1]; -/******/ -/******/ // add "moreModules" to the modules object, -/******/ // then flag all "chunkIds" as loaded and fire callback -/******/ var moduleId, chunkId, i = 0, resolves = []; -/******/ for(;i < chunkIds.length; i++) { -/******/ chunkId = chunkIds[i]; -/******/ if(installedChunks[chunkId]) { -/******/ resolves.push(installedChunks[chunkId][0]); -/******/ } -/******/ installedChunks[chunkId] = 0; -/******/ } -/******/ for(moduleId in moreModules) { -/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { -/******/ modules[moduleId] = moreModules[moduleId]; -/******/ } -/******/ } -/******/ if(parentJsonpFunction) parentJsonpFunction(data); -/******/ while(resolves.length) { -/******/ resolves.shift()(); -/******/ } -/******/ -/******/ }; -/******/ -/******/ +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ({}); +``` + +
/* webpack runtime code */ + +``` js +/************************************************************************/ /******/ // The module cache -/******/ var installedModules = {}; -/******/ -/******/ // object to store loaded and loading chunks -/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched -/******/ // Promise = chunk loading, 0 = chunk loaded -/******/ var installedChunks = { -/******/ 1: 0 -/******/ }; -/******/ -/******/ -/******/ -/******/ // script path function -/******/ function jsonpScriptSrc(chunkId) { -/******/ return __webpack_require__.p + "" + chunkId + ".output.js" -/******/ } -/******/ +/******/ const __webpack_module_cache__ = {}; +/******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { -/******/ /******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) { -/******/ return installedModules[moduleId].exports; +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, +/******/ const module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed /******/ exports: {} /******/ }; -/******/ +/******/ /******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } -/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = __webpack_modules__; +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/ensure chunk */ +/******/ __webpack_require__.f = {}; /******/ // This file contains only the entry chunk. /******/ // The chunk loading function for additional chunks -/******/ __webpack_require__.e = function requireEnsure(chunkId) { -/******/ var promises = []; -/******/ -/******/ -/******/ // JSONP chunk loading for javascript -/******/ -/******/ var installedChunkData = installedChunks[chunkId]; -/******/ if(installedChunkData !== 0) { // 0 means "already installed". -/******/ -/******/ // a Promise means "currently loading". -/******/ if(installedChunkData) { -/******/ promises.push(installedChunkData[2]); -/******/ } else { -/******/ // setup Promise in chunk cache -/******/ var promise = new Promise(function(resolve, reject) { -/******/ installedChunkData = installedChunks[chunkId] = [resolve, reject]; -/******/ }); -/******/ promises.push(installedChunkData[2] = promise); -/******/ -/******/ // start chunk loading -/******/ var head = document.getElementsByTagName('head')[0]; -/******/ var script = document.createElement('script'); -/******/ +/******/ __webpack_require__.e = (chunkId) => { +/******/ return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => { +/******/ __webpack_require__.f[key](chunkId, promises); +/******/ return promises; +/******/ }, [])); +/******/ }; +/******/ +/******/ /* webpack/runtime/get javascript chunk filename */ +/******/ // This function allow to reference async chunks +/******/ __webpack_require__.u = (chunkId) => { +/******/ // return url for filenames based on template +/******/ return "" + chunkId + ".output.js"; +/******/ }; +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)); +/******/ +/******/ /* webpack/runtime/load script */ +/******/ (() => { +/******/ const inProgress = {}; +/******/ // data-webpack is not used as build has no uniqueName +/******/ // loadScript function to load a script via script tag +/******/ __webpack_require__.l = (url, done, key, chunkId) => { +/******/ if(inProgress[url]) { inProgress[url].push(done); return; } +/******/ let script, needAttach; +/******/ if(key !== undefined) { +/******/ const scripts = document.getElementsByTagName("script"); +/******/ for(var i = 0; i < scripts.length; i++) { +/******/ const s = scripts[i]; +/******/ if(s.getAttribute("src") == url) { script = s; break; } +/******/ } +/******/ } +/******/ if(!script) { +/******/ needAttach = true; +/******/ script = document.createElement('script'); +/******/ /******/ script.charset = 'utf-8'; -/******/ script.timeout = 120; -/******/ /******/ if (__webpack_require__.nc) { /******/ script.setAttribute("nonce", __webpack_require__.nc); /******/ } -/******/ script.src = jsonpScriptSrc(chunkId); -/******/ var timeout = setTimeout(function(){ -/******/ onScriptComplete({ type: 'timeout', target: script }); -/******/ }, 120000); -/******/ script.onerror = script.onload = onScriptComplete; -/******/ function onScriptComplete(event) { -/******/ // avoid mem leaks in IE. -/******/ script.onerror = script.onload = null; -/******/ clearTimeout(timeout); -/******/ var chunk = installedChunks[chunkId]; -/******/ if(chunk !== 0) { -/******/ if(chunk) { -/******/ var errorType = event && (event.type === 'load' ? 'missing' : event.type); -/******/ var realSrc = event && event.target && event.target.src; -/******/ var error = new Error('Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'); -/******/ error.type = errorType; -/******/ error.request = realSrc; -/******/ chunk[1](error); +/******/ +/******/ +/******/ script.src = url; +/******/ } +/******/ inProgress[url] = [done]; +/******/ const onScriptComplete = (prev, event) => { +/******/ // avoid mem leaks in IE. +/******/ script.onerror = script.onload = null; +/******/ clearTimeout(timeout); +/******/ const doneFns = inProgress[url]; +/******/ delete inProgress[url]; +/******/ script.parentNode?.removeChild(script); +/******/ doneFns?.forEach((fn) => (fn(event))); +/******/ if(prev) return prev(event); +/******/ } +/******/ const timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000); +/******/ script.onerror = onScriptComplete.bind(null, script.onerror); +/******/ script.onload = onScriptComplete.bind(null, script.onload); +/******/ needAttach && document.head.appendChild(script); +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/publicPath */ +/******/ __webpack_require__.p = "dist/"; +/******/ +/******/ /* webpack/runtime/jsonp chunk loading */ +/******/ (() => { +/******/ // no baseURI +/******/ +/******/ // object to store loaded and loading chunks +/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched +/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded +/******/ const installedChunks = { +/******/ "main": 0 +/******/ }; +/******/ +/******/ __webpack_require__.f.j = (chunkId, promises) => { +/******/ // JSONP chunk loading for javascript +/******/ let installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined; +/******/ if(installedChunkData !== 0) { // 0 means "already installed". +/******/ +/******/ // a Promise means "currently loading". +/******/ if(installedChunkData) { +/******/ promises.push(installedChunkData[2]); +/******/ } else { +/******/ if(true) { // all chunks have JS +/******/ // setup Promise in chunk cache +/******/ const promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject])); +/******/ promises.push(installedChunkData[2] = promise); +/******/ +/******/ // start chunk loading +/******/ const url = __webpack_require__.p + __webpack_require__.u(chunkId); +/******/ // create error before stack unwound to get useful stacktrace later +/******/ const error = new Error(); +/******/ const loadingEnded = (event) => { +/******/ if(__webpack_require__.o(installedChunks, chunkId)) { +/******/ installedChunkData = installedChunks[chunkId]; +/******/ if(installedChunkData !== 0) installedChunks[chunkId] = undefined; +/******/ if(installedChunkData) { +/******/ const errorType = event && (event.type === 'load' ? 'missing' : event.type); +/******/ const realSrc = event && event.target && event.target.src; +/******/ error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'; +/******/ error.name = 'ChunkLoadError'; +/******/ error.type = errorType; +/******/ error.request = realSrc; +/******/ error.event = event; +/******/ installedChunkData[1](error); +/******/ } +/******/ } +/******/ }; +/******/ __webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId); /******/ } -/******/ installedChunks[chunkId] = undefined; /******/ } -/******/ }; -/******/ head.appendChild(script); +/******/ } +/******/ }; +/******/ +/******/ // no prefetching +/******/ +/******/ // no preloaded +/******/ +/******/ // no HMR +/******/ +/******/ // no HMR manifest +/******/ +/******/ // no on chunks loaded +/******/ +/******/ // install a JSONP callback for chunk loading +/******/ const webpackJsonpCallback = (parentChunkLoadingFunction, data) => { +/******/ let [chunkIds, moreModules, runtime] = data; +/******/ // add "moreModules" to the modules object, +/******/ // then flag all "chunkIds" as loaded and fire callback +/******/ var moduleId, chunkId, i = 0; +/******/ if(chunkIds.some((id) => (installedChunks[id] !== 0))) { +/******/ for(moduleId in moreModules) { +/******/ if(__webpack_require__.o(moreModules, moduleId)) { +/******/ __webpack_require__.m[moduleId] = moreModules[moduleId]; +/******/ } +/******/ } +/******/ if(runtime) var result = runtime(__webpack_require__); /******/ } +/******/ if(parentChunkLoadingFunction) parentChunkLoadingFunction(data); +/******/ for(;i < chunkIds.length; i++) { +/******/ chunkId = chunkIds[i]; +/******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) { +/******/ installedChunks[chunkId][0](); +/******/ } +/******/ installedChunks[chunkId] = 0; +/******/ } +/******/ /******/ } -/******/ return Promise.all(promises); -/******/ }; -/******/ -/******/ // 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, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); -/******/ } -/******/ }; -/******/ -/******/ // define __esModule on exports -/******/ __webpack_require__.r = function(exports) { -/******/ Object.defineProperty(exports, '__esModule', { value: true }); -/******/ }; -/******/ -/******/ // 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 = "dist/"; -/******/ -/******/ // on error function for async loading -/******/ __webpack_require__.oe = function(err) { console.error(err); throw err; }; -/******/ -/******/ var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || []; -/******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); -/******/ jsonpArray.push = webpackJsonpCallback; -/******/ jsonpArray = jsonpArray.slice(); -/******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]); -/******/ var parentJsonpFunction = oldJsonpFunction; -/******/ -/******/ -/******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 0); -/******/ }) +/******/ +/******/ const chunkLoadingGlobal = self["webpackChunk"] = self["webpackChunk"] || []; +/******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0)); +/******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal)); +/******/ })(); +/******/ /************************************************************************/ ```
-``` javascript -/******/ ([ -/* 0 */ +``` js /*!********************!*\ !*** ./example.js ***! \********************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: __webpack_require__.e, __webpack_require__, __webpack_require__.* */ function getTemplate(templateName, callback) { - __webpack_require__.e(/*! require.ensure */ 0).then((function(require) { + __webpack_require__.e(/*! require.ensure */ "require_context_templates_sync_recursive_").then((function(require) { callback(__webpack_require__(1)("./"+templateName)()); - }).bind(null, __webpack_require__)).catch(__webpack_require__.oe); + }).bind(null, __webpack_require__))['catch'](__webpack_require__.oe); } getTemplate("a", function(a) { console.log(a); @@ -224,46 +235,45 @@ getTemplate("a", function(a) { getTemplate("b", function(b) { console.log(b); }); - -/***/ }) -/******/ ]); +/******/ })() +; ``` -# dist/0.output.js +# dist/require_context_templates_sync_recursive_.output.js ``` javascript -(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0],[ +(self["webpackChunk"] = self["webpackChunk"] || []).push([["require_context_templates_sync_recursive_"],[ /* 0 */, /* 1 */ -/*!**************************************************!*\ - !*** ../require.context/templates sync ^\.\/.*$ ***! - \**************************************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*!***************************************************!*\ + !*** ../require.context/templates/ sync ^\.\/.*$ ***! + \***************************************************/ +/*! default exports */ +/*! exports [not provided] [no usage info] */ +/*! runtime requirements: module, __webpack_require__.o, __webpack_require__ */ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { -var map = { - "./a": 4, - "./a.js": 4, +const map = { + "./a": 2, + "./a.js": 2, "./b": 3, "./b.js": 3, - "./c": 2, - "./c.js": 2 + "./c": 4, + "./c.js": 4 }; function webpackContext(req) { - var id = webpackContextResolve(req); - var module = __webpack_require__(id); - return module; + const id = webpackContextResolve(req); + return __webpack_require__(id); } function webpackContextResolve(req) { - var id = map[req]; - if(!(id + 1)) { // check for number or string - var e = new Error("Cannot find module '" + req + "'"); + if(!__webpack_require__.o(map, req)) { + const e = new Error("Cannot find module '" + req + "'"); e.code = 'MODULE_NOT_FOUND'; throw e; } - return id; + return map[req]; } webpackContext.keys = function webpackContextKeys() { return Object.keys(map); @@ -275,13 +285,15 @@ webpackContext.id = 1; /***/ }), /* 2 */ /*!*****************************************!*\ - !*** ../require.context/templates/c.js ***! + !*** ../require.context/templates/a.js ***! \*****************************************/ -/*! no static exports found */ -/***/ (function(module, exports) { +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: module */ +/*! CommonJS bailout: module.exports is used directly at 1:0-14 */ +/***/ ((module) => { module.exports = function() { - return "This text was generated by template C"; + return "This text was generated by template A"; } /***/ }), @@ -289,8 +301,10 @@ module.exports = function() { /*!*****************************************!*\ !*** ../require.context/templates/b.js ***! \*****************************************/ -/*! no static exports found */ -/***/ (function(module, exports) { +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: module */ +/*! CommonJS bailout: module.exports is used directly at 1:0-14 */ +/***/ ((module) => { module.exports = function() { return "This text was generated by template B"; @@ -299,13 +313,15 @@ module.exports = function() { /***/ }), /* 4 */ /*!*****************************************!*\ - !*** ../require.context/templates/a.js ***! + !*** ../require.context/templates/c.js ***! \*****************************************/ -/*! no static exports found */ -/***/ (function(module, exports) { +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: module */ +/*! CommonJS bailout: module.exports is used directly at 1:0-14 */ +/***/ ((module) => { module.exports = function() { - return "This text was generated by template A"; + return "This text was generated by template C"; } /***/ }) @@ -317,55 +333,40 @@ module.exports = function() { ## Unoptimized ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 - Asset Size Chunks Chunk Names -0.output.js 1.86 KiB 0 [emitted] - output.js 7.4 KiB 1 [emitted] main -Entrypoint main = output.js -chunk {0} 0.output.js 463 bytes <{1}> [rendered] - > [0] ./example.js 2:1-4:3 - [1] ../require.context/templates sync ^\.\/.*$ 217 bytes {0} [built] - cjs require context ../require.context/templates [0] ./example.js 3:11-64 - [2] ../require.context/templates/c.js 82 bytes {0} [optional] [built] - context element ./c.js [1] ../require.context/templates sync ^\.\/.*$ ./c.js - context element ./c [1] ../require.context/templates sync ^\.\/.*$ ./c - [3] ../require.context/templates/b.js 82 bytes {0} [optional] [built] - context element ./b.js [1] ../require.context/templates sync ^\.\/.*$ ./b.js - context element ./b [1] ../require.context/templates sync ^\.\/.*$ ./b - [4] ../require.context/templates/a.js 82 bytes {0} [optional] [built] - context element ./a.js [1] ../require.context/templates sync ^\.\/.*$ ./a.js - context element ./a [1] ../require.context/templates sync ^\.\/.*$ ./a -chunk {1} output.js (main) 276 bytes >{0}< [entry] [rendered] - > .\example.js main - [0] ./example.js 276 bytes {1} [built] - single entry .\example.js main +asset output.js 8.8 KiB [emitted] (name: main) +asset require_context_templates_sync_recursive_.output.js 2.28 KiB [emitted] +chunk (runtime: main) output.js (main) 266 bytes (javascript) 4.95 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 4.95 KiB 6 modules + ./example.js 266 bytes [built] [code generated] + [used exports unknown] + entry ./example.js main +chunk (runtime: main) require_context_templates_sync_recursive_.output.js 457 bytes [rendered] + > ./example.js 2:1-4:3 + dependent modules 240 bytes [dependent] 3 modules + ../require.context/templates/ sync ^\.\/.*$ 217 bytes [built] [code generated] + [no exports] + [used exports unknown] + cjs require context ./example.js 3:11-64 +webpack X.X.X compiled successfully ``` ## Production mode ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 - Asset Size Chunks Chunk Names -0.output.js 627 bytes 0 [emitted] - output.js 1.75 KiB 1 [emitted] main -Entrypoint main = output.js -chunk {0} 0.output.js 463 bytes <{1}> [rendered] - > [0] ./example.js 2:1-4:3 - [1] ../require.context/templates sync ^\.\/.*$ 217 bytes {0} [built] - cjs require context ../require.context/templates [0] ./example.js 3:11-64 - [2] ../require.context/templates/c.js 82 bytes {0} [optional] [built] - context element ./c.js [1] ../require.context/templates sync ^\.\/.*$ ./c.js - context element ./c [1] ../require.context/templates sync ^\.\/.*$ ./c - [3] ../require.context/templates/b.js 82 bytes {0} [optional] [built] - context element ./b.js [1] ../require.context/templates sync ^\.\/.*$ ./b.js - context element ./b [1] ../require.context/templates sync ^\.\/.*$ ./b - [4] ../require.context/templates/a.js 82 bytes {0} [optional] [built] - context element ./a.js [1] ../require.context/templates sync ^\.\/.*$ ./a.js - context element ./a [1] ../require.context/templates sync ^\.\/.*$ ./a -chunk {1} output.js (main) 276 bytes >{0}< [entry] [rendered] - > .\example.js main - [0] ./example.js 276 bytes {1} [built] - single entry .\example.js main +asset output.js 1.86 KiB [emitted] [minimized] (name: main) +asset require_context_templates_sync_recursive_.output.js 652 bytes [emitted] [minimized] +chunk (runtime: main) output.js (main) 266 bytes (javascript) 4.95 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 4.95 KiB 6 modules + ./example.js 266 bytes [built] [code generated] + [no exports used] + entry ./example.js main +chunk (runtime: main) require_context_templates_sync_recursive_.output.js 457 bytes [rendered] + > ./example.js 2:1-4:3 + dependent modules 240 bytes [dependent] 3 modules + ../require.context/templates/ sync ^\.\/.*$ 217 bytes [built] [code generated] + [no exports] + cjs require context ./example.js 3:11-64 +webpack X.X.X compiled successfully ``` diff --git a/examples/code-splitted-require.context/template.md b/examples/code-splitted-require.context/template.md index 0b7b60cfd4f..5c134294f17 100644 --- a/examples/code-splitted-require.context/template.md +++ b/examples/code-splitted-require.context/template.md @@ -1,19 +1,19 @@ # example.js ``` javascript -{{example.js}} +_{{example.js}}_ ``` # dist/output.js ``` javascript -{{dist/output.js}} +_{{dist/output.js}}_ ``` -# dist/0.output.js +# dist/require_context_templates_sync_recursive_.output.js ``` javascript -{{dist/0.output.js}} +_{{dist/require_context_templates_sync_recursive_.output.js}}_ ``` # Info @@ -21,11 +21,11 @@ ## Unoptimized ``` -{{stdout}} +_{{stdout}}_ ``` ## Production mode ``` -{{production:stdout}} +_{{production:stdout}}_ ``` diff --git a/examples/code-splitted-require.context/webpack.config.js b/examples/code-splitted-require.context/webpack.config.js index 0d554bf62ea..3a2046aae21 100644 --- a/examples/code-splitted-require.context/webpack.config.js +++ b/examples/code-splitted-require.context/webpack.config.js @@ -1,5 +1,10 @@ -module.exports = { +"use strict"; + +/** @type {import("webpack").Configuration} */ +const config = { optimization: { - occurrenceOrder: true // To keep filename consistent between different modes (for example building only) + chunkIds: "named" // To keep filename consistent between different modes (for example building only) } }; + +module.exports = config; diff --git a/examples/code-splitting-bundle-loader/README.md b/examples/code-splitting-bundle-loader/README.md index a59a581ea7c..2bb07917ae1 100644 --- a/examples/code-splitting-bundle-loader/README.md +++ b/examples/code-splitting-bundle-loader/README.md @@ -4,7 +4,7 @@ The bundle loader is used to create a wrapper module for `file.js` that loads th # example.js -``` javascript +```javascript require("bundle-loader!./file.js")(function(fileJsExports) { console.log(fileJsExports); }); @@ -12,257 +12,276 @@ require("bundle-loader!./file.js")(function(fileJsExports) { # file.js -``` javascript +```javascript module.exports = "It works"; ``` - # dist/output.js -
/******/ (function(modules) { /* webpackBootstrap */ }) +```javascript +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ([ +/* 0 */, +/* 1 */ +/*!***********************************************************!*\ + !*** ../../node_modules/bundle-loader/index.js!./file.js ***! + \***********************************************************/ +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: module, __webpack_require__, __webpack_require__.e, __webpack_require__.* */ +/*! CommonJS bailout: module.exports is used directly at 3:0-14 */ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { -``` javascript -/******/ (function(modules) { // webpackBootstrap -/******/ // install a JSONP callback for chunk loading -/******/ function webpackJsonpCallback(data) { -/******/ var chunkIds = data[0]; -/******/ var moreModules = data[1]; -/******/ -/******/ // add "moreModules" to the modules object, -/******/ // then flag all "chunkIds" as loaded and fire callback -/******/ var moduleId, chunkId, i = 0, resolves = []; -/******/ for(;i < chunkIds.length; i++) { -/******/ chunkId = chunkIds[i]; -/******/ if(installedChunks[chunkId]) { -/******/ resolves.push(installedChunks[chunkId][0]); -/******/ } -/******/ installedChunks[chunkId] = 0; -/******/ } -/******/ for(moduleId in moreModules) { -/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { -/******/ modules[moduleId] = moreModules[moduleId]; -/******/ } -/******/ } -/******/ if(parentJsonpFunction) parentJsonpFunction(data); -/******/ while(resolves.length) { -/******/ resolves.shift()(); -/******/ } -/******/ -/******/ }; -/******/ -/******/ +var cbs = [], + data; +module.exports = function(cb) { + if(cbs) cbs.push(cb); + else cb(data); +} +__webpack_require__.e(/*! require.ensure */ "file_js").then((function(require) { + data = __webpack_require__(/*! !!./file.js */ 2); + var callbacks = cbs; + cbs = null; + for(var i = 0, l = callbacks.length; i < l; i++) { + callbacks[i](data); + } +}).bind(null, __webpack_require__))['catch'](__webpack_require__.oe); + +/***/ }) +/******/ ]); +``` + +
/* webpack runtime code */ + +``` js +/************************************************************************/ /******/ // The module cache -/******/ var installedModules = {}; -/******/ -/******/ // object to store loaded and loading chunks -/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched -/******/ // Promise = chunk loading, 0 = chunk loaded -/******/ var installedChunks = { -/******/ 1: 0 -/******/ }; -/******/ -/******/ -/******/ -/******/ // script path function -/******/ function jsonpScriptSrc(chunkId) { -/******/ return __webpack_require__.p + "" + chunkId + ".output.js" -/******/ } -/******/ +/******/ const __webpack_module_cache__ = {}; +/******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { -/******/ /******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) { -/******/ return installedModules[moduleId].exports; +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, +/******/ const module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed /******/ exports: {} /******/ }; -/******/ +/******/ /******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } -/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = __webpack_modules__; +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/ensure chunk */ +/******/ __webpack_require__.f = {}; /******/ // This file contains only the entry chunk. /******/ // The chunk loading function for additional chunks -/******/ __webpack_require__.e = function requireEnsure(chunkId) { -/******/ var promises = []; -/******/ -/******/ -/******/ // JSONP chunk loading for javascript -/******/ -/******/ var installedChunkData = installedChunks[chunkId]; -/******/ if(installedChunkData !== 0) { // 0 means "already installed". -/******/ -/******/ // a Promise means "currently loading". -/******/ if(installedChunkData) { -/******/ promises.push(installedChunkData[2]); -/******/ } else { -/******/ // setup Promise in chunk cache -/******/ var promise = new Promise(function(resolve, reject) { -/******/ installedChunkData = installedChunks[chunkId] = [resolve, reject]; -/******/ }); -/******/ promises.push(installedChunkData[2] = promise); -/******/ -/******/ // start chunk loading -/******/ var head = document.getElementsByTagName('head')[0]; -/******/ var script = document.createElement('script'); -/******/ +/******/ __webpack_require__.e = (chunkId) => { +/******/ return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => { +/******/ __webpack_require__.f[key](chunkId, promises); +/******/ return promises; +/******/ }, [])); +/******/ }; +/******/ +/******/ /* webpack/runtime/get javascript chunk filename */ +/******/ // This function allow to reference async chunks +/******/ __webpack_require__.u = (chunkId) => { +/******/ // return url for filenames based on template +/******/ return "" + chunkId + ".output.js"; +/******/ }; +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)); +/******/ +/******/ /* webpack/runtime/load script */ +/******/ (() => { +/******/ const inProgress = {}; +/******/ // data-webpack is not used as build has no uniqueName +/******/ // loadScript function to load a script via script tag +/******/ __webpack_require__.l = (url, done, key, chunkId) => { +/******/ if(inProgress[url]) { inProgress[url].push(done); return; } +/******/ let script, needAttach; +/******/ if(key !== undefined) { +/******/ const scripts = document.getElementsByTagName("script"); +/******/ for(var i = 0; i < scripts.length; i++) { +/******/ const s = scripts[i]; +/******/ if(s.getAttribute("src") == url) { script = s; break; } +/******/ } +/******/ } +/******/ if(!script) { +/******/ needAttach = true; +/******/ script = document.createElement('script'); +/******/ /******/ script.charset = 'utf-8'; -/******/ script.timeout = 120; -/******/ /******/ if (__webpack_require__.nc) { /******/ script.setAttribute("nonce", __webpack_require__.nc); /******/ } -/******/ script.src = jsonpScriptSrc(chunkId); -/******/ var timeout = setTimeout(function(){ -/******/ onScriptComplete({ type: 'timeout', target: script }); -/******/ }, 120000); -/******/ script.onerror = script.onload = onScriptComplete; -/******/ function onScriptComplete(event) { -/******/ // avoid mem leaks in IE. -/******/ script.onerror = script.onload = null; -/******/ clearTimeout(timeout); -/******/ var chunk = installedChunks[chunkId]; -/******/ if(chunk !== 0) { -/******/ if(chunk) { -/******/ var errorType = event && (event.type === 'load' ? 'missing' : event.type); -/******/ var realSrc = event && event.target && event.target.src; -/******/ var error = new Error('Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'); -/******/ error.type = errorType; -/******/ error.request = realSrc; -/******/ chunk[1](error); +/******/ +/******/ +/******/ script.src = url; +/******/ } +/******/ inProgress[url] = [done]; +/******/ const onScriptComplete = (prev, event) => { +/******/ // avoid mem leaks in IE. +/******/ script.onerror = script.onload = null; +/******/ clearTimeout(timeout); +/******/ const doneFns = inProgress[url]; +/******/ delete inProgress[url]; +/******/ script.parentNode?.removeChild(script); +/******/ doneFns?.forEach((fn) => (fn(event))); +/******/ if(prev) return prev(event); +/******/ } +/******/ const timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000); +/******/ script.onerror = onScriptComplete.bind(null, script.onerror); +/******/ script.onload = onScriptComplete.bind(null, script.onload); +/******/ needAttach && document.head.appendChild(script); +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/publicPath */ +/******/ __webpack_require__.p = "dist/"; +/******/ +/******/ /* webpack/runtime/jsonp chunk loading */ +/******/ (() => { +/******/ // no baseURI +/******/ +/******/ // object to store loaded and loading chunks +/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched +/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded +/******/ const installedChunks = { +/******/ "main": 0 +/******/ }; +/******/ +/******/ __webpack_require__.f.j = (chunkId, promises) => { +/******/ // JSONP chunk loading for javascript +/******/ let installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined; +/******/ if(installedChunkData !== 0) { // 0 means "already installed". +/******/ +/******/ // a Promise means "currently loading". +/******/ if(installedChunkData) { +/******/ promises.push(installedChunkData[2]); +/******/ } else { +/******/ if(true) { // all chunks have JS +/******/ // setup Promise in chunk cache +/******/ const promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject])); +/******/ promises.push(installedChunkData[2] = promise); +/******/ +/******/ // start chunk loading +/******/ const url = __webpack_require__.p + __webpack_require__.u(chunkId); +/******/ // create error before stack unwound to get useful stacktrace later +/******/ const error = new Error(); +/******/ const loadingEnded = (event) => { +/******/ if(__webpack_require__.o(installedChunks, chunkId)) { +/******/ installedChunkData = installedChunks[chunkId]; +/******/ if(installedChunkData !== 0) installedChunks[chunkId] = undefined; +/******/ if(installedChunkData) { +/******/ const errorType = event && (event.type === 'load' ? 'missing' : event.type); +/******/ const realSrc = event && event.target && event.target.src; +/******/ error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'; +/******/ error.name = 'ChunkLoadError'; +/******/ error.type = errorType; +/******/ error.request = realSrc; +/******/ error.event = event; +/******/ installedChunkData[1](error); +/******/ } +/******/ } +/******/ }; +/******/ __webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId); /******/ } -/******/ installedChunks[chunkId] = undefined; /******/ } -/******/ }; -/******/ head.appendChild(script); +/******/ } +/******/ }; +/******/ +/******/ // no prefetching +/******/ +/******/ // no preloaded +/******/ +/******/ // no HMR +/******/ +/******/ // no HMR manifest +/******/ +/******/ // no on chunks loaded +/******/ +/******/ // install a JSONP callback for chunk loading +/******/ const webpackJsonpCallback = (parentChunkLoadingFunction, data) => { +/******/ let [chunkIds, moreModules, runtime] = data; +/******/ // add "moreModules" to the modules object, +/******/ // then flag all "chunkIds" as loaded and fire callback +/******/ var moduleId, chunkId, i = 0; +/******/ if(chunkIds.some((id) => (installedChunks[id] !== 0))) { +/******/ for(moduleId in moreModules) { +/******/ if(__webpack_require__.o(moreModules, moduleId)) { +/******/ __webpack_require__.m[moduleId] = moreModules[moduleId]; +/******/ } +/******/ } +/******/ if(runtime) var result = runtime(__webpack_require__); /******/ } +/******/ if(parentChunkLoadingFunction) parentChunkLoadingFunction(data); +/******/ for(;i < chunkIds.length; i++) { +/******/ chunkId = chunkIds[i]; +/******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) { +/******/ installedChunks[chunkId][0](); +/******/ } +/******/ installedChunks[chunkId] = 0; +/******/ } +/******/ /******/ } -/******/ return Promise.all(promises); -/******/ }; -/******/ -/******/ // 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, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); -/******/ } -/******/ }; -/******/ -/******/ // define __esModule on exports -/******/ __webpack_require__.r = function(exports) { -/******/ Object.defineProperty(exports, '__esModule', { value: true }); -/******/ }; -/******/ -/******/ // 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 = "dist/"; -/******/ -/******/ // on error function for async loading -/******/ __webpack_require__.oe = function(err) { console.error(err); throw err; }; -/******/ -/******/ var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || []; -/******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); -/******/ jsonpArray.push = webpackJsonpCallback; -/******/ jsonpArray = jsonpArray.slice(); -/******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]); -/******/ var parentJsonpFunction = oldJsonpFunction; -/******/ -/******/ -/******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 1); -/******/ }) +/******/ +/******/ const chunkLoadingGlobal = self["webpackChunk"] = self["webpackChunk"] || []; +/******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0)); +/******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal)); +/******/ })(); +/******/ /************************************************************************/ ```
-``` javascript -/******/ ([ -/* 0 */ -/*!******************************************************!*\ - !*** (webpack)/node_modules/bundle-loader!./file.js ***! - \******************************************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -var cbs = [], - data; -module.exports = function(cb) { - if(cbs) cbs.push(cb); - else cb(data); -} -__webpack_require__.e(/*! require.ensure */ 0).then((function(require) { - data = __webpack_require__(/*! !./file.js */ 2); - var callbacks = cbs; - cbs = null; - for(var i = 0, l = callbacks.length; i < l; i++) { - callbacks[i](data); - } -}).bind(null, __webpack_require__)).catch(__webpack_require__.oe); - -/***/ }), -/* 1 */ +``` js +// This entry needs to be wrapped in an IIFE because it needs to be isolated against other modules in the chunk. +(() => { /*!********************!*\ !*** ./example.js ***! \********************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -__webpack_require__(/*! bundle-loader!./file.js */ 0)(function(fileJsExports) { +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: __webpack_require__ */ +__webpack_require__(/*! bundle-loader!./file.js */ 1)(function(fileJsExports) { console.log(fileJsExports); }); +})(); -/***/ }) -/******/ ]); +/******/ })() +; ``` -# dist/0.output.js +# dist/file_js.output.js -``` javascript -(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0],{ +```javascript +(self["webpackChunk"] = self["webpackChunk"] || []).push([["file_js"],{ -/***/ 2: +/***/ 2 /*!*****************!*\ !*** ./file.js ***! \*****************/ -/*! no static exports found */ -/***/ (function(module, exports) { +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: module */ +/*! CommonJS bailout: module.exports is used directly at 1:0-14 */ +(module) { module.exports = "It works"; -/***/ }) +/***/ } }]); ``` @@ -272,41 +291,41 @@ module.exports = "It works"; ## Unoptimized ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 - Asset Size Chunks Chunk Names -0.output.js 257 bytes 0 [emitted] - output.js 7.82 KiB 1 [emitted] main -Entrypoint main = output.js -chunk {0} 0.output.js 28 bytes <{1}> [rendered] - > [0] (webpack)/node_modules/bundle-loader!./file.js 7:0-14:2 - [2] ./file.js 28 bytes {0} [built] - cjs require !!./file.js [0] (webpack)/node_modules/bundle-loader!./file.js 8:8-30 -chunk {1} output.js (main) 378 bytes >{0}< [entry] [rendered] - > .\example.js main - [0] (webpack)/node_modules/bundle-loader!./file.js 281 bytes {1} [built] - cjs require bundle-loader!./file.js [1] ./example.js 1:0-34 - [1] ./example.js 97 bytes {1} [built] - single entry .\example.js main +asset output.js 9.5 KiB [emitted] (name: main) +asset file_js.output.js 348 bytes [emitted] +chunk (runtime: main) file_js.output.js 28 bytes [rendered] + > ../../node_modules/bundle-loader/index.js!./file.js 7:0-14:2 + ./file.js 28 bytes [built] [code generated] + [used exports unknown] + cjs self exports reference ./file.js 1:0-14 + cjs require !!./file.js ../../node_modules/bundle-loader/index.js!./file.js 8:8-30 +chunk (runtime: main) output.js (main) 375 bytes (javascript) 4.95 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 4.95 KiB 6 modules + dependent modules 281 bytes [dependent] 1 module + ./example.js 94 bytes [built] [code generated] + [used exports unknown] + entry ./example.js main +webpack X.X.X compiled successfully ``` ## Production mode ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 - Asset Size Chunks Chunk Names -0.output.js 98 bytes 0 [emitted] - output.js 1.81 KiB 1 [emitted] main -Entrypoint main = output.js -chunk {0} 0.output.js 28 bytes <{1}> [rendered] - > [0] (webpack)/node_modules/bundle-loader!./file.js 7:0-14:2 - [2] ./file.js 28 bytes {0} [built] - cjs require !!./file.js [0] (webpack)/node_modules/bundle-loader!./file.js 8:8-30 -chunk {1} output.js (main) 378 bytes >{0}< [entry] [rendered] - > .\example.js main - [0] (webpack)/node_modules/bundle-loader!./file.js 281 bytes {1} [built] - cjs require bundle-loader!./file.js [1] ./example.js 1:0-34 - [1] ./example.js 97 bytes {1} [built] - single entry .\example.js main +asset output.js 1.88 KiB [emitted] [minimized] (name: main) +asset file_js.output.js 93 bytes [emitted] [minimized] +chunk (runtime: main) file_js.output.js 28 bytes [rendered] + > ../../node_modules/bundle-loader/index.js!./file.js 7:0-14:2 + ./file.js 28 bytes [built] [code generated] + [used exports unknown] + cjs self exports reference ./file.js 1:0-14 + cjs require !!./file.js ../../node_modules/bundle-loader/index.js!./file.js 8:8-30 +chunk (runtime: main) output.js (main) 375 bytes (javascript) 4.95 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 4.95 KiB 6 modules + dependent modules 281 bytes [dependent] 1 module + ./example.js 94 bytes [built] [code generated] + [no exports used] + entry ./example.js main +webpack X.X.X compiled successfully ``` diff --git a/examples/code-splitting-bundle-loader/template.md b/examples/code-splitting-bundle-loader/template.md index f7c26e8b154..38e642808d5 100644 --- a/examples/code-splitting-bundle-loader/template.md +++ b/examples/code-splitting-bundle-loader/template.md @@ -4,27 +4,26 @@ The bundle loader is used to create a wrapper module for `file.js` that loads th # example.js -``` javascript -{{example.js}} +```javascript +_{{example.js}}_ ``` # file.js -``` javascript -{{file.js}} +```javascript +_{{file.js}}_ ``` - # dist/output.js -``` javascript -{{dist/output.js}} +```javascript +_{{dist/output.js}}_ ``` -# dist/0.output.js +# dist/file_js.output.js -``` javascript -{{dist/0.output.js}} +```javascript +_{{dist/file_js.output.js}}_ ``` # Info @@ -32,11 +31,11 @@ The bundle loader is used to create a wrapper module for `file.js` that loads th ## Unoptimized ``` -{{stdout}} +_{{stdout}}_ ``` ## Production mode ``` -{{production:stdout}} +_{{production:stdout}}_ ``` diff --git a/examples/code-splitting-bundle-loader/webpack.config.js b/examples/code-splitting-bundle-loader/webpack.config.js index 0d554bf62ea..3a2046aae21 100644 --- a/examples/code-splitting-bundle-loader/webpack.config.js +++ b/examples/code-splitting-bundle-loader/webpack.config.js @@ -1,5 +1,10 @@ -module.exports = { +"use strict"; + +/** @type {import("webpack").Configuration} */ +const config = { optimization: { - occurrenceOrder: true // To keep filename consistent between different modes (for example building only) + chunkIds: "named" // To keep filename consistent between different modes (for example building only) } }; + +module.exports = config; diff --git a/examples/code-splitting-depend-on-advanced/README.md b/examples/code-splitting-depend-on-advanced/README.md new file mode 100644 index 00000000000..38cb14fb367 --- /dev/null +++ b/examples/code-splitting-depend-on-advanced/README.md @@ -0,0 +1,694 @@ +This example shows how to use Code Splitting with entrypoint dependOn + +# webpack.config.js + +```javascript +"use strict"; + +/** @type {import("webpack").Configuration} */ +const config = { + entry: { + app: { import: "./app.js", dependOn: ["other-vendors"] }, + page1: { import: "./page1.js", dependOn: ["app", "react-vendors"] }, + "react-vendors": ["react", "react-dom", "prop-types"], + "other-vendors": "./other-vendors" + }, + optimization: { + runtimeChunk: "single", + chunkIds: "named" // To keep filename consistent between different modes (for example building only) + }, + stats: { + chunks: true, + chunkRelations: true + } +}; + +module.exports = config; +``` + +# app.js + +```javascript +import isomorphicFetch from "isomorphic-fetch"; +import lodash from "lodash"; + +console.log(isomorphicFetch, lodash); +``` + +# page1.js + +```javascript +import isomorphicFetch from "isomorphic-fetch"; +import react from "react"; +import reactDOM from "react-dom"; + +console.log(isomorphicFetch, react, reactDOM); + +import("./lazy"); +``` + +# lazy.js + +```javascript +import lodash from "lodash"; +import propTypes from "prop-types"; + +console.log(lodash, propTypes); +``` + +# other-vendors.js + +```javascript +import lodash from "lodash"; +import isomorphicFetch from "isomorphic-fetch"; + +// Additional initializations +console.log(lodash, isomorphicFetch); +``` + +# dist/runtime.js + +```javascript +/******/ (() => { // webpackBootstrap +/******/ "use strict"; +/******/ var __webpack_modules__ = ({}); +``` + +
/* webpack runtime code */ + +``` js +/************************************************************************/ +/******/ // The module cache +/******/ const __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ const 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; +/******/ } +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = __webpack_modules__; +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/chunk loaded */ +/******/ (() => { +/******/ const deferred = []; +/******/ __webpack_require__.O = (result, chunkIds, fn, priority) => { +/******/ if(chunkIds) { +/******/ priority = priority || 0; +/******/ for(var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1]; +/******/ deferred[i] = [chunkIds, fn, priority]; +/******/ return; +/******/ } +/******/ let notFulfilled = Infinity; +/******/ for (var i = 0; i < deferred.length; i++) { +/******/ let [chunkIds, fn, priority] = deferred[i]; +/******/ let fulfilled = true; +/******/ for (var j = 0; j < chunkIds.length; j++) { +/******/ if ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every((key) => (__webpack_require__.O[key](chunkIds[j])))) { +/******/ chunkIds.splice(j--, 1); +/******/ } else { +/******/ fulfilled = false; +/******/ if(priority < notFulfilled) notFulfilled = priority; +/******/ } +/******/ } +/******/ if(fulfilled) { +/******/ deferred.splice(i--, 1) +/******/ const r = fn(); +/******/ if (r !== undefined) result = r; +/******/ } +/******/ } +/******/ return result; +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/compat get default export */ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = (module) => { +/******/ const getter = module && module.__esModule ? +/******/ () => (module['default']) : +/******/ () => (module); +/******/ __webpack_require__.d(getter, { a: getter }); +/******/ return getter; +/******/ }; +/******/ +/******/ /* webpack/runtime/define property getters */ +/******/ // define getter/value functions for harmony exports +/******/ __webpack_require__.d = (exports, definition) => { +/******/ if(Array.isArray(definition)) { +/******/ var i = 0; +/******/ while(i < definition.length) { +/******/ var key = definition[i++]; +/******/ var binding = definition[i++]; +/******/ if(!__webpack_require__.o(exports, key)) { +/******/ if(binding === 0) { +/******/ Object.defineProperty(exports, key, { enumerable: true, value: definition[i++] }); +/******/ } else { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: binding }); +/******/ } +/******/ } else if(binding === 0) { i++; } +/******/ } +/******/ } else { +/******/ for(var key in definition) { +/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); +/******/ } +/******/ } +/******/ } +/******/ }; +/******/ +/******/ /* webpack/runtime/ensure chunk */ +/******/ __webpack_require__.f = {}; +/******/ // This file contains only the entry chunk. +/******/ // The chunk loading function for additional chunks +/******/ __webpack_require__.e = (chunkId) => { +/******/ return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => { +/******/ __webpack_require__.f[key](chunkId, promises); +/******/ return promises; +/******/ }, [])); +/******/ }; +/******/ +/******/ /* webpack/runtime/get javascript chunk filename */ +/******/ // This function allow to reference async chunks +/******/ __webpack_require__.u = (chunkId) => { +/******/ // return url for filenames based on template +/******/ return "" + chunkId + ".js"; +/******/ }; +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)); +/******/ +/******/ /* webpack/runtime/load script */ +/******/ (() => { +/******/ const inProgress = {}; +/******/ // data-webpack is not used as build has no uniqueName +/******/ // loadScript function to load a script via script tag +/******/ __webpack_require__.l = (url, done, key, chunkId) => { +/******/ if(inProgress[url]) { inProgress[url].push(done); return; } +/******/ let script, needAttach; +/******/ if(key !== undefined) { +/******/ const scripts = document.getElementsByTagName("script"); +/******/ for(var i = 0; i < scripts.length; i++) { +/******/ const s = scripts[i]; +/******/ if(s.getAttribute("src") == url) { script = s; break; } +/******/ } +/******/ } +/******/ if(!script) { +/******/ needAttach = true; +/******/ script = document.createElement('script'); +/******/ +/******/ script.charset = 'utf-8'; +/******/ if (__webpack_require__.nc) { +/******/ script.setAttribute("nonce", __webpack_require__.nc); +/******/ } +/******/ +/******/ +/******/ script.src = url; +/******/ } +/******/ inProgress[url] = [done]; +/******/ const onScriptComplete = (prev, event) => { +/******/ // avoid mem leaks in IE. +/******/ script.onerror = script.onload = null; +/******/ clearTimeout(timeout); +/******/ const doneFns = inProgress[url]; +/******/ delete inProgress[url]; +/******/ script.parentNode?.removeChild(script); +/******/ doneFns?.forEach((fn) => (fn(event))); +/******/ if(prev) return prev(event); +/******/ } +/******/ const timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000); +/******/ script.onerror = onScriptComplete.bind(null, script.onerror); +/******/ script.onload = onScriptComplete.bind(null, script.onload); +/******/ needAttach && document.head.appendChild(script); +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/make namespace object */ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = (exports) => { +/******/ if(Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/******/ /* webpack/runtime/publicPath */ +/******/ __webpack_require__.p = "dist/"; +/******/ +/******/ /* webpack/runtime/jsonp chunk loading */ +/******/ (() => { +/******/ // no baseURI +/******/ +/******/ // object to store loaded and loading chunks +/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched +/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded +/******/ const installedChunks = { +/******/ "runtime": 0 +/******/ }; +/******/ +/******/ __webpack_require__.f.j = (chunkId, promises) => { +/******/ // JSONP chunk loading for javascript +/******/ let installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined; +/******/ if(installedChunkData !== 0) { // 0 means "already installed". +/******/ +/******/ // a Promise means "currently loading". +/******/ if(installedChunkData) { +/******/ promises.push(installedChunkData[2]); +/******/ } else { +/******/ if("runtime" != chunkId) { +/******/ // setup Promise in chunk cache +/******/ const promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject])); +/******/ promises.push(installedChunkData[2] = promise); +/******/ +/******/ // start chunk loading +/******/ const url = __webpack_require__.p + __webpack_require__.u(chunkId); +/******/ // create error before stack unwound to get useful stacktrace later +/******/ const error = new Error(); +/******/ const loadingEnded = (event) => { +/******/ if(__webpack_require__.o(installedChunks, chunkId)) { +/******/ installedChunkData = installedChunks[chunkId]; +/******/ if(installedChunkData !== 0) installedChunks[chunkId] = undefined; +/******/ if(installedChunkData) { +/******/ const errorType = event && (event.type === 'load' ? 'missing' : event.type); +/******/ const realSrc = event && event.target && event.target.src; +/******/ error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'; +/******/ error.name = 'ChunkLoadError'; +/******/ error.type = errorType; +/******/ error.request = realSrc; +/******/ error.event = event; +/******/ installedChunkData[1](error); +/******/ } +/******/ } +/******/ }; +/******/ __webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId); +/******/ } else installedChunks[chunkId] = 0; +/******/ } +/******/ } +/******/ }; +/******/ +/******/ // no prefetching +/******/ +/******/ // no preloaded +/******/ +/******/ // no HMR +/******/ +/******/ // no HMR manifest +/******/ +/******/ __webpack_require__.O.j = (chunkId) => (installedChunks[chunkId] === 0); +/******/ +/******/ // install a JSONP callback for chunk loading +/******/ const webpackJsonpCallback = (parentChunkLoadingFunction, data) => { +/******/ let [chunkIds, moreModules, runtime] = data; +/******/ // add "moreModules" to the modules object, +/******/ // then flag all "chunkIds" as loaded and fire callback +/******/ var moduleId, chunkId, i = 0; +/******/ if(chunkIds.some((id) => (installedChunks[id] !== 0))) { +/******/ for(moduleId in moreModules) { +/******/ if(__webpack_require__.o(moreModules, moduleId)) { +/******/ __webpack_require__.m[moduleId] = moreModules[moduleId]; +/******/ } +/******/ } +/******/ if(runtime) var result = runtime(__webpack_require__); +/******/ } +/******/ if(parentChunkLoadingFunction) parentChunkLoadingFunction(data); +/******/ for(;i < chunkIds.length; i++) { +/******/ chunkId = chunkIds[i]; +/******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) { +/******/ installedChunks[chunkId][0](); +/******/ } +/******/ installedChunks[chunkId] = 0; +/******/ } +/******/ return __webpack_require__.O(result); +/******/ } +/******/ +/******/ const chunkLoadingGlobal = self["webpackChunk"] = self["webpackChunk"] || []; +/******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0)); +/******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal)); +/******/ })(); +/******/ +/************************************************************************/ +``` + +
+ +``` js +/******/ +/******/ +/******/ })() +; +``` + +# dist/app.js + +```javascript +"use strict"; +(self["webpackChunk"] = self["webpackChunk"] || []).push([["app"],{ + +/***/ 6 +/*!****************!*\ + !*** ./app.js ***! + \****************/ +/*! namespace exports */ +/*! exports [not provided] [no usage info] */ +/*! runtime requirements: __webpack_require__, __webpack_require__.n, __webpack_require__.r, __webpack_exports__, __webpack_require__.* */ +(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var isomorphic_fetch__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! isomorphic-fetch */ 5); +/* harmony import */ var isomorphic_fetch__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(isomorphic_fetch__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var lodash__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! lodash */ 4); +/* harmony import */ var lodash__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(lodash__WEBPACK_IMPORTED_MODULE_1__); + + + +console.log((isomorphic_fetch__WEBPACK_IMPORTED_MODULE_0___default()), (lodash__WEBPACK_IMPORTED_MODULE_1___default())); + + +/***/ } + +}, +/******/ __webpack_require__ => { // webpackRuntimeModules +/******/ var __webpack_exec__ = (moduleId) => (__webpack_require__(__webpack_require__.s = moduleId)) +/******/ __webpack_require__.O(0, ["other-vendors"], () => (__webpack_exec__(6))); +/******/ var __webpack_exports__ = __webpack_require__.O(); +/******/ } +]); +``` + +# dist/page1.js + +```javascript +"use strict"; +(self["webpackChunk"] = self["webpackChunk"] || []).push([["page1"],{ + +/***/ 7 +/*!******************!*\ + !*** ./page1.js ***! + \******************/ +/*! namespace exports */ +/*! exports [not provided] [no usage info] */ +/*! runtime requirements: __webpack_require__, __webpack_require__.n, __webpack_require__.r, __webpack_exports__, __webpack_require__.e, __webpack_require__.* */ +(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var isomorphic_fetch__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! isomorphic-fetch */ 5); +/* harmony import */ var isomorphic_fetch__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(isomorphic_fetch__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react */ 0); +/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! react-dom */ 1); +/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(react_dom__WEBPACK_IMPORTED_MODULE_2__); + + + + +console.log((isomorphic_fetch__WEBPACK_IMPORTED_MODULE_0___default()), (react__WEBPACK_IMPORTED_MODULE_1___default()), (react_dom__WEBPACK_IMPORTED_MODULE_2___default())); + +__webpack_require__.e(/*! import() */ "lazy_js").then(__webpack_require__.bind(__webpack_require__, /*! ./lazy */ 8)); + + +/***/ } + +}, +/******/ __webpack_require__ => { // webpackRuntimeModules +/******/ var __webpack_exec__ = (moduleId) => (__webpack_require__(__webpack_require__.s = moduleId)) +/******/ __webpack_require__.O(0, ["app","react-vendors","other-vendors"], () => (__webpack_exec__(7))); +/******/ var __webpack_exports__ = __webpack_require__.O(); +/******/ } +]); +``` + +# dist/other-vendors.js + +```javascript +(self["webpackChunk"] = self["webpackChunk"] || []).push([["other-vendors"],[ +/* 0 */, +/* 1 */, +/* 2 */, +/* 3 */ +/*!**************************!*\ + !*** ./other-vendors.js ***! + \**************************/ +/*! namespace exports */ +/*! exports [not provided] [no usage info] */ +/*! runtime requirements: __webpack_require__, __webpack_require__.n, __webpack_require__.r, __webpack_exports__, __webpack_require__.* */ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var lodash__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! lodash */ 4); +/* harmony import */ var lodash__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(lodash__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var isomorphic_fetch__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! isomorphic-fetch */ 5); +/* harmony import */ var isomorphic_fetch__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(isomorphic_fetch__WEBPACK_IMPORTED_MODULE_1__); + + + +// Additional initializations +console.log((lodash__WEBPACK_IMPORTED_MODULE_0___default()), (isomorphic_fetch__WEBPACK_IMPORTED_MODULE_1___default())); + + +/***/ }), +/* 4 */ +/*!********************************!*\ + !*** ./node_modules/lodash.js ***! + \********************************/ +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: module */ +/*! CommonJS bailout: module.exports is used directly at 1:0-14 */ +/***/ ((module) => { + +module.exports = 'lodash'; + + +/***/ }), +/* 5 */ +/*!******************************************!*\ + !*** ./node_modules/isomorphic-fetch.js ***! + \******************************************/ +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: module */ +/*! CommonJS bailout: module.exports is used directly at 1:0-14 */ +/***/ ((module) => { + +module.exports = "isomorphic-fetch"; + + +/***/ }) +], +/******/ __webpack_require__ => { // webpackRuntimeModules +/******/ var __webpack_exec__ = (moduleId) => (__webpack_require__(__webpack_require__.s = moduleId)) +/******/ var __webpack_exports__ = (__webpack_exec__(3)); +/******/ } +]); +``` + +# dist/react-vendors.js + +```javascript +(self["webpackChunk"] = self["webpackChunk"] || []).push([["react-vendors"],[ +/* 0 */ +/*!*******************************!*\ + !*** ./node_modules/react.js ***! + \*******************************/ +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: module */ +/*! CommonJS bailout: module.exports is used directly at 1:0-14 */ +/***/ ((module) => { + +module.exports = 'react'; + + +/***/ }), +/* 1 */ +/*!***********************************!*\ + !*** ./node_modules/react-dom.js ***! + \***********************************/ +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: module */ +/*! CommonJS bailout: module.exports is used directly at 1:0-14 */ +/***/ ((module) => { + +module.exports = 'react-dom'; + + +/***/ }), +/* 2 */ +/*!************************************!*\ + !*** ./node_modules/prop-types.js ***! + \************************************/ +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: module */ +/*! CommonJS bailout: module.exports is used directly at 1:0-14 */ +/***/ ((module) => { + +module.exports = 'prop-types'; + + +/***/ }) +], +/******/ __webpack_require__ => { // webpackRuntimeModules +/******/ var __webpack_exec__ = (moduleId) => (__webpack_require__(__webpack_require__.s = moduleId)) +/******/ var __webpack_exports__ = (__webpack_exec__(0), __webpack_exec__(1), __webpack_exec__(2)); +/******/ } +]); +``` + +# Info + +## Unoptimized + +``` +asset runtime.js 11.4 KiB [emitted] (name: runtime) +asset other-vendors.js 2.13 KiB [emitted] (name: other-vendors) +asset page1.js 1.9 KiB [emitted] (name: page1) +asset app.js 1.43 KiB [emitted] (name: app) +asset react-vendors.js 1.33 KiB [emitted] (name: react-vendors) +asset lazy_js.js 1.1 KiB [emitted] +Entrypoint app 1.43 KiB = app.js +Entrypoint page1 1.9 KiB = page1.js +Entrypoint react-vendors 12.8 KiB = runtime.js 11.4 KiB react-vendors.js 1.33 KiB +Entrypoint other-vendors 13.6 KiB = runtime.js 11.4 KiB other-vendors.js 2.13 KiB +chunk (runtime: runtime) app.js (app) 116 bytes <{other-vendors}> <{runtime}> >{page1}< [initial] [rendered] + > ./app.js app + ./app.js 116 bytes [built] [code generated] + [no exports] + [used exports unknown] + entry ./app.js app +chunk (runtime: runtime) lazy_js.js 98 bytes <{page1}> [rendered] + > ./lazy ./page1.js 7:0-16 + ./lazy.js 98 bytes [built] [code generated] + [no exports] + [used exports unknown] + import() ./lazy ./page1.js 7:0-16 +chunk (runtime: runtime) other-vendors.js (other-vendors) 210 bytes ={runtime}= >{app}< [initial] [rendered] + > ./other-vendors other-vendors + dependent modules 64 bytes [dependent] 2 modules + ./other-vendors.js 146 bytes [built] [code generated] + [no exports] + [used exports unknown] + entry ./other-vendors other-vendors +chunk (runtime: runtime) page1.js (page1) 176 bytes <{app}> <{react-vendors}> <{runtime}> >{lazy_js}< [initial] [rendered] + > ./page1.js page1 + ./page1.js 176 bytes [built] [code generated] + [no exports] + [used exports unknown] + entry ./page1.js page1 +chunk (runtime: runtime) react-vendors.js (react-vendors) 87 bytes ={runtime}= >{page1}< [initial] [rendered] + > prop-types react-vendors + > react react-vendors + > react-dom react-vendors + ./node_modules/prop-types.js 31 bytes [built] [code generated] + [used exports unknown] + from origin ./lazy.js + harmony side effect evaluation prop-types ./lazy.js 2:0-35 + harmony import specifier prop-types ./lazy.js 4:20-29 + cjs self exports reference ./node_modules/prop-types.js 1:0-14 + entry prop-types react-vendors + ./node_modules/react-dom.js 30 bytes [built] [code generated] + [used exports unknown] + from origin ./page1.js + harmony side effect evaluation react-dom ./page1.js 3:0-33 + harmony import specifier react-dom ./page1.js 5:36-44 + cjs self exports reference ./node_modules/react-dom.js 1:0-14 + entry react-dom react-vendors + ./node_modules/react.js 26 bytes [built] [code generated] + [used exports unknown] + from origin ./page1.js + harmony side effect evaluation react ./page1.js 2:0-26 + harmony import specifier react ./page1.js 5:29-34 + cjs self exports reference ./node_modules/react.js 1:0-14 + entry react react-vendors +chunk (runtime: runtime) runtime.js (runtime) 7.18 KiB ={other-vendors}= ={react-vendors}= >{app}< >{page1}< [entry] [rendered] + > ./other-vendors other-vendors + > prop-types react-vendors + > react react-vendors + > react-dom react-vendors + runtime modules 7.18 KiB 10 modules +webpack X.X.X compiled successfully +``` + +## Production mode + +``` +asset runtime.js 2.6 KiB [emitted] [minimized] (name: runtime) +asset page1.js 278 bytes [emitted] [minimized] (name: page1) +asset other-vendors.js 236 bytes [emitted] [minimized] (name: other-vendors) +asset react-vendors.js 201 bytes [emitted] [minimized] (name: react-vendors) +asset app.js 197 bytes [emitted] [minimized] (name: app) +asset lazy_js.js 157 bytes [emitted] [minimized] +Entrypoint app 197 bytes = app.js +Entrypoint page1 278 bytes = page1.js +Entrypoint react-vendors 2.8 KiB = runtime.js 2.6 KiB react-vendors.js 201 bytes +Entrypoint other-vendors 2.83 KiB = runtime.js 2.6 KiB other-vendors.js 236 bytes +chunk (runtime: runtime) app.js (app) 116 bytes <{other-vendors}> <{runtime}> >{page1}< [initial] [rendered] + > ./app.js app + ./app.js 116 bytes [built] [code generated] + [no exports] + [no exports used] + entry ./app.js app +chunk (runtime: runtime) lazy_js.js 98 bytes <{page1}> [rendered] + > ./lazy ./page1.js 7:0-16 + ./lazy.js 98 bytes [built] [code generated] + [no exports] + import() ./lazy ./page1.js 7:0-16 +chunk (runtime: runtime) other-vendors.js (other-vendors) 210 bytes ={runtime}= >{app}< [initial] [rendered] + > ./other-vendors other-vendors + dependent modules 64 bytes [dependent] 2 modules + ./other-vendors.js 146 bytes [built] [code generated] + [no exports] + [no exports used] + entry ./other-vendors other-vendors +chunk (runtime: runtime) page1.js (page1) 176 bytes <{app}> <{react-vendors}> <{runtime}> >{lazy_js}< [initial] [rendered] + > ./page1.js page1 + ./page1.js 176 bytes [built] [code generated] + [no exports] + [no exports used] + entry ./page1.js page1 +chunk (runtime: runtime) react-vendors.js (react-vendors) 87 bytes ={runtime}= >{page1}< [initial] [rendered] + > prop-types react-vendors + > react react-vendors + > react-dom react-vendors + ./node_modules/prop-types.js 31 bytes [built] [code generated] + [used exports unknown] + from origin ./lazy.js + harmony side effect evaluation prop-types ./lazy.js 2:0-35 + harmony import specifier prop-types ./lazy.js 4:20-29 + cjs self exports reference ./node_modules/prop-types.js 1:0-14 + entry prop-types react-vendors + ./node_modules/react-dom.js 30 bytes [built] [code generated] + [used exports unknown] + from origin ./page1.js + harmony side effect evaluation react-dom ./page1.js 3:0-33 + harmony import specifier react-dom ./page1.js 5:36-44 + cjs self exports reference ./node_modules/react-dom.js 1:0-14 + entry react-dom react-vendors + ./node_modules/react.js 26 bytes [built] [code generated] + [used exports unknown] + from origin ./page1.js + harmony side effect evaluation react ./page1.js 2:0-26 + harmony import specifier react ./page1.js 5:29-34 + cjs self exports reference ./node_modules/react.js 1:0-14 + entry react react-vendors +chunk (runtime: runtime) runtime.js (runtime) 7.18 KiB ={other-vendors}= ={react-vendors}= >{app}< >{page1}< [entry] [rendered] + > ./other-vendors other-vendors + > prop-types react-vendors + > react react-vendors + > react-dom react-vendors + runtime modules 7.18 KiB 10 modules +webpack X.X.X compiled successfully +``` diff --git a/examples/code-splitting-depend-on-advanced/app.js b/examples/code-splitting-depend-on-advanced/app.js new file mode 100644 index 00000000000..2fd657f1d8f --- /dev/null +++ b/examples/code-splitting-depend-on-advanced/app.js @@ -0,0 +1,4 @@ +import isomorphicFetch from "isomorphic-fetch"; +import lodash from "lodash"; + +console.log(isomorphicFetch, lodash); diff --git a/examples/i18n/build.js b/examples/code-splitting-depend-on-advanced/build.js similarity index 100% rename from examples/i18n/build.js rename to examples/code-splitting-depend-on-advanced/build.js diff --git a/examples/code-splitting-depend-on-advanced/lazy.js b/examples/code-splitting-depend-on-advanced/lazy.js new file mode 100644 index 00000000000..e2013cc26fd --- /dev/null +++ b/examples/code-splitting-depend-on-advanced/lazy.js @@ -0,0 +1,4 @@ +import lodash from "lodash"; +import propTypes from "prop-types"; + +console.log(lodash, propTypes); diff --git a/examples/code-splitting-depend-on-advanced/node_modules/isomorphic-fetch.js b/examples/code-splitting-depend-on-advanced/node_modules/isomorphic-fetch.js new file mode 100644 index 00000000000..ce0c36b3158 --- /dev/null +++ b/examples/code-splitting-depend-on-advanced/node_modules/isomorphic-fetch.js @@ -0,0 +1 @@ +module.exports = "isomorphic-fetch"; diff --git a/examples/code-splitting-depend-on-advanced/node_modules/lodash.js b/examples/code-splitting-depend-on-advanced/node_modules/lodash.js new file mode 100644 index 00000000000..8cae1154e6b --- /dev/null +++ b/examples/code-splitting-depend-on-advanced/node_modules/lodash.js @@ -0,0 +1 @@ +module.exports = 'lodash'; diff --git a/examples/code-splitting-depend-on-advanced/node_modules/prop-types.js b/examples/code-splitting-depend-on-advanced/node_modules/prop-types.js new file mode 100644 index 00000000000..9c6971329ce --- /dev/null +++ b/examples/code-splitting-depend-on-advanced/node_modules/prop-types.js @@ -0,0 +1 @@ +module.exports = 'prop-types'; diff --git a/examples/code-splitting-depend-on-advanced/node_modules/react-dom.js b/examples/code-splitting-depend-on-advanced/node_modules/react-dom.js new file mode 100644 index 00000000000..d2f4c643e28 --- /dev/null +++ b/examples/code-splitting-depend-on-advanced/node_modules/react-dom.js @@ -0,0 +1 @@ +module.exports = 'react-dom'; diff --git a/examples/code-splitting-depend-on-advanced/node_modules/react.js b/examples/code-splitting-depend-on-advanced/node_modules/react.js new file mode 100644 index 00000000000..11c0b89c737 --- /dev/null +++ b/examples/code-splitting-depend-on-advanced/node_modules/react.js @@ -0,0 +1 @@ +module.exports = 'react'; diff --git a/examples/code-splitting-depend-on-advanced/other-vendors.js b/examples/code-splitting-depend-on-advanced/other-vendors.js new file mode 100644 index 00000000000..49e051a810c --- /dev/null +++ b/examples/code-splitting-depend-on-advanced/other-vendors.js @@ -0,0 +1,5 @@ +import lodash from "lodash"; +import isomorphicFetch from "isomorphic-fetch"; + +// Additional initializations +console.log(lodash, isomorphicFetch); diff --git a/examples/code-splitting-depend-on-advanced/page1.js b/examples/code-splitting-depend-on-advanced/page1.js new file mode 100644 index 00000000000..18c9e0c9ba6 --- /dev/null +++ b/examples/code-splitting-depend-on-advanced/page1.js @@ -0,0 +1,7 @@ +import isomorphicFetch from "isomorphic-fetch"; +import react from "react"; +import reactDOM from "react-dom"; + +console.log(isomorphicFetch, react, reactDOM); + +import("./lazy"); diff --git a/examples/code-splitting-depend-on-advanced/template.md b/examples/code-splitting-depend-on-advanced/template.md new file mode 100644 index 00000000000..e2e842027e5 --- /dev/null +++ b/examples/code-splitting-depend-on-advanced/template.md @@ -0,0 +1,75 @@ +This example shows how to use Code Splitting with entrypoint dependOn + +# webpack.config.js + +```javascript +_{{webpack.config.js}}_ +``` + +# app.js + +```javascript +_{{app.js}}_ +``` + +# page1.js + +```javascript +_{{page1.js}}_ +``` + +# lazy.js + +```javascript +_{{lazy.js}}_ +``` + +# other-vendors.js + +```javascript +_{{other-vendors.js}}_ +``` + +# dist/runtime.js + +```javascript +_{{dist/runtime.js}}_ +``` + +# dist/app.js + +```javascript +_{{dist/app.js}}_ +``` + +# dist/page1.js + +```javascript +_{{dist/page1.js}}_ +``` + +# dist/other-vendors.js + +```javascript +_{{dist/other-vendors.js}}_ +``` + +# dist/react-vendors.js + +```javascript +_{{dist/react-vendors.js}}_ +``` + +# Info + +## Unoptimized + +``` +_{{stdout}}_ +``` + +## Production mode + +``` +_{{production:stdout}}_ +``` diff --git a/examples/code-splitting-depend-on-advanced/webpack.config.js b/examples/code-splitting-depend-on-advanced/webpack.config.js new file mode 100644 index 00000000000..620cd9240b6 --- /dev/null +++ b/examples/code-splitting-depend-on-advanced/webpack.config.js @@ -0,0 +1,21 @@ +"use strict"; + +/** @type {import("webpack").Configuration} */ +const config = { + entry: { + app: { import: "./app.js", dependOn: ["other-vendors"] }, + page1: { import: "./page1.js", dependOn: ["app", "react-vendors"] }, + "react-vendors": ["react", "react-dom", "prop-types"], + "other-vendors": "./other-vendors" + }, + optimization: { + runtimeChunk: "single", + chunkIds: "named" // To keep filename consistent between different modes (for example building only) + }, + stats: { + chunks: true, + chunkRelations: true + } +}; + +module.exports = config; diff --git a/examples/code-splitting-depend-on-simple/README.md b/examples/code-splitting-depend-on-simple/README.md new file mode 100644 index 00000000000..1a9a69cd0e8 --- /dev/null +++ b/examples/code-splitting-depend-on-simple/README.md @@ -0,0 +1,387 @@ +This example shows how to use Code Splitting with entrypoint dependOn + +# webpack.config.js + +```javascript +"use strict"; + +/** @type {import("webpack").Configuration} */ +const config = { + entry: { + app: { import: "./app.js", dependOn: ["react-vendors"] }, + "react-vendors": ["react", "react-dom", "prop-types"] + }, + optimization: { + chunkIds: "named" // To keep filename consistent between different modes (for example building only) + }, + stats: { + chunks: true, + chunkRelations: true + } +}; + +module.exports = config; +``` + +# app.js + +```javascript +import react from "react"; +import reactDOM from "react-dom"; +import propTypes from "prop-types"; + +console.log(react, reactDOM, propTypes); +``` + +# dist/app.js + +```javascript +"use strict"; +(self["webpackChunk"] = self["webpackChunk"] || []).push([["app"],{ + +/***/ 3 +/*!****************!*\ + !*** ./app.js ***! + \****************/ +/*! namespace exports */ +/*! exports [not provided] [no usage info] */ +/*! runtime requirements: __webpack_require__, __webpack_require__.n, __webpack_require__.r, __webpack_exports__, __webpack_require__.* */ +(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ 0); +/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-dom */ 1); +/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_dom__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var prop_types__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! prop-types */ 2); +/* harmony import */ var prop_types__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(prop_types__WEBPACK_IMPORTED_MODULE_2__); + + + + +console.log((react__WEBPACK_IMPORTED_MODULE_0___default()), (react_dom__WEBPACK_IMPORTED_MODULE_1___default()), (prop_types__WEBPACK_IMPORTED_MODULE_2___default())); + + +/***/ } + +}, +/******/ __webpack_require__ => { // webpackRuntimeModules +/******/ var __webpack_exec__ = (moduleId) => (__webpack_require__(__webpack_require__.s = moduleId)) +/******/ var __webpack_exports__ = (__webpack_exec__(3)); +/******/ } +]); +``` + +# dist/react-vendors.js + +```javascript +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ([ +/* 0 */ +/*!*******************************!*\ + !*** ./node_modules/react.js ***! + \*******************************/ +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: module */ +/*! CommonJS bailout: module.exports is used directly at 1:0-14 */ +/***/ ((module) => { + +module.exports = 'react'; + + +/***/ }), +/* 1 */ +/*!***********************************!*\ + !*** ./node_modules/react-dom.js ***! + \***********************************/ +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: module */ +/*! CommonJS bailout: module.exports is used directly at 1:0-14 */ +/***/ ((module) => { + +module.exports = 'react-dom'; + + +/***/ }), +/* 2 */ +/*!************************************!*\ + !*** ./node_modules/prop-types.js ***! + \************************************/ +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: module */ +/*! CommonJS bailout: module.exports is used directly at 1:0-14 */ +/***/ ((module) => { + +module.exports = 'prop-types'; + + +/***/ }) +/******/ ]); +``` + +
/* webpack runtime code */ + +``` js +/************************************************************************/ +/******/ // The module cache +/******/ const __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ const 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; +/******/ } +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = __webpack_modules__; +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/chunk loaded */ +/******/ (() => { +/******/ const deferred = []; +/******/ __webpack_require__.O = (result, chunkIds, fn, priority) => { +/******/ if(chunkIds) { +/******/ priority = priority || 0; +/******/ for(var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1]; +/******/ deferred[i] = [chunkIds, fn, priority]; +/******/ return; +/******/ } +/******/ let notFulfilled = Infinity; +/******/ for (var i = 0; i < deferred.length; i++) { +/******/ let [chunkIds, fn, priority] = deferred[i]; +/******/ let fulfilled = true; +/******/ for (var j = 0; j < chunkIds.length; j++) { +/******/ if ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every((key) => (__webpack_require__.O[key](chunkIds[j])))) { +/******/ chunkIds.splice(j--, 1); +/******/ } else { +/******/ fulfilled = false; +/******/ if(priority < notFulfilled) notFulfilled = priority; +/******/ } +/******/ } +/******/ if(fulfilled) { +/******/ deferred.splice(i--, 1) +/******/ const r = fn(); +/******/ if (r !== undefined) result = r; +/******/ } +/******/ } +/******/ return result; +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/compat get default export */ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = (module) => { +/******/ const getter = module && module.__esModule ? +/******/ () => (module['default']) : +/******/ () => (module); +/******/ __webpack_require__.d(getter, { a: getter }); +/******/ return getter; +/******/ }; +/******/ +/******/ /* webpack/runtime/define property getters */ +/******/ // define getter/value functions for harmony exports +/******/ __webpack_require__.d = (exports, definition) => { +/******/ if(Array.isArray(definition)) { +/******/ var i = 0; +/******/ while(i < definition.length) { +/******/ var key = definition[i++]; +/******/ var binding = definition[i++]; +/******/ if(!__webpack_require__.o(exports, key)) { +/******/ if(binding === 0) { +/******/ Object.defineProperty(exports, key, { enumerable: true, value: definition[i++] }); +/******/ } else { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: binding }); +/******/ } +/******/ } else if(binding === 0) { i++; } +/******/ } +/******/ } else { +/******/ for(var key in definition) { +/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); +/******/ } +/******/ } +/******/ } +/******/ }; +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)); +/******/ +/******/ /* webpack/runtime/make namespace object */ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = (exports) => { +/******/ if(Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/******/ /* webpack/runtime/jsonp chunk loading */ +/******/ (() => { +/******/ // no baseURI +/******/ +/******/ // object to store loaded and loading chunks +/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched +/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded +/******/ const installedChunks = { +/******/ "react-vendors": 0 +/******/ }; +/******/ +/******/ // no chunk on demand loading +/******/ +/******/ // no prefetching +/******/ +/******/ // no preloaded +/******/ +/******/ // no HMR +/******/ +/******/ // no HMR manifest +/******/ +/******/ __webpack_require__.O.j = (chunkId) => (installedChunks[chunkId] === 0); +/******/ +/******/ // install a JSONP callback for chunk loading +/******/ const webpackJsonpCallback = (parentChunkLoadingFunction, data) => { +/******/ let [chunkIds, moreModules, runtime] = data; +/******/ // add "moreModules" to the modules object, +/******/ // then flag all "chunkIds" as loaded and fire callback +/******/ var moduleId, chunkId, i = 0; +/******/ if(chunkIds.some((id) => (installedChunks[id] !== 0))) { +/******/ for(moduleId in moreModules) { +/******/ if(__webpack_require__.o(moreModules, moduleId)) { +/******/ __webpack_require__.m[moduleId] = moreModules[moduleId]; +/******/ } +/******/ } +/******/ if(runtime) var result = runtime(__webpack_require__); +/******/ } +/******/ if(parentChunkLoadingFunction) parentChunkLoadingFunction(data); +/******/ for(;i < chunkIds.length; i++) { +/******/ chunkId = chunkIds[i]; +/******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) { +/******/ installedChunks[chunkId][0](); +/******/ } +/******/ installedChunks[chunkId] = 0; +/******/ } +/******/ return __webpack_require__.O(result); +/******/ } +/******/ +/******/ const chunkLoadingGlobal = self["webpackChunk"] = self["webpackChunk"] || []; +/******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0)); +/******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal)); +/******/ })(); +/******/ +/************************************************************************/ +``` + +
+ +``` js +/******/ +/******/ // startup +/******/ // Load entry module and return exports +/******/ // This entry module is referenced by other modules so it can't be inlined +/******/ __webpack_require__(0); +/******/ __webpack_require__(1); +/******/ let __webpack_exports__ = __webpack_require__(2); +/******/ __webpack_exports__ = __webpack_require__.O(__webpack_exports__); +/******/ +/******/ })() +; +``` + +# Info + +## Unoptimized + +``` +asset react-vendors.js 8.04 KiB [emitted] (name: react-vendors) +asset app.js 1.62 KiB [emitted] (name: app) +chunk (runtime: react-vendors) app.js (app) 139 bytes <{react-vendors}> [initial] [rendered] + > ./app.js app + ./app.js 139 bytes [built] [code generated] + [no exports] + [used exports unknown] + entry ./app.js app +chunk (runtime: react-vendors) react-vendors.js (react-vendors) 87 bytes (javascript) 3.73 KiB (runtime) >{app}< [entry] [rendered] + > prop-types react-vendors + > react react-vendors + > react-dom react-vendors + runtime modules 3.73 KiB 6 modules + cacheable modules 87 bytes + ./node_modules/prop-types.js 31 bytes [built] [code generated] + [used exports unknown] + from origin ./app.js + harmony side effect evaluation prop-types ./app.js 3:0-35 + harmony import specifier prop-types ./app.js 5:29-38 + cjs self exports reference ./node_modules/prop-types.js 1:0-14 + entry prop-types react-vendors + ./node_modules/react-dom.js 30 bytes [built] [code generated] + [used exports unknown] + from origin ./app.js + harmony side effect evaluation react-dom ./app.js 2:0-33 + harmony import specifier react-dom ./app.js 5:19-27 + cjs self exports reference ./node_modules/react-dom.js 1:0-14 + entry react-dom react-vendors + ./node_modules/react.js 26 bytes [built] [code generated] + [used exports unknown] + from origin ./app.js + harmony side effect evaluation react ./app.js 1:0-26 + harmony import specifier react ./app.js 5:12-17 + cjs self exports reference ./node_modules/react.js 1:0-14 + entry react react-vendors +webpack X.X.X compiled successfully +``` + +## Production mode + +``` +asset react-vendors.js 1.4 KiB [emitted] [minimized] (name: react-vendors) +asset app.js 184 bytes [emitted] [minimized] (name: app) +chunk (runtime: react-vendors) app.js (app) 139 bytes <{react-vendors}> [initial] [rendered] + > ./app.js app + ./app.js 139 bytes [built] [code generated] + [no exports] + [no exports used] + entry ./app.js app +chunk (runtime: react-vendors) react-vendors.js (react-vendors) 87 bytes (javascript) 3.49 KiB (runtime) >{app}< [entry] [rendered] + > prop-types react-vendors + > react react-vendors + > react-dom react-vendors + runtime modules 3.49 KiB 5 modules + cacheable modules 87 bytes + ./node_modules/prop-types.js 31 bytes [built] [code generated] + [used exports unknown] + from origin ./app.js + harmony side effect evaluation prop-types ./app.js 3:0-35 + harmony import specifier prop-types ./app.js 5:29-38 + cjs self exports reference ./node_modules/prop-types.js 1:0-14 + entry prop-types react-vendors + ./node_modules/react-dom.js 30 bytes [built] [code generated] + [used exports unknown] + from origin ./app.js + harmony side effect evaluation react-dom ./app.js 2:0-33 + harmony import specifier react-dom ./app.js 5:19-27 + cjs self exports reference ./node_modules/react-dom.js 1:0-14 + entry react-dom react-vendors + ./node_modules/react.js 26 bytes [built] [code generated] + [used exports unknown] + from origin ./app.js + harmony side effect evaluation react ./app.js 1:0-26 + harmony import specifier react ./app.js 5:12-17 + cjs self exports reference ./node_modules/react.js 1:0-14 + entry react react-vendors +webpack X.X.X compiled successfully +``` diff --git a/examples/code-splitting-depend-on-simple/app.js b/examples/code-splitting-depend-on-simple/app.js new file mode 100644 index 00000000000..0249287abe5 --- /dev/null +++ b/examples/code-splitting-depend-on-simple/app.js @@ -0,0 +1,5 @@ +import react from "react"; +import reactDOM from "react-dom"; +import propTypes from "prop-types"; + +console.log(react, reactDOM, propTypes); diff --git a/examples/code-splitting-depend-on-simple/build.js b/examples/code-splitting-depend-on-simple/build.js new file mode 100644 index 00000000000..39292a5b712 --- /dev/null +++ b/examples/code-splitting-depend-on-simple/build.js @@ -0,0 +1,2 @@ +global.NO_TARGET_ARGS = true; +require("../build-common"); \ No newline at end of file diff --git a/examples/code-splitting-depend-on-simple/node_modules/prop-types.js b/examples/code-splitting-depend-on-simple/node_modules/prop-types.js new file mode 100644 index 00000000000..9c6971329ce --- /dev/null +++ b/examples/code-splitting-depend-on-simple/node_modules/prop-types.js @@ -0,0 +1 @@ +module.exports = 'prop-types'; diff --git a/examples/code-splitting-depend-on-simple/node_modules/react-dom.js b/examples/code-splitting-depend-on-simple/node_modules/react-dom.js new file mode 100644 index 00000000000..d2f4c643e28 --- /dev/null +++ b/examples/code-splitting-depend-on-simple/node_modules/react-dom.js @@ -0,0 +1 @@ +module.exports = 'react-dom'; diff --git a/examples/code-splitting-depend-on-simple/node_modules/react.js b/examples/code-splitting-depend-on-simple/node_modules/react.js new file mode 100644 index 00000000000..11c0b89c737 --- /dev/null +++ b/examples/code-splitting-depend-on-simple/node_modules/react.js @@ -0,0 +1 @@ +module.exports = 'react'; diff --git a/examples/code-splitting-depend-on-simple/template.md b/examples/code-splitting-depend-on-simple/template.md new file mode 100644 index 00000000000..c89cef9b070 --- /dev/null +++ b/examples/code-splitting-depend-on-simple/template.md @@ -0,0 +1,39 @@ +This example shows how to use Code Splitting with entrypoint dependOn + +# webpack.config.js + +```javascript +_{{webpack.config.js}}_ +``` + +# app.js + +```javascript +_{{app.js}}_ +``` + +# dist/app.js + +```javascript +_{{dist/app.js}}_ +``` + +# dist/react-vendors.js + +```javascript +_{{dist/react-vendors.js}}_ +``` + +# Info + +## Unoptimized + +``` +_{{stdout}}_ +``` + +## Production mode + +``` +_{{production:stdout}}_ +``` diff --git a/examples/code-splitting-depend-on-simple/webpack.config.js b/examples/code-splitting-depend-on-simple/webpack.config.js new file mode 100644 index 00000000000..9768ec4949e --- /dev/null +++ b/examples/code-splitting-depend-on-simple/webpack.config.js @@ -0,0 +1,18 @@ +"use strict"; + +/** @type {import("webpack").Configuration} */ +const config = { + entry: { + app: { import: "./app.js", dependOn: ["react-vendors"] }, + "react-vendors": ["react", "react-dom", "prop-types"] + }, + optimization: { + chunkIds: "named" // To keep filename consistent between different modes (for example building only) + }, + stats: { + chunks: true, + chunkRelations: true + } +}; + +module.exports = config; diff --git a/examples/code-splitting-harmony/README.md b/examples/code-splitting-harmony/README.md index c3e9e9d4c8d..fcbf8d5a615 100644 --- a/examples/code-splitting-harmony/README.md +++ b/examples/code-splitting-harmony/README.md @@ -2,13 +2,13 @@ This example show how to use Code Splitting with the ES6 module syntax. The standard `import` is sync. -`import(module: string) -> Promise` can be used to load modules on demand. This acts as split point for webpack and creates a chunk. +`import(module: string) -> Promise` can be used to load modules on demand. This acts as a split point for webpack and creates a chunk. Providing dynamic expressions to `import` is possible. The same limits as with dynamic expressions in `require` calls apply here. Each possible module creates an additional chunk. In this example `import("c/" + name)` creates two additional chunks (one for each file in `node_modules/c/`). This is called "async context". # example.js -``` javascript +```javascript import a from "a"; import("b").then(function(b) { @@ -24,271 +24,370 @@ Promise.all([loadC("1"), loadC("2")]).then(function(arr) { }); ``` - # dist/output.js -
/******/ (function(modules) { /* webpackBootstrap */ }) +```javascript +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ([ +/* 0 */, +/* 1 */ +/*!***************************!*\ + !*** ./node_modules/a.js ***! + \***************************/ +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: */ +/***/ (() => { -``` javascript -/******/ (function(modules) { // webpackBootstrap -/******/ // install a JSONP callback for chunk loading -/******/ function webpackJsonpCallback(data) { -/******/ var chunkIds = data[0]; -/******/ var moreModules = data[1]; -/******/ -/******/ // add "moreModules" to the modules object, -/******/ // then flag all "chunkIds" as loaded and fire callback -/******/ var moduleId, chunkId, i = 0, resolves = []; -/******/ for(;i < chunkIds.length; i++) { -/******/ chunkId = chunkIds[i]; -/******/ if(installedChunks[chunkId]) { -/******/ resolves.push(installedChunks[chunkId][0]); -/******/ } -/******/ installedChunks[chunkId] = 0; -/******/ } -/******/ for(moduleId in moreModules) { -/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { -/******/ modules[moduleId] = moreModules[moduleId]; -/******/ } -/******/ } -/******/ if(parentJsonpFunction) parentJsonpFunction(data); -/******/ while(resolves.length) { -/******/ resolves.shift()(); -/******/ } -/******/ -/******/ }; -/******/ -/******/ +// module a + +/***/ }), +/* 2 */ +/*!********************************************************!*\ + !*** ./node_modules/c/ lazy ^\.\/.*$ namespace object ***! + \********************************************************/ +/*! default exports */ +/*! exports [not provided] [no usage info] */ +/*! runtime requirements: __webpack_require__.e, module, __webpack_require__.o, __webpack_require__, __webpack_require__.t, __webpack_require__.* */ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +const map = { + "./1": [ + 4, + [ + 197 + ] + ], + "./1.js": [ + 4, + [ + 197 + ] + ], + "./2": [ + 5, + [ + 140 + ] + ], + "./2.js": [ + 5, + [ + 140 + ] + ] +}; +function webpackAsyncContext(req) { + try { + if(!__webpack_require__.o(map, req)) { + return Promise.resolve().then(() => { + const e = new Error("Cannot find module '" + req + "'"); + e.code = 'MODULE_NOT_FOUND'; + throw e; +}); + } + } catch(err) { + return Promise.reject(err); + } + + const ids = map[req], id = ids[0]; + return __webpack_require__.e(ids[1][0]).then(() => (__webpack_require__.t(id, 7 | 16))); +} +webpackAsyncContext.keys = () => (Object.keys(map)); +webpackAsyncContext.id = 2; +module.exports = webpackAsyncContext; + +/***/ }) +/******/ ]); +``` + +
/* webpack runtime code */ + +``` js +/************************************************************************/ /******/ // The module cache -/******/ var installedModules = {}; -/******/ -/******/ // object to store loaded and loading chunks -/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched -/******/ // Promise = chunk loading, 0 = chunk loaded -/******/ var installedChunks = { -/******/ 3: 0 -/******/ }; -/******/ -/******/ -/******/ -/******/ // script path function -/******/ function jsonpScriptSrc(chunkId) { -/******/ return __webpack_require__.p + "" + chunkId + ".output.js" -/******/ } -/******/ +/******/ const __webpack_module_cache__ = {}; +/******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { -/******/ /******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) { -/******/ return installedModules[moduleId].exports; +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, +/******/ const module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed /******/ exports: {} /******/ }; -/******/ +/******/ /******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } -/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = __webpack_modules__; +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/compat get default export */ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = (module) => { +/******/ const getter = module && module.__esModule ? +/******/ () => (module['default']) : +/******/ () => (module); +/******/ __webpack_require__.d(getter, { a: getter }); +/******/ return getter; +/******/ }; +/******/ +/******/ /* webpack/runtime/create fake namespace object */ +/******/ (() => { +/******/ const getProto = Object.getPrototypeOf ? (obj) => (Object.getPrototypeOf(obj)) : (obj) => (obj.__proto__); +/******/ let leafPrototypes; +/******/ // 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 & 16: return value when it's Promise-like +/******/ // mode & 8|1: behave like require +/******/ __webpack_require__.t = function(value, mode) { +/******/ if(mode & 1) value = this(value); +/******/ if(mode & 8) return value; +/******/ if(typeof value === 'object' && value) { +/******/ if((mode & 4) && value.__esModule) return value; +/******/ if((mode & 16) && typeof value.then === 'function') return value; +/******/ } +/******/ const ns = Object.create(null); +/******/ __webpack_require__.r(ns); +/******/ const def = {}; +/******/ leafPrototypes = leafPrototypes || [null, getProto({}), getProto([]), getProto(getProto)]; +/******/ for(var current = mode & 2 && value; (typeof current == 'object' || typeof current == 'function') && !~leafPrototypes.indexOf(current); current = getProto(current)) { +/******/ Object.getOwnPropertyNames(current).forEach((key) => (def[key] = () => (value[key]))); +/******/ } +/******/ def['default'] = () => (value); +/******/ __webpack_require__.d(ns, def); +/******/ return ns; +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/define property getters */ +/******/ // define getter/value functions for harmony exports +/******/ __webpack_require__.d = (exports, definition) => { +/******/ if(Array.isArray(definition)) { +/******/ var i = 0; +/******/ while(i < definition.length) { +/******/ var key = definition[i++]; +/******/ var binding = definition[i++]; +/******/ if(!__webpack_require__.o(exports, key)) { +/******/ if(binding === 0) { +/******/ Object.defineProperty(exports, key, { enumerable: true, value: definition[i++] }); +/******/ } else { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: binding }); +/******/ } +/******/ } else if(binding === 0) { i++; } +/******/ } +/******/ } else { +/******/ for(var key in definition) { +/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); +/******/ } +/******/ } +/******/ } +/******/ }; +/******/ +/******/ /* webpack/runtime/ensure chunk */ +/******/ __webpack_require__.f = {}; /******/ // This file contains only the entry chunk. /******/ // The chunk loading function for additional chunks -/******/ __webpack_require__.e = function requireEnsure(chunkId) { -/******/ var promises = []; -/******/ -/******/ -/******/ // JSONP chunk loading for javascript -/******/ -/******/ var installedChunkData = installedChunks[chunkId]; -/******/ if(installedChunkData !== 0) { // 0 means "already installed". -/******/ -/******/ // a Promise means "currently loading". -/******/ if(installedChunkData) { -/******/ promises.push(installedChunkData[2]); -/******/ } else { -/******/ // setup Promise in chunk cache -/******/ var promise = new Promise(function(resolve, reject) { -/******/ installedChunkData = installedChunks[chunkId] = [resolve, reject]; -/******/ }); -/******/ promises.push(installedChunkData[2] = promise); -/******/ -/******/ // start chunk loading -/******/ var head = document.getElementsByTagName('head')[0]; -/******/ var script = document.createElement('script'); -/******/ +/******/ __webpack_require__.e = (chunkId) => { +/******/ return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => { +/******/ __webpack_require__.f[key](chunkId, promises); +/******/ return promises; +/******/ }, [])); +/******/ }; +/******/ +/******/ /* webpack/runtime/get javascript chunk filename */ +/******/ // This function allow to reference async chunks +/******/ __webpack_require__.u = (chunkId) => { +/******/ // return url for filenames based on template +/******/ return "" + chunkId + ".output.js"; +/******/ }; +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)); +/******/ +/******/ /* webpack/runtime/load script */ +/******/ (() => { +/******/ const inProgress = {}; +/******/ // data-webpack is not used as build has no uniqueName +/******/ // loadScript function to load a script via script tag +/******/ __webpack_require__.l = (url, done, key, chunkId) => { +/******/ if(inProgress[url]) { inProgress[url].push(done); return; } +/******/ let script, needAttach; +/******/ if(key !== undefined) { +/******/ const scripts = document.getElementsByTagName("script"); +/******/ for(var i = 0; i < scripts.length; i++) { +/******/ const s = scripts[i]; +/******/ if(s.getAttribute("src") == url) { script = s; break; } +/******/ } +/******/ } +/******/ if(!script) { +/******/ needAttach = true; +/******/ script = document.createElement('script'); +/******/ /******/ script.charset = 'utf-8'; -/******/ script.timeout = 120; -/******/ /******/ if (__webpack_require__.nc) { /******/ script.setAttribute("nonce", __webpack_require__.nc); /******/ } -/******/ script.src = jsonpScriptSrc(chunkId); -/******/ var timeout = setTimeout(function(){ -/******/ onScriptComplete({ type: 'timeout', target: script }); -/******/ }, 120000); -/******/ script.onerror = script.onload = onScriptComplete; -/******/ function onScriptComplete(event) { -/******/ // avoid mem leaks in IE. -/******/ script.onerror = script.onload = null; -/******/ clearTimeout(timeout); -/******/ var chunk = installedChunks[chunkId]; -/******/ if(chunk !== 0) { -/******/ if(chunk) { -/******/ var errorType = event && (event.type === 'load' ? 'missing' : event.type); -/******/ var realSrc = event && event.target && event.target.src; -/******/ var error = new Error('Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'); -/******/ error.type = errorType; -/******/ error.request = realSrc; -/******/ chunk[1](error); -/******/ } -/******/ installedChunks[chunkId] = undefined; -/******/ } -/******/ }; -/******/ head.appendChild(script); +/******/ +/******/ +/******/ script.src = url; /******/ } -/******/ } -/******/ return Promise.all(promises); -/******/ }; -/******/ -/******/ // 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, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); -/******/ } -/******/ }; -/******/ +/******/ inProgress[url] = [done]; +/******/ const onScriptComplete = (prev, event) => { +/******/ // avoid mem leaks in IE. +/******/ script.onerror = script.onload = null; +/******/ clearTimeout(timeout); +/******/ const doneFns = inProgress[url]; +/******/ delete inProgress[url]; +/******/ script.parentNode?.removeChild(script); +/******/ doneFns?.forEach((fn) => (fn(event))); +/******/ if(prev) return prev(event); +/******/ } +/******/ const timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000); +/******/ script.onerror = onScriptComplete.bind(null, script.onerror); +/******/ script.onload = onScriptComplete.bind(null, script.onload); +/******/ needAttach && document.head.appendChild(script); +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/make namespace object */ /******/ // define __esModule on exports -/******/ __webpack_require__.r = function(exports) { +/******/ __webpack_require__.r = (exports) => { +/******/ if(Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } /******/ Object.defineProperty(exports, '__esModule', { value: true }); /******/ }; -/******/ -/******/ // 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/runtime/publicPath */ /******/ __webpack_require__.p = "dist/"; -/******/ -/******/ // on error function for async loading -/******/ __webpack_require__.oe = function(err) { console.error(err); throw err; }; -/******/ -/******/ var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || []; -/******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); -/******/ jsonpArray.push = webpackJsonpCallback; -/******/ jsonpArray = jsonpArray.slice(); -/******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]); -/******/ var parentJsonpFunction = oldJsonpFunction; -/******/ -/******/ -/******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 4); -/******/ }) +/******/ +/******/ /* webpack/runtime/jsonp chunk loading */ +/******/ (() => { +/******/ // no baseURI +/******/ +/******/ // object to store loaded and loading chunks +/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched +/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded +/******/ const installedChunks = { +/******/ 792: 0 +/******/ }; +/******/ +/******/ __webpack_require__.f.j = (chunkId, promises) => { +/******/ // JSONP chunk loading for javascript +/******/ let installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined; +/******/ if(installedChunkData !== 0) { // 0 means "already installed". +/******/ +/******/ // a Promise means "currently loading". +/******/ if(installedChunkData) { +/******/ promises.push(installedChunkData[2]); +/******/ } else { +/******/ if(true) { // all chunks have JS +/******/ // setup Promise in chunk cache +/******/ const promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject])); +/******/ promises.push(installedChunkData[2] = promise); +/******/ +/******/ // start chunk loading +/******/ const url = __webpack_require__.p + __webpack_require__.u(chunkId); +/******/ // create error before stack unwound to get useful stacktrace later +/******/ const error = new Error(); +/******/ const loadingEnded = (event) => { +/******/ if(__webpack_require__.o(installedChunks, chunkId)) { +/******/ installedChunkData = installedChunks[chunkId]; +/******/ if(installedChunkData !== 0) installedChunks[chunkId] = undefined; +/******/ if(installedChunkData) { +/******/ const errorType = event && (event.type === 'load' ? 'missing' : event.type); +/******/ const realSrc = event && event.target && event.target.src; +/******/ error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'; +/******/ error.name = 'ChunkLoadError'; +/******/ error.type = errorType; +/******/ error.request = realSrc; +/******/ error.event = event; +/******/ installedChunkData[1](error); +/******/ } +/******/ } +/******/ }; +/******/ __webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId); +/******/ } +/******/ } +/******/ } +/******/ }; +/******/ +/******/ // no prefetching +/******/ +/******/ // no preloaded +/******/ +/******/ // no HMR +/******/ +/******/ // no HMR manifest +/******/ +/******/ // no on chunks loaded +/******/ +/******/ // install a JSONP callback for chunk loading +/******/ const webpackJsonpCallback = (parentChunkLoadingFunction, data) => { +/******/ let [chunkIds, moreModules, runtime] = data; +/******/ // add "moreModules" to the modules object, +/******/ // then flag all "chunkIds" as loaded and fire callback +/******/ var moduleId, chunkId, i = 0; +/******/ if(chunkIds.some((id) => (installedChunks[id] !== 0))) { +/******/ for(moduleId in moreModules) { +/******/ if(__webpack_require__.o(moreModules, moduleId)) { +/******/ __webpack_require__.m[moduleId] = moreModules[moduleId]; +/******/ } +/******/ } +/******/ if(runtime) var result = runtime(__webpack_require__); +/******/ } +/******/ if(parentChunkLoadingFunction) parentChunkLoadingFunction(data); +/******/ for(;i < chunkIds.length; i++) { +/******/ chunkId = chunkIds[i]; +/******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) { +/******/ installedChunks[chunkId][0](); +/******/ } +/******/ installedChunks[chunkId] = 0; +/******/ } +/******/ +/******/ } +/******/ +/******/ const chunkLoadingGlobal = self["webpackChunk"] = self["webpackChunk"] || []; +/******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0)); +/******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal)); +/******/ })(); +/******/ /************************************************************************/ ```
-``` javascript -/******/ ([ -/* 0 */, -/* 1 */, -/* 2 */ -/*!*******************************************************!*\ - !*** ./node_modules/c lazy ^\.\/.*$ namespace object ***! - \*******************************************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -var map = { - "./1": [ - 1, - 1 - ], - "./1.js": [ - 1, - 1 - ], - "./2": [ - 0, - 0 - ], - "./2.js": [ - 0, - 0 - ] -}; -function webpackAsyncContext(req) { - var ids = map[req]; - if(!ids) { - return Promise.resolve().then(function() { - var e = new Error("Cannot find module '" + req + "'"); - e.code = 'MODULE_NOT_FOUND'; - throw e; - }); - } - return __webpack_require__.e(ids[1]).then(function() { - var module = __webpack_require__(ids[0]); - return (typeof module === "object" && module && module.__esModule ? module : Object.assign({/* fake namespace object */}, typeof module === "object" && module, { "default": module })); - }); -} -webpackAsyncContext.keys = function webpackAsyncContextKeys() { - return Object.keys(map); -}; -webpackAsyncContext.id = 2; -module.exports = webpackAsyncContext; - -/***/ }), -/* 3 */ -/*!***************************!*\ - !*** ./node_modules/a.js ***! - \***************************/ -/*! no static exports found */ -/***/ (function(module, exports) { - -// module a - -/***/ }), -/* 4 */ +``` js +let __webpack_exports__ = {}; +// This entry needs to be wrapped in an IIFE because it needs to be in strict mode. +(() => { +"use strict"; /*!********************!*\ !*** ./example.js ***! \********************/ -/*! no exports provided */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; +/*! namespace exports */ +/*! exports [not provided] [no usage info] */ +/*! runtime requirements: __webpack_require__, __webpack_require__.n, __webpack_require__.r, __webpack_exports__, __webpack_require__.e, __webpack_require__.t, __webpack_require__.* */ __webpack_require__.r(__webpack_exports__); -/* harmony import */ var a__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! a */ 3); +/* harmony import */ var a__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! a */ 1); /* harmony import */ var a__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(a__WEBPACK_IMPORTED_MODULE_0__); -__webpack_require__.e(/*! import() */ 2).then(function() { var module = __webpack_require__(/*! b */ 5); return typeof module === "object" && module && module.__esModule ? module : Object.assign({/* fake namespace object */}, typeof module === "object" && module, { "default": module }); }).then(function(b) { +__webpack_require__.e(/*! import() */ 414).then(__webpack_require__.t.bind(__webpack_require__, /*! b */ 3, 23)).then(function(b) { console.log("b loaded", b); }) @@ -300,74 +399,84 @@ Promise.all([loadC("1"), loadC("2")]).then(function(arr) { console.log("c/1 and c/2 loaded", arr); }); +})(); -/***/ }) -/******/ ]); +/******/ })() +; ``` - # Info ## Unoptimized ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 - Asset Size Chunks Chunk Names -0.output.js 275 bytes 0 [emitted] -1.output.js 284 bytes 1 [emitted] -2.output.js 270 bytes 2 [emitted] - output.js 9.11 KiB 3 [emitted] main -Entrypoint main = output.js -chunk {0} 0.output.js 13 bytes <{3}> [rendered] - > ./2 [2] ./node_modules/c lazy ^\.\/.*$ namespace object ./2 - > ./2.js [2] ./node_modules/c lazy ^\.\/.*$ namespace object ./2.js - 1 module -chunk {1} 1.output.js 13 bytes <{3}> [rendered] - > ./1 [2] ./node_modules/c lazy ^\.\/.*$ namespace object ./1 - > ./1.js [2] ./node_modules/c lazy ^\.\/.*$ namespace object ./1.js - 1 module -chunk {2} 2.output.js 11 bytes <{3}> [rendered] - > b [4] ./example.js 3:0-11 - 1 module -chunk {3} output.js (main) 427 bytes >{0}< >{1}< >{2}< [entry] [rendered] - > .\example.js main - [2] ./node_modules/c lazy ^\.\/.*$ namespace object 160 bytes {3} [built] - import() context lazy c [4] ./example.js 8:8-27 - [4] ./example.js 256 bytes {3} [built] - [no exports] - single entry .\example.js main - + 1 hidden module +asset output.js 14 KiB [emitted] (name: main) +asset 140.output.js 284 bytes [emitted] +asset 197.output.js 284 bytes [emitted] +asset 414.output.js 276 bytes [emitted] +chunk (runtime: main) 140.output.js 13 bytes [rendered] + > ./2 ./node_modules/c/ lazy ^\.\/.*$ namespace object ./2 + > ./2.js ./node_modules/c/ lazy ^\.\/.*$ namespace object ./2.js + ./node_modules/c/2.js 13 bytes [optional] [built] [code generated] + [used exports unknown] + import() context element ./2 ./node_modules/c/ lazy ^\.\/.*$ namespace object ./2 + import() context element ./2.js ./node_modules/c/ lazy ^\.\/.*$ namespace object ./2.js +chunk (runtime: main) 197.output.js 13 bytes [rendered] + > ./1 ./node_modules/c/ lazy ^\.\/.*$ namespace object ./1 + > ./1.js ./node_modules/c/ lazy ^\.\/.*$ namespace object ./1.js + ./node_modules/c/1.js 13 bytes [optional] [built] [code generated] + [used exports unknown] + import() context element ./1 ./node_modules/c/ lazy ^\.\/.*$ namespace object ./1 + import() context element ./1.js ./node_modules/c/ lazy ^\.\/.*$ namespace object ./1.js +chunk (runtime: main) 414.output.js 11 bytes [rendered] + > b ./example.js 3:0-11 + ./node_modules/b.js 11 bytes [built] [code generated] + [used exports unknown] + import() b ./example.js 3:0-11 +chunk (runtime: main) output.js (main) 414 bytes (javascript) 7.35 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 7.35 KiB 10 modules + dependent modules 171 bytes [dependent] 2 modules + ./example.js 243 bytes [built] [code generated] + [no exports] + [used exports unknown] + entry ./example.js main +webpack X.X.X compiled successfully ``` ## Production mode ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 - Asset Size Chunks Chunk Names -0.output.js 76 bytes 0 [emitted] -1.output.js 77 bytes 1 [emitted] -2.output.js 78 bytes 2 [emitted] - output.js 2.35 KiB 3 [emitted] main -Entrypoint main = output.js -chunk {0} 0.output.js 13 bytes <{3}> [rendered] - > ./2 [2] ./node_modules/c lazy ^\.\/.*$ namespace object ./2 - > ./2.js [2] ./node_modules/c lazy ^\.\/.*$ namespace object ./2.js - 1 module -chunk {1} 1.output.js 13 bytes <{3}> [rendered] - > ./1 [2] ./node_modules/c lazy ^\.\/.*$ namespace object ./1 - > ./1.js [2] ./node_modules/c lazy ^\.\/.*$ namespace object ./1.js - 1 module -chunk {2} 2.output.js 11 bytes <{3}> [rendered] - > b [4] ./example.js 3:0-11 - 1 module -chunk {3} output.js (main) 427 bytes >{0}< >{1}< >{2}< [entry] [rendered] - > .\example.js main - [2] ./node_modules/c lazy ^\.\/.*$ namespace object 160 bytes {3} [built] - import() context lazy c [4] ./example.js 8:8-27 - [4] ./example.js 256 bytes {3} [built] - [no exports] - single entry .\example.js main - + 1 hidden module +asset output.js 3.17 KiB [emitted] [minimized] (name: main) +asset 140.output.js 66 bytes [emitted] [minimized] +asset 197.output.js 66 bytes [emitted] [minimized] +asset 414.output.js 66 bytes [emitted] [minimized] +chunk (runtime: main) 140.output.js 13 bytes [rendered] + > ./2 ./node_modules/c/ lazy ^\.\/.*$ namespace object ./2 + > ./2.js ./node_modules/c/ lazy ^\.\/.*$ namespace object ./2.js + ./node_modules/c/2.js 13 bytes [optional] [built] [code generated] + [used exports unknown] + import() context element ./2 ./node_modules/c/ lazy ^\.\/.*$ namespace object ./2 + import() context element ./2.js ./node_modules/c/ lazy ^\.\/.*$ namespace object ./2.js +chunk (runtime: main) 197.output.js 13 bytes [rendered] + > ./1 ./node_modules/c/ lazy ^\.\/.*$ namespace object ./1 + > ./1.js ./node_modules/c/ lazy ^\.\/.*$ namespace object ./1.js + ./node_modules/c/1.js 13 bytes [optional] [built] [code generated] + [used exports unknown] + import() context element ./1 ./node_modules/c/ lazy ^\.\/.*$ namespace object ./1 + import() context element ./1.js ./node_modules/c/ lazy ^\.\/.*$ namespace object ./1.js +chunk (runtime: main) 414.output.js 11 bytes [rendered] + > b ./example.js 3:0-11 + ./node_modules/b.js 11 bytes [built] [code generated] + [used exports unknown] + import() b ./example.js 3:0-11 +chunk (runtime: main) output.js (main) 403 bytes (javascript) 7.08 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 7.08 KiB 9 modules + dependent modules 160 bytes [dependent] 1 module + ./example.js 243 bytes [built] [code generated] + [no exports] + [no exports used] + entry ./example.js main +webpack X.X.X compiled successfully ``` diff --git a/examples/code-splitting-harmony/template.md b/examples/code-splitting-harmony/template.md index 5f6a68cdf3b..3f47ddca765 100644 --- a/examples/code-splitting-harmony/template.md +++ b/examples/code-splitting-harmony/template.md @@ -2,34 +2,32 @@ This example show how to use Code Splitting with the ES6 module syntax. The standard `import` is sync. -`import(module: string) -> Promise` can be used to load modules on demand. This acts as split point for webpack and creates a chunk. +`import(module: string) -> Promise` can be used to load modules on demand. This acts as a split point for webpack and creates a chunk. Providing dynamic expressions to `import` is possible. The same limits as with dynamic expressions in `require` calls apply here. Each possible module creates an additional chunk. In this example `import("c/" + name)` creates two additional chunks (one for each file in `node_modules/c/`). This is called "async context". # example.js -``` javascript -{{example.js}} +```javascript +_{{example.js}}_ ``` - # dist/output.js -``` javascript -{{dist/output.js}} +```javascript +_{{dist/output.js}}_ ``` - # Info ## Unoptimized ``` -{{stdout}} +_{{stdout}}_ ``` ## Production mode ``` -{{production:stdout}} +_{{production:stdout}}_ ``` diff --git a/examples/code-splitting-harmony/webpack.config.js b/examples/code-splitting-harmony/webpack.config.js index 0d554bf62ea..6685d0c375d 100644 --- a/examples/code-splitting-harmony/webpack.config.js +++ b/examples/code-splitting-harmony/webpack.config.js @@ -1,5 +1,10 @@ -module.exports = { +"use strict"; + +/** @type {import("webpack").Configuration} */ +const config = { optimization: { - occurrenceOrder: true // To keep filename consistent between different modes (for example building only) + chunkIds: "deterministic" // To keep filename consistent between different modes (for example building only) } }; + +module.exports = config; diff --git a/examples/code-splitting-native-import-context-filter/README.md b/examples/code-splitting-native-import-context-filter/README.md index 86f2a9e9164..9e42d3838c7 100644 --- a/examples/code-splitting-native-import-context-filter/README.md +++ b/examples/code-splitting-native-import-context-filter/README.md @@ -1,9 +1,9 @@ # example.js -This example illustrates how to filter the ContextModule results of `import()` statements. only `.js` files that don't +This example illustrates how to filter the ContextModule results of `import()` statements. Only `.js` files that don't end in `.noimport.js` within the `templates` folder will be bundled. -``` javascript +```javascript async function getTemplate(templateName) { try { let template = await import( @@ -28,16 +28,16 @@ getTemplate("baz.noimport"); # templates/ -* foo.js -* foo.noimport.js -* baz.js -* foo.noimport.js -* bar.js -* foo.noimport.js +- foo.js +- foo.noimport.js +- baz.js +- foo.noimport.js +- bar.js +- foo.noimport.js All templates are of this pattern: -``` javascript +```javascript var foo = "foo"; export default foo; @@ -45,263 +45,324 @@ export default foo; # dist/output.js -
/******/ (function(modules) { /* webpackBootstrap */ }) +```javascript +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ([ +/* 0 */, +/* 1 */ +/*!***************************************************************************************************************!*\ + !*** ./templates/ lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ referencedExports: namespace object ***! + \***************************************************************************************************************/ +/*! default exports */ +/*! exports [not provided] [no usage info] */ +/*! runtime requirements: __webpack_require__.e, module, __webpack_require__.o, __webpack_require__, __webpack_require__.* */ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { -``` javascript -/******/ (function(modules) { // webpackBootstrap -/******/ // install a JSONP callback for chunk loading -/******/ function webpackJsonpCallback(data) { -/******/ var chunkIds = data[0]; -/******/ var moreModules = data[1]; -/******/ -/******/ // add "moreModules" to the modules object, -/******/ // then flag all "chunkIds" as loaded and fire callback -/******/ var moduleId, chunkId, i = 0, resolves = []; -/******/ for(;i < chunkIds.length; i++) { -/******/ chunkId = chunkIds[i]; -/******/ if(installedChunks[chunkId]) { -/******/ resolves.push(installedChunks[chunkId][0]); -/******/ } -/******/ installedChunks[chunkId] = 0; -/******/ } -/******/ for(moduleId in moreModules) { -/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { -/******/ modules[moduleId] = moreModules[moduleId]; -/******/ } -/******/ } -/******/ if(parentJsonpFunction) parentJsonpFunction(data); -/******/ while(resolves.length) { -/******/ resolves.shift()(); -/******/ } -/******/ -/******/ }; -/******/ -/******/ +const map = { + "./bar": [ + 2, + [ + 776 + ] + ], + "./bar.js": [ + 2, + [ + 776 + ] + ], + "./baz": [ + 3, + [ + 0 + ] + ], + "./baz.js": [ + 3, + [ + 0 + ] + ], + "./foo": [ + 4, + [ + 717 + ] + ], + "./foo.js": [ + 4, + [ + 717 + ] + ] +}; +function webpackAsyncContext(req) { + try { + if(!__webpack_require__.o(map, req)) { + return Promise.resolve().then(() => { + const e = new Error("Cannot find module '" + req + "'"); + e.code = 'MODULE_NOT_FOUND'; + throw e; +}); + } + } catch(err) { + return Promise.reject(err); + } + + const ids = map[req], id = ids[0]; + return __webpack_require__.e(ids[1][0]).then(() => (__webpack_require__(id))); +} +webpackAsyncContext.keys = () => (Object.keys(map)); +webpackAsyncContext.id = 1; +module.exports = webpackAsyncContext; + +/***/ }) +/******/ ]); +``` + +
/* webpack runtime code */ + +``` js +/************************************************************************/ /******/ // The module cache -/******/ var installedModules = {}; -/******/ -/******/ // object to store loaded and loading chunks -/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched -/******/ // Promise = chunk loading, 0 = chunk loaded -/******/ var installedChunks = { -/******/ 3: 0 -/******/ }; -/******/ -/******/ -/******/ -/******/ // script path function -/******/ function jsonpScriptSrc(chunkId) { -/******/ return __webpack_require__.p + "" + chunkId + ".output.js" -/******/ } -/******/ +/******/ const __webpack_module_cache__ = {}; +/******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { -/******/ /******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) { -/******/ return installedModules[moduleId].exports; +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, +/******/ const module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed /******/ exports: {} /******/ }; -/******/ +/******/ /******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } -/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = __webpack_modules__; +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/define property getters */ +/******/ // define getter/value functions for harmony exports +/******/ __webpack_require__.d = (exports, definition) => { +/******/ if(Array.isArray(definition)) { +/******/ var i = 0; +/******/ while(i < definition.length) { +/******/ var key = definition[i++]; +/******/ var binding = definition[i++]; +/******/ if(!__webpack_require__.o(exports, key)) { +/******/ if(binding === 0) { +/******/ Object.defineProperty(exports, key, { enumerable: true, value: definition[i++] }); +/******/ } else { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: binding }); +/******/ } +/******/ } else if(binding === 0) { i++; } +/******/ } +/******/ } else { +/******/ for(var key in definition) { +/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); +/******/ } +/******/ } +/******/ } +/******/ }; +/******/ +/******/ /* webpack/runtime/ensure chunk */ +/******/ __webpack_require__.f = {}; /******/ // This file contains only the entry chunk. /******/ // The chunk loading function for additional chunks -/******/ __webpack_require__.e = function requireEnsure(chunkId) { -/******/ var promises = []; -/******/ -/******/ -/******/ // JSONP chunk loading for javascript -/******/ -/******/ var installedChunkData = installedChunks[chunkId]; -/******/ if(installedChunkData !== 0) { // 0 means "already installed". -/******/ -/******/ // a Promise means "currently loading". -/******/ if(installedChunkData) { -/******/ promises.push(installedChunkData[2]); -/******/ } else { -/******/ // setup Promise in chunk cache -/******/ var promise = new Promise(function(resolve, reject) { -/******/ installedChunkData = installedChunks[chunkId] = [resolve, reject]; -/******/ }); -/******/ promises.push(installedChunkData[2] = promise); -/******/ -/******/ // start chunk loading -/******/ var head = document.getElementsByTagName('head')[0]; -/******/ var script = document.createElement('script'); -/******/ +/******/ __webpack_require__.e = (chunkId) => { +/******/ return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => { +/******/ __webpack_require__.f[key](chunkId, promises); +/******/ return promises; +/******/ }, [])); +/******/ }; +/******/ +/******/ /* webpack/runtime/get javascript chunk filename */ +/******/ // This function allow to reference async chunks +/******/ __webpack_require__.u = (chunkId) => { +/******/ // return url for filenames based on template +/******/ return "" + chunkId + ".output.js"; +/******/ }; +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)); +/******/ +/******/ /* webpack/runtime/load script */ +/******/ (() => { +/******/ const inProgress = {}; +/******/ // data-webpack is not used as build has no uniqueName +/******/ // loadScript function to load a script via script tag +/******/ __webpack_require__.l = (url, done, key, chunkId) => { +/******/ if(inProgress[url]) { inProgress[url].push(done); return; } +/******/ let script, needAttach; +/******/ if(key !== undefined) { +/******/ const scripts = document.getElementsByTagName("script"); +/******/ for(var i = 0; i < scripts.length; i++) { +/******/ const s = scripts[i]; +/******/ if(s.getAttribute("src") == url) { script = s; break; } +/******/ } +/******/ } +/******/ if(!script) { +/******/ needAttach = true; +/******/ script = document.createElement('script'); +/******/ /******/ script.charset = 'utf-8'; -/******/ script.timeout = 120; -/******/ /******/ if (__webpack_require__.nc) { /******/ script.setAttribute("nonce", __webpack_require__.nc); /******/ } -/******/ script.src = jsonpScriptSrc(chunkId); -/******/ var timeout = setTimeout(function(){ -/******/ onScriptComplete({ type: 'timeout', target: script }); -/******/ }, 120000); -/******/ script.onerror = script.onload = onScriptComplete; -/******/ function onScriptComplete(event) { -/******/ // avoid mem leaks in IE. -/******/ script.onerror = script.onload = null; -/******/ clearTimeout(timeout); -/******/ var chunk = installedChunks[chunkId]; -/******/ if(chunk !== 0) { -/******/ if(chunk) { -/******/ var errorType = event && (event.type === 'load' ? 'missing' : event.type); -/******/ var realSrc = event && event.target && event.target.src; -/******/ var error = new Error('Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'); -/******/ error.type = errorType; -/******/ error.request = realSrc; -/******/ chunk[1](error); -/******/ } -/******/ installedChunks[chunkId] = undefined; -/******/ } -/******/ }; -/******/ head.appendChild(script); +/******/ +/******/ +/******/ script.src = url; /******/ } -/******/ } -/******/ return Promise.all(promises); -/******/ }; -/******/ -/******/ // 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, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); -/******/ } -/******/ }; -/******/ +/******/ inProgress[url] = [done]; +/******/ const onScriptComplete = (prev, event) => { +/******/ // avoid mem leaks in IE. +/******/ script.onerror = script.onload = null; +/******/ clearTimeout(timeout); +/******/ const doneFns = inProgress[url]; +/******/ delete inProgress[url]; +/******/ script.parentNode?.removeChild(script); +/******/ doneFns?.forEach((fn) => (fn(event))); +/******/ if(prev) return prev(event); +/******/ } +/******/ const timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000); +/******/ script.onerror = onScriptComplete.bind(null, script.onerror); +/******/ script.onload = onScriptComplete.bind(null, script.onload); +/******/ needAttach && document.head.appendChild(script); +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/make namespace object */ /******/ // define __esModule on exports -/******/ __webpack_require__.r = function(exports) { +/******/ __webpack_require__.r = (exports) => { +/******/ if(Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } /******/ Object.defineProperty(exports, '__esModule', { value: true }); /******/ }; -/******/ -/******/ // 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/runtime/publicPath */ /******/ __webpack_require__.p = "dist/"; -/******/ -/******/ // on error function for async loading -/******/ __webpack_require__.oe = function(err) { console.error(err); throw err; }; -/******/ -/******/ var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || []; -/******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); -/******/ jsonpArray.push = webpackJsonpCallback; -/******/ jsonpArray = jsonpArray.slice(); -/******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]); -/******/ var parentJsonpFunction = oldJsonpFunction; -/******/ -/******/ -/******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 4); -/******/ }) +/******/ +/******/ /* webpack/runtime/jsonp chunk loading */ +/******/ (() => { +/******/ // no baseURI +/******/ +/******/ // object to store loaded and loading chunks +/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched +/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded +/******/ const installedChunks = { +/******/ 792: 0 +/******/ }; +/******/ +/******/ __webpack_require__.f.j = (chunkId, promises) => { +/******/ // JSONP chunk loading for javascript +/******/ let installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined; +/******/ if(installedChunkData !== 0) { // 0 means "already installed". +/******/ +/******/ // a Promise means "currently loading". +/******/ if(installedChunkData) { +/******/ promises.push(installedChunkData[2]); +/******/ } else { +/******/ if(true) { // all chunks have JS +/******/ // setup Promise in chunk cache +/******/ const promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject])); +/******/ promises.push(installedChunkData[2] = promise); +/******/ +/******/ // start chunk loading +/******/ const url = __webpack_require__.p + __webpack_require__.u(chunkId); +/******/ // create error before stack unwound to get useful stacktrace later +/******/ const error = new Error(); +/******/ const loadingEnded = (event) => { +/******/ if(__webpack_require__.o(installedChunks, chunkId)) { +/******/ installedChunkData = installedChunks[chunkId]; +/******/ if(installedChunkData !== 0) installedChunks[chunkId] = undefined; +/******/ if(installedChunkData) { +/******/ const errorType = event && (event.type === 'load' ? 'missing' : event.type); +/******/ const realSrc = event && event.target && event.target.src; +/******/ error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'; +/******/ error.name = 'ChunkLoadError'; +/******/ error.type = errorType; +/******/ error.request = realSrc; +/******/ error.event = event; +/******/ installedChunkData[1](error); +/******/ } +/******/ } +/******/ }; +/******/ __webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId); +/******/ } +/******/ } +/******/ } +/******/ }; +/******/ +/******/ // no prefetching +/******/ +/******/ // no preloaded +/******/ +/******/ // no HMR +/******/ +/******/ // no HMR manifest +/******/ +/******/ // no on chunks loaded +/******/ +/******/ // install a JSONP callback for chunk loading +/******/ const webpackJsonpCallback = (parentChunkLoadingFunction, data) => { +/******/ let [chunkIds, moreModules, runtime] = data; +/******/ // add "moreModules" to the modules object, +/******/ // then flag all "chunkIds" as loaded and fire callback +/******/ var moduleId, chunkId, i = 0; +/******/ if(chunkIds.some((id) => (installedChunks[id] !== 0))) { +/******/ for(moduleId in moreModules) { +/******/ if(__webpack_require__.o(moreModules, moduleId)) { +/******/ __webpack_require__.m[moduleId] = moreModules[moduleId]; +/******/ } +/******/ } +/******/ if(runtime) var result = runtime(__webpack_require__); +/******/ } +/******/ if(parentChunkLoadingFunction) parentChunkLoadingFunction(data); +/******/ for(;i < chunkIds.length; i++) { +/******/ chunkId = chunkIds[i]; +/******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) { +/******/ installedChunks[chunkId][0](); +/******/ } +/******/ installedChunks[chunkId] = 0; +/******/ } +/******/ +/******/ } +/******/ +/******/ const chunkLoadingGlobal = self["webpackChunk"] = self["webpackChunk"] || []; +/******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0)); +/******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal)); +/******/ })(); +/******/ /************************************************************************/ ```
-``` javascript -/******/ ([ -/* 0 */, -/* 1 */, -/* 2 */, -/* 3 */ -/*!******************************************************************************************!*\ - !*** ./templates lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ namespace object ***! - \******************************************************************************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -var map = { - "./bar": [ - 2, - 2 - ], - "./bar.js": [ - 2, - 2 - ], - "./baz": [ - 1, - 1 - ], - "./baz.js": [ - 1, - 1 - ], - "./foo": [ - 0, - 0 - ], - "./foo.js": [ - 0, - 0 - ] -}; -function webpackAsyncContext(req) { - var ids = map[req]; - if(!ids) { - return Promise.resolve().then(function() { - var e = new Error("Cannot find module '" + req + "'"); - e.code = 'MODULE_NOT_FOUND'; - throw e; - }); - } - return __webpack_require__.e(ids[1]).then(function() { - var module = __webpack_require__(ids[0]); - return module; - }); -} -webpackAsyncContext.keys = function webpackAsyncContextKeys() { - return Object.keys(map); -}; -webpackAsyncContext.id = 3; -module.exports = webpackAsyncContext; - -/***/ }), -/* 4 */ +``` js +let __webpack_exports__ = {}; +// This entry needs to be wrapped in an IIFE because it needs to be isolated against other modules in the chunk. +(() => { /*!********************!*\ !*** ./example.js ***! \********************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: __webpack_require__ */ async function getTemplate(templateName) { try { - let template = await __webpack_require__(3)(`./${templateName}`); + let template = await __webpack_require__(1)(`./${templateName}`); console.log(template); } catch(err) { console.error(err); @@ -317,9 +378,10 @@ getTemplate("bar.noimport"); getTemplate("baz.noimport"); +})(); -/***/ }) -/******/ ]); +/******/ })() +; ``` # Info @@ -327,79 +389,78 @@ getTemplate("baz.noimport"); ## Unoptimized ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 - Asset Size Chunks Chunk Names -0.output.js 433 bytes 0 [emitted] -1.output.js 442 bytes 1 [emitted] -2.output.js 436 bytes 2 [emitted] - output.js 8.47 KiB 3 [emitted] main -Entrypoint main = output.js -chunk {0} 0.output.js 38 bytes <{3}> [rendered] - > ./foo [3] ./templates lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ namespace object ./foo - > ./foo.js [3] ./templates lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ namespace object ./foo.js - [0] ./templates/foo.js 38 bytes {0} [optional] [built] - [exports: default] - context element ./foo.js [3] ./templates lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ namespace object ./foo.js - context element ./foo [3] ./templates lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ namespace object ./foo -chunk {1} 1.output.js 38 bytes <{3}> [rendered] - > ./baz [3] ./templates lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ namespace object ./baz - > ./baz.js [3] ./templates lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ namespace object ./baz.js - [1] ./templates/baz.js 38 bytes {1} [optional] [built] - [exports: default] - context element ./baz.js [3] ./templates lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ namespace object ./baz.js - context element ./baz [3] ./templates lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ namespace object ./baz -chunk {2} 2.output.js 38 bytes <{3}> [rendered] - > ./bar [3] ./templates lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ namespace object ./bar - > ./bar.js [3] ./templates lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ namespace object ./bar.js - [2] ./templates/bar.js 38 bytes {2} [optional] [built] - [exports: default] - context element ./bar.js [3] ./templates lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ namespace object ./bar.js - context element ./bar [3] ./templates lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ namespace object ./bar -chunk {3} output.js (main) 597 bytes >{0}< >{1}< >{2}< [entry] [rendered] - > .\example.js main - [3] ./templates lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ namespace object 160 bytes {3} [optional] [built] - import() context lazy ./templates [4] ./example.js 3:23-7:3 - [4] ./example.js 437 bytes {3} [built] - single entry .\example.js main +asset output.js 11.7 KiB [emitted] (name: main) +asset 717.output.js 846 bytes [emitted] +asset 776.output.js 846 bytes [emitted] +asset 0.output.js 844 bytes [emitted] +chunk (runtime: main) 0.output.js 38 bytes [rendered] + > ./baz ./templates/ lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ referencedExports: namespace object ./baz + > ./baz.js ./templates/ lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ referencedExports: namespace object ./baz.js + ./templates/baz.js 38 bytes [optional] [built] [code generated] + [exports: default] + [used exports unknown] + import() context element ./baz ./templates/ lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ referencedExports: namespace object ./baz + import() context element ./baz.js ./templates/ lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ referencedExports: namespace object ./baz.js +chunk (runtime: main) 717.output.js 38 bytes [rendered] + > ./foo ./templates/ lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ referencedExports: namespace object ./foo + > ./foo.js ./templates/ lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ referencedExports: namespace object ./foo.js + ./templates/foo.js 38 bytes [optional] [built] [code generated] + [exports: default] + [used exports unknown] + import() context element ./foo ./templates/ lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ referencedExports: namespace object ./foo + import() context element ./foo.js ./templates/ lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ referencedExports: namespace object ./foo.js +chunk (runtime: main) 776.output.js 38 bytes [rendered] + > ./bar ./templates/ lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ referencedExports: namespace object ./bar + > ./bar.js ./templates/ lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ referencedExports: namespace object ./bar.js + ./templates/bar.js 38 bytes [optional] [built] [code generated] + [exports: default] + [used exports unknown] + import() context element ./bar ./templates/ lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ referencedExports: namespace object ./bar + import() context element ./bar.js ./templates/ lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ referencedExports: namespace object ./bar.js +chunk (runtime: main) output.js (main) 597 bytes (javascript) 5.93 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 5.93 KiB 8 modules + dependent modules 160 bytes [dependent] 1 module + ./example.js 437 bytes [built] [code generated] + [used exports unknown] + entry ./example.js main +webpack X.X.X compiled successfully ``` ## Production mode ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 - Asset Size Chunks Chunk Names -0.output.js 113 bytes 0 [emitted] -1.output.js 114 bytes 1 [emitted] -2.output.js 115 bytes 2 [emitted] - output.js 2.16 KiB 3 [emitted] main -Entrypoint main = output.js -chunk {0} 0.output.js 38 bytes <{3}> [rendered] - > ./foo [3] ./templates lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ namespace object ./foo - > ./foo.js [3] ./templates lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ namespace object ./foo.js - [0] ./templates/foo.js 38 bytes {0} [optional] [built] - [exports: default] - context element ./foo.js [3] ./templates lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ namespace object ./foo.js - context element ./foo [3] ./templates lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ namespace object ./foo -chunk {1} 1.output.js 38 bytes <{3}> [rendered] - > ./baz [3] ./templates lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ namespace object ./baz - > ./baz.js [3] ./templates lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ namespace object ./baz.js - [1] ./templates/baz.js 38 bytes {1} [optional] [built] - [exports: default] - context element ./baz.js [3] ./templates lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ namespace object ./baz.js - context element ./baz [3] ./templates lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ namespace object ./baz -chunk {2} 2.output.js 38 bytes <{3}> [rendered] - > ./bar [3] ./templates lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ namespace object ./bar - > ./bar.js [3] ./templates lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ namespace object ./bar.js - [2] ./templates/bar.js 38 bytes {2} [optional] [built] - [exports: default] - context element ./bar.js [3] ./templates lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ namespace object ./bar.js - context element ./bar [3] ./templates lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ namespace object ./bar -chunk {3} output.js (main) 597 bytes >{0}< >{1}< >{2}< [entry] [rendered] - > .\example.js main - [3] ./templates lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ namespace object 160 bytes {3} [optional] [built] - import() context lazy ./templates [4] ./example.js 3:23-7:3 - [4] ./example.js 437 bytes {3} [built] - single entry .\example.js main +asset output.js 2.71 KiB [emitted] [minimized] (name: main) +asset 717.output.js 117 bytes [emitted] [minimized] +asset 776.output.js 117 bytes [emitted] [minimized] +asset 0.output.js 114 bytes [emitted] [minimized] +chunk (runtime: main) 0.output.js 38 bytes [rendered] + > ./baz ./templates/ lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ referencedExports: namespace object ./baz + > ./baz.js ./templates/ lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ referencedExports: namespace object ./baz.js + ./templates/baz.js 38 bytes [optional] [built] [code generated] + [exports: default] + import() context element ./baz ./templates/ lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ referencedExports: namespace object ./baz + import() context element ./baz.js ./templates/ lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ referencedExports: namespace object ./baz.js +chunk (runtime: main) 717.output.js 38 bytes [rendered] + > ./foo ./templates/ lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ referencedExports: namespace object ./foo + > ./foo.js ./templates/ lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ referencedExports: namespace object ./foo.js + ./templates/foo.js 38 bytes [optional] [built] [code generated] + [exports: default] + import() context element ./foo ./templates/ lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ referencedExports: namespace object ./foo + import() context element ./foo.js ./templates/ lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ referencedExports: namespace object ./foo.js +chunk (runtime: main) 776.output.js 38 bytes [rendered] + > ./bar ./templates/ lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ referencedExports: namespace object ./bar + > ./bar.js ./templates/ lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ referencedExports: namespace object ./bar.js + ./templates/bar.js 38 bytes [optional] [built] [code generated] + [exports: default] + import() context element ./bar ./templates/ lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ referencedExports: namespace object ./bar + import() context element ./bar.js ./templates/ lazy ^\.\/.*$ include: \.js$ exclude: \.noimport\.js$ referencedExports: namespace object ./bar.js +chunk (runtime: main) output.js (main) 597 bytes (javascript) 5.93 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 5.93 KiB 8 modules + dependent modules 160 bytes [dependent] 1 module + ./example.js 437 bytes [built] [code generated] + [no exports used] + entry ./example.js main +webpack X.X.X compiled successfully ``` diff --git a/examples/code-splitting-native-import-context-filter/template.md b/examples/code-splitting-native-import-context-filter/template.md index 31fa2aeb04d..a4dd169ea27 100644 --- a/examples/code-splitting-native-import-context-filter/template.md +++ b/examples/code-splitting-native-import-context-filter/template.md @@ -1,31 +1,31 @@ # example.js -This example illustrates how to filter the ContextModule results of `import()` statements. only `.js` files that don't +This example illustrates how to filter the ContextModule results of `import()` statements. Only `.js` files that don't end in `.noimport.js` within the `templates` folder will be bundled. -``` javascript -{{example.js}} +```javascript +_{{example.js}}_ ``` # templates/ -* foo.js -* foo.noimport.js -* baz.js -* foo.noimport.js -* bar.js -* foo.noimport.js +- foo.js +- foo.noimport.js +- baz.js +- foo.noimport.js +- bar.js +- foo.noimport.js All templates are of this pattern: -``` javascript -{{templates/foo.js}} +```javascript +_{{templates/foo.js}}_ ``` # dist/output.js -``` javascript -{{dist/output.js}} +```javascript +_{{dist/output.js}}_ ``` # Info @@ -33,11 +33,11 @@ All templates are of this pattern: ## Unoptimized ``` -{{stdout}} +_{{stdout}}_ ``` ## Production mode ``` -{{production:stdout}} +_{{production:stdout}}_ ``` diff --git a/examples/code-splitting-native-import-context-filter/webpack.config.js b/examples/code-splitting-native-import-context-filter/webpack.config.js index 0d554bf62ea..6685d0c375d 100644 --- a/examples/code-splitting-native-import-context-filter/webpack.config.js +++ b/examples/code-splitting-native-import-context-filter/webpack.config.js @@ -1,5 +1,10 @@ -module.exports = { +"use strict"; + +/** @type {import("webpack").Configuration} */ +const config = { optimization: { - occurrenceOrder: true // To keep filename consistent between different modes (for example building only) + chunkIds: "deterministic" // To keep filename consistent between different modes (for example building only) } }; + +module.exports = config; diff --git a/examples/code-splitting-native-import-context/README.md b/examples/code-splitting-native-import-context/README.md index f751eb9b227..5925f739536 100644 --- a/examples/code-splitting-native-import-context/README.md +++ b/examples/code-splitting-native-import-context/README.md @@ -2,7 +2,7 @@ This example illustrates how to leverage the `import()` syntax to create ContextModules which are separated into separate chunks for each module in the `./templates` folder. -``` javascript +```javascript async function getTemplate(templateName) { try { let template = await import(`./templates/${templateName}`); @@ -20,13 +20,13 @@ getTemplate("baz"); # templates/ -* foo.js -* baz.js -* bar.js +- foo.js +- baz.js +- bar.js All templates are of this pattern: -``` javascript +```javascript var foo = "foo"; export default foo; @@ -34,263 +34,324 @@ export default foo; # dist/output.js -
/******/ (function(modules) { /* webpackBootstrap */ }) +```javascript +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ([ +/* 0 */, +/* 1 */ +/*!***********************************************************************!*\ + !*** ./templates/ lazy ^\.\/.*$ referencedExports: namespace object ***! + \***********************************************************************/ +/*! default exports */ +/*! exports [not provided] [no usage info] */ +/*! runtime requirements: __webpack_require__.e, module, __webpack_require__.o, __webpack_require__, __webpack_require__.* */ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { -``` javascript -/******/ (function(modules) { // webpackBootstrap -/******/ // install a JSONP callback for chunk loading -/******/ function webpackJsonpCallback(data) { -/******/ var chunkIds = data[0]; -/******/ var moreModules = data[1]; -/******/ -/******/ // add "moreModules" to the modules object, -/******/ // then flag all "chunkIds" as loaded and fire callback -/******/ var moduleId, chunkId, i = 0, resolves = []; -/******/ for(;i < chunkIds.length; i++) { -/******/ chunkId = chunkIds[i]; -/******/ if(installedChunks[chunkId]) { -/******/ resolves.push(installedChunks[chunkId][0]); -/******/ } -/******/ installedChunks[chunkId] = 0; -/******/ } -/******/ for(moduleId in moreModules) { -/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { -/******/ modules[moduleId] = moreModules[moduleId]; -/******/ } -/******/ } -/******/ if(parentJsonpFunction) parentJsonpFunction(data); -/******/ while(resolves.length) { -/******/ resolves.shift()(); -/******/ } -/******/ -/******/ }; -/******/ -/******/ +const map = { + "./bar": [ + 2, + [ + 776 + ] + ], + "./bar.js": [ + 2, + [ + 776 + ] + ], + "./baz": [ + 3, + [ + 0 + ] + ], + "./baz.js": [ + 3, + [ + 0 + ] + ], + "./foo": [ + 4, + [ + 717 + ] + ], + "./foo.js": [ + 4, + [ + 717 + ] + ] +}; +function webpackAsyncContext(req) { + try { + if(!__webpack_require__.o(map, req)) { + return Promise.resolve().then(() => { + const e = new Error("Cannot find module '" + req + "'"); + e.code = 'MODULE_NOT_FOUND'; + throw e; +}); + } + } catch(err) { + return Promise.reject(err); + } + + const ids = map[req], id = ids[0]; + return __webpack_require__.e(ids[1][0]).then(() => (__webpack_require__(id))); +} +webpackAsyncContext.keys = () => (Object.keys(map)); +webpackAsyncContext.id = 1; +module.exports = webpackAsyncContext; + +/***/ }) +/******/ ]); +``` + +
/* webpack runtime code */ + +``` js +/************************************************************************/ /******/ // The module cache -/******/ var installedModules = {}; -/******/ -/******/ // object to store loaded and loading chunks -/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched -/******/ // Promise = chunk loading, 0 = chunk loaded -/******/ var installedChunks = { -/******/ 3: 0 -/******/ }; -/******/ -/******/ -/******/ -/******/ // script path function -/******/ function jsonpScriptSrc(chunkId) { -/******/ return __webpack_require__.p + "" + chunkId + ".output.js" -/******/ } -/******/ +/******/ const __webpack_module_cache__ = {}; +/******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { -/******/ /******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) { -/******/ return installedModules[moduleId].exports; +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, +/******/ const module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed /******/ exports: {} /******/ }; -/******/ +/******/ /******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } -/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = __webpack_modules__; +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/define property getters */ +/******/ // define getter/value functions for harmony exports +/******/ __webpack_require__.d = (exports, definition) => { +/******/ if(Array.isArray(definition)) { +/******/ var i = 0; +/******/ while(i < definition.length) { +/******/ var key = definition[i++]; +/******/ var binding = definition[i++]; +/******/ if(!__webpack_require__.o(exports, key)) { +/******/ if(binding === 0) { +/******/ Object.defineProperty(exports, key, { enumerable: true, value: definition[i++] }); +/******/ } else { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: binding }); +/******/ } +/******/ } else if(binding === 0) { i++; } +/******/ } +/******/ } else { +/******/ for(var key in definition) { +/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); +/******/ } +/******/ } +/******/ } +/******/ }; +/******/ +/******/ /* webpack/runtime/ensure chunk */ +/******/ __webpack_require__.f = {}; /******/ // This file contains only the entry chunk. /******/ // The chunk loading function for additional chunks -/******/ __webpack_require__.e = function requireEnsure(chunkId) { -/******/ var promises = []; -/******/ -/******/ -/******/ // JSONP chunk loading for javascript -/******/ -/******/ var installedChunkData = installedChunks[chunkId]; -/******/ if(installedChunkData !== 0) { // 0 means "already installed". -/******/ -/******/ // a Promise means "currently loading". -/******/ if(installedChunkData) { -/******/ promises.push(installedChunkData[2]); -/******/ } else { -/******/ // setup Promise in chunk cache -/******/ var promise = new Promise(function(resolve, reject) { -/******/ installedChunkData = installedChunks[chunkId] = [resolve, reject]; -/******/ }); -/******/ promises.push(installedChunkData[2] = promise); -/******/ -/******/ // start chunk loading -/******/ var head = document.getElementsByTagName('head')[0]; -/******/ var script = document.createElement('script'); -/******/ +/******/ __webpack_require__.e = (chunkId) => { +/******/ return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => { +/******/ __webpack_require__.f[key](chunkId, promises); +/******/ return promises; +/******/ }, [])); +/******/ }; +/******/ +/******/ /* webpack/runtime/get javascript chunk filename */ +/******/ // This function allow to reference async chunks +/******/ __webpack_require__.u = (chunkId) => { +/******/ // return url for filenames based on template +/******/ return "" + chunkId + ".output.js"; +/******/ }; +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)); +/******/ +/******/ /* webpack/runtime/load script */ +/******/ (() => { +/******/ const inProgress = {}; +/******/ // data-webpack is not used as build has no uniqueName +/******/ // loadScript function to load a script via script tag +/******/ __webpack_require__.l = (url, done, key, chunkId) => { +/******/ if(inProgress[url]) { inProgress[url].push(done); return; } +/******/ let script, needAttach; +/******/ if(key !== undefined) { +/******/ const scripts = document.getElementsByTagName("script"); +/******/ for(var i = 0; i < scripts.length; i++) { +/******/ const s = scripts[i]; +/******/ if(s.getAttribute("src") == url) { script = s; break; } +/******/ } +/******/ } +/******/ if(!script) { +/******/ needAttach = true; +/******/ script = document.createElement('script'); +/******/ /******/ script.charset = 'utf-8'; -/******/ script.timeout = 120; -/******/ /******/ if (__webpack_require__.nc) { /******/ script.setAttribute("nonce", __webpack_require__.nc); /******/ } -/******/ script.src = jsonpScriptSrc(chunkId); -/******/ var timeout = setTimeout(function(){ -/******/ onScriptComplete({ type: 'timeout', target: script }); -/******/ }, 120000); -/******/ script.onerror = script.onload = onScriptComplete; -/******/ function onScriptComplete(event) { -/******/ // avoid mem leaks in IE. -/******/ script.onerror = script.onload = null; -/******/ clearTimeout(timeout); -/******/ var chunk = installedChunks[chunkId]; -/******/ if(chunk !== 0) { -/******/ if(chunk) { -/******/ var errorType = event && (event.type === 'load' ? 'missing' : event.type); -/******/ var realSrc = event && event.target && event.target.src; -/******/ var error = new Error('Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'); -/******/ error.type = errorType; -/******/ error.request = realSrc; -/******/ chunk[1](error); -/******/ } -/******/ installedChunks[chunkId] = undefined; -/******/ } -/******/ }; -/******/ head.appendChild(script); +/******/ +/******/ +/******/ script.src = url; /******/ } -/******/ } -/******/ return Promise.all(promises); -/******/ }; -/******/ -/******/ // 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, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); -/******/ } -/******/ }; -/******/ +/******/ inProgress[url] = [done]; +/******/ const onScriptComplete = (prev, event) => { +/******/ // avoid mem leaks in IE. +/******/ script.onerror = script.onload = null; +/******/ clearTimeout(timeout); +/******/ const doneFns = inProgress[url]; +/******/ delete inProgress[url]; +/******/ script.parentNode?.removeChild(script); +/******/ doneFns?.forEach((fn) => (fn(event))); +/******/ if(prev) return prev(event); +/******/ } +/******/ const timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000); +/******/ script.onerror = onScriptComplete.bind(null, script.onerror); +/******/ script.onload = onScriptComplete.bind(null, script.onload); +/******/ needAttach && document.head.appendChild(script); +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/make namespace object */ /******/ // define __esModule on exports -/******/ __webpack_require__.r = function(exports) { +/******/ __webpack_require__.r = (exports) => { +/******/ if(Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } /******/ Object.defineProperty(exports, '__esModule', { value: true }); /******/ }; -/******/ -/******/ // 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/runtime/publicPath */ /******/ __webpack_require__.p = "dist/"; -/******/ -/******/ // on error function for async loading -/******/ __webpack_require__.oe = function(err) { console.error(err); throw err; }; -/******/ -/******/ var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || []; -/******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); -/******/ jsonpArray.push = webpackJsonpCallback; -/******/ jsonpArray = jsonpArray.slice(); -/******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]); -/******/ var parentJsonpFunction = oldJsonpFunction; -/******/ -/******/ -/******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 4); -/******/ }) +/******/ +/******/ /* webpack/runtime/jsonp chunk loading */ +/******/ (() => { +/******/ // no baseURI +/******/ +/******/ // object to store loaded and loading chunks +/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched +/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded +/******/ const installedChunks = { +/******/ 792: 0 +/******/ }; +/******/ +/******/ __webpack_require__.f.j = (chunkId, promises) => { +/******/ // JSONP chunk loading for javascript +/******/ let installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined; +/******/ if(installedChunkData !== 0) { // 0 means "already installed". +/******/ +/******/ // a Promise means "currently loading". +/******/ if(installedChunkData) { +/******/ promises.push(installedChunkData[2]); +/******/ } else { +/******/ if(true) { // all chunks have JS +/******/ // setup Promise in chunk cache +/******/ const promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject])); +/******/ promises.push(installedChunkData[2] = promise); +/******/ +/******/ // start chunk loading +/******/ const url = __webpack_require__.p + __webpack_require__.u(chunkId); +/******/ // create error before stack unwound to get useful stacktrace later +/******/ const error = new Error(); +/******/ const loadingEnded = (event) => { +/******/ if(__webpack_require__.o(installedChunks, chunkId)) { +/******/ installedChunkData = installedChunks[chunkId]; +/******/ if(installedChunkData !== 0) installedChunks[chunkId] = undefined; +/******/ if(installedChunkData) { +/******/ const errorType = event && (event.type === 'load' ? 'missing' : event.type); +/******/ const realSrc = event && event.target && event.target.src; +/******/ error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'; +/******/ error.name = 'ChunkLoadError'; +/******/ error.type = errorType; +/******/ error.request = realSrc; +/******/ error.event = event; +/******/ installedChunkData[1](error); +/******/ } +/******/ } +/******/ }; +/******/ __webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId); +/******/ } +/******/ } +/******/ } +/******/ }; +/******/ +/******/ // no prefetching +/******/ +/******/ // no preloaded +/******/ +/******/ // no HMR +/******/ +/******/ // no HMR manifest +/******/ +/******/ // no on chunks loaded +/******/ +/******/ // install a JSONP callback for chunk loading +/******/ const webpackJsonpCallback = (parentChunkLoadingFunction, data) => { +/******/ let [chunkIds, moreModules, runtime] = data; +/******/ // add "moreModules" to the modules object, +/******/ // then flag all "chunkIds" as loaded and fire callback +/******/ var moduleId, chunkId, i = 0; +/******/ if(chunkIds.some((id) => (installedChunks[id] !== 0))) { +/******/ for(moduleId in moreModules) { +/******/ if(__webpack_require__.o(moreModules, moduleId)) { +/******/ __webpack_require__.m[moduleId] = moreModules[moduleId]; +/******/ } +/******/ } +/******/ if(runtime) var result = runtime(__webpack_require__); +/******/ } +/******/ if(parentChunkLoadingFunction) parentChunkLoadingFunction(data); +/******/ for(;i < chunkIds.length; i++) { +/******/ chunkId = chunkIds[i]; +/******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) { +/******/ installedChunks[chunkId][0](); +/******/ } +/******/ installedChunks[chunkId] = 0; +/******/ } +/******/ +/******/ } +/******/ +/******/ const chunkLoadingGlobal = self["webpackChunk"] = self["webpackChunk"] || []; +/******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0)); +/******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal)); +/******/ })(); +/******/ /************************************************************************/ ```
-``` javascript -/******/ ([ -/* 0 */, -/* 1 */, -/* 2 */, -/* 3 */ -/*!**************************************************!*\ - !*** ./templates lazy ^\.\/.*$ namespace object ***! - \**************************************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -var map = { - "./bar": [ - 2, - 2 - ], - "./bar.js": [ - 2, - 2 - ], - "./baz": [ - 1, - 1 - ], - "./baz.js": [ - 1, - 1 - ], - "./foo": [ - 0, - 0 - ], - "./foo.js": [ - 0, - 0 - ] -}; -function webpackAsyncContext(req) { - var ids = map[req]; - if(!ids) { - return Promise.resolve().then(function() { - var e = new Error("Cannot find module '" + req + "'"); - e.code = 'MODULE_NOT_FOUND'; - throw e; - }); - } - return __webpack_require__.e(ids[1]).then(function() { - var module = __webpack_require__(ids[0]); - return module; - }); -} -webpackAsyncContext.keys = function webpackAsyncContextKeys() { - return Object.keys(map); -}; -webpackAsyncContext.id = 3; -module.exports = webpackAsyncContext; - -/***/ }), -/* 4 */ +``` js +let __webpack_exports__ = {}; +// This entry needs to be wrapped in an IIFE because it needs to be isolated against other modules in the chunk. +(() => { /*!********************!*\ !*** ./example.js ***! \********************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: __webpack_require__ */ async function getTemplate(templateName) { try { - let template = await __webpack_require__(3)(`./${templateName}`); + let template = await __webpack_require__(1)(`./${templateName}`); console.log(template); } catch(err) { console.error("template error"); @@ -304,9 +365,10 @@ getTemplate("baz"); +})(); -/***/ }) -/******/ ]); +/******/ })() +; ``` # Info @@ -314,79 +376,78 @@ getTemplate("baz"); ## Unoptimized ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 - Asset Size Chunks Chunk Names -0.output.js 436 bytes 0 [emitted] -1.output.js 445 bytes 1 [emitted] -2.output.js 439 bytes 2 [emitted] - output.js 8.3 KiB 3 [emitted] main -Entrypoint main = output.js -chunk {0} 0.output.js 41 bytes <{3}> [rendered] - > ./foo [3] ./templates lazy ^\.\/.*$ namespace object ./foo - > ./foo.js [3] ./templates lazy ^\.\/.*$ namespace object ./foo.js - [0] ./templates/foo.js 41 bytes {0} [optional] [built] - [exports: default] - context element ./foo.js [3] ./templates lazy ^\.\/.*$ namespace object ./foo.js - context element ./foo [3] ./templates lazy ^\.\/.*$ namespace object ./foo -chunk {1} 1.output.js 41 bytes <{3}> [rendered] - > ./baz [3] ./templates lazy ^\.\/.*$ namespace object ./baz - > ./baz.js [3] ./templates lazy ^\.\/.*$ namespace object ./baz.js - [1] ./templates/baz.js 41 bytes {1} [optional] [built] - [exports: default] - context element ./baz.js [3] ./templates lazy ^\.\/.*$ namespace object ./baz.js - context element ./baz [3] ./templates lazy ^\.\/.*$ namespace object ./baz -chunk {2} 2.output.js 41 bytes <{3}> [rendered] - > ./bar [3] ./templates lazy ^\.\/.*$ namespace object ./bar - > ./bar.js [3] ./templates lazy ^\.\/.*$ namespace object ./bar.js - [2] ./templates/bar.js 41 bytes {2} [optional] [built] - [exports: default] - context element ./bar.js [3] ./templates lazy ^\.\/.*$ namespace object ./bar.js - context element ./bar [3] ./templates lazy ^\.\/.*$ namespace object ./bar -chunk {3} output.js (main) 456 bytes >{0}< >{1}< >{2}< [entry] [rendered] - > .\example.js main - [3] ./templates lazy ^\.\/.*$ namespace object 160 bytes {3} [optional] [built] - import() context lazy ./templates [4] ./example.js 3:23-60 - [4] ./example.js 296 bytes {3} [built] - single entry .\example.js main +asset output.js 11.5 KiB [emitted] (name: main) +asset 717.output.js 846 bytes [emitted] +asset 776.output.js 846 bytes [emitted] +asset 0.output.js 844 bytes [emitted] +chunk (runtime: main) 0.output.js 38 bytes [rendered] + > ./baz ./templates/ lazy ^\.\/.*$ referencedExports: namespace object ./baz + > ./baz.js ./templates/ lazy ^\.\/.*$ referencedExports: namespace object ./baz.js + ./templates/baz.js 38 bytes [optional] [built] [code generated] + [exports: default] + [used exports unknown] + import() context element ./baz ./templates/ lazy ^\.\/.*$ referencedExports: namespace object ./baz + import() context element ./baz.js ./templates/ lazy ^\.\/.*$ referencedExports: namespace object ./baz.js +chunk (runtime: main) 717.output.js 38 bytes [rendered] + > ./foo ./templates/ lazy ^\.\/.*$ referencedExports: namespace object ./foo + > ./foo.js ./templates/ lazy ^\.\/.*$ referencedExports: namespace object ./foo.js + ./templates/foo.js 38 bytes [optional] [built] [code generated] + [exports: default] + [used exports unknown] + import() context element ./foo ./templates/ lazy ^\.\/.*$ referencedExports: namespace object ./foo + import() context element ./foo.js ./templates/ lazy ^\.\/.*$ referencedExports: namespace object ./foo.js +chunk (runtime: main) 776.output.js 38 bytes [rendered] + > ./bar ./templates/ lazy ^\.\/.*$ referencedExports: namespace object ./bar + > ./bar.js ./templates/ lazy ^\.\/.*$ referencedExports: namespace object ./bar.js + ./templates/bar.js 38 bytes [optional] [built] [code generated] + [exports: default] + [used exports unknown] + import() context element ./bar ./templates/ lazy ^\.\/.*$ referencedExports: namespace object ./bar + import() context element ./bar.js ./templates/ lazy ^\.\/.*$ referencedExports: namespace object ./bar.js +chunk (runtime: main) output.js (main) 441 bytes (javascript) 5.93 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 5.93 KiB 8 modules + dependent modules 160 bytes [dependent] 1 module + ./example.js 281 bytes [built] [code generated] + [used exports unknown] + entry ./example.js main +webpack X.X.X compiled successfully ``` ## Production mode ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 - Asset Size Chunks Chunk Names -0.output.js 113 bytes 0 [emitted] -1.output.js 114 bytes 1 [emitted] -2.output.js 115 bytes 2 [emitted] - output.js 2.12 KiB 3 [emitted] main -Entrypoint main = output.js -chunk {0} 0.output.js 41 bytes <{3}> [rendered] - > ./foo [3] ./templates lazy ^\.\/.*$ namespace object ./foo - > ./foo.js [3] ./templates lazy ^\.\/.*$ namespace object ./foo.js - [0] ./templates/foo.js 41 bytes {0} [optional] [built] - [exports: default] - context element ./foo.js [3] ./templates lazy ^\.\/.*$ namespace object ./foo.js - context element ./foo [3] ./templates lazy ^\.\/.*$ namespace object ./foo -chunk {1} 1.output.js 41 bytes <{3}> [rendered] - > ./baz [3] ./templates lazy ^\.\/.*$ namespace object ./baz - > ./baz.js [3] ./templates lazy ^\.\/.*$ namespace object ./baz.js - [1] ./templates/baz.js 41 bytes {1} [optional] [built] - [exports: default] - context element ./baz.js [3] ./templates lazy ^\.\/.*$ namespace object ./baz.js - context element ./baz [3] ./templates lazy ^\.\/.*$ namespace object ./baz -chunk {2} 2.output.js 41 bytes <{3}> [rendered] - > ./bar [3] ./templates lazy ^\.\/.*$ namespace object ./bar - > ./bar.js [3] ./templates lazy ^\.\/.*$ namespace object ./bar.js - [2] ./templates/bar.js 41 bytes {2} [optional] [built] - [exports: default] - context element ./bar.js [3] ./templates lazy ^\.\/.*$ namespace object ./bar.js - context element ./bar [3] ./templates lazy ^\.\/.*$ namespace object ./bar -chunk {3} output.js (main) 456 bytes >{0}< >{1}< >{2}< [entry] [rendered] - > .\example.js main - [3] ./templates lazy ^\.\/.*$ namespace object 160 bytes {3} [optional] [built] - import() context lazy ./templates [4] ./example.js 3:23-60 - [4] ./example.js 296 bytes {3} [built] - single entry .\example.js main +asset output.js 2.67 KiB [emitted] [minimized] (name: main) +asset 717.output.js 117 bytes [emitted] [minimized] +asset 776.output.js 117 bytes [emitted] [minimized] +asset 0.output.js 114 bytes [emitted] [minimized] +chunk (runtime: main) 0.output.js 38 bytes [rendered] + > ./baz ./templates/ lazy ^\.\/.*$ referencedExports: namespace object ./baz + > ./baz.js ./templates/ lazy ^\.\/.*$ referencedExports: namespace object ./baz.js + ./templates/baz.js 38 bytes [optional] [built] [code generated] + [exports: default] + import() context element ./baz ./templates/ lazy ^\.\/.*$ referencedExports: namespace object ./baz + import() context element ./baz.js ./templates/ lazy ^\.\/.*$ referencedExports: namespace object ./baz.js +chunk (runtime: main) 717.output.js 38 bytes [rendered] + > ./foo ./templates/ lazy ^\.\/.*$ referencedExports: namespace object ./foo + > ./foo.js ./templates/ lazy ^\.\/.*$ referencedExports: namespace object ./foo.js + ./templates/foo.js 38 bytes [optional] [built] [code generated] + [exports: default] + import() context element ./foo ./templates/ lazy ^\.\/.*$ referencedExports: namespace object ./foo + import() context element ./foo.js ./templates/ lazy ^\.\/.*$ referencedExports: namespace object ./foo.js +chunk (runtime: main) 776.output.js 38 bytes [rendered] + > ./bar ./templates/ lazy ^\.\/.*$ referencedExports: namespace object ./bar + > ./bar.js ./templates/ lazy ^\.\/.*$ referencedExports: namespace object ./bar.js + ./templates/bar.js 38 bytes [optional] [built] [code generated] + [exports: default] + import() context element ./bar ./templates/ lazy ^\.\/.*$ referencedExports: namespace object ./bar + import() context element ./bar.js ./templates/ lazy ^\.\/.*$ referencedExports: namespace object ./bar.js +chunk (runtime: main) output.js (main) 441 bytes (javascript) 5.93 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 5.93 KiB 8 modules + dependent modules 160 bytes [dependent] 1 module + ./example.js 281 bytes [built] [code generated] + [no exports used] + entry ./example.js main +webpack X.X.X compiled successfully ``` diff --git a/examples/code-splitting-native-import-context/template.md b/examples/code-splitting-native-import-context/template.md index f26cba077bf..f3c30f24490 100644 --- a/examples/code-splitting-native-import-context/template.md +++ b/examples/code-splitting-native-import-context/template.md @@ -2,26 +2,26 @@ This example illustrates how to leverage the `import()` syntax to create ContextModules which are separated into separate chunks for each module in the `./templates` folder. -``` javascript -{{example.js}} +```javascript +_{{example.js}}_ ``` # templates/ -* foo.js -* baz.js -* bar.js +- foo.js +- baz.js +- bar.js All templates are of this pattern: -``` javascript -{{templates/foo.js}} +```javascript +_{{templates/foo.js}}_ ``` # dist/output.js -``` javascript -{{dist/output.js}} +```javascript +_{{dist/output.js}}_ ``` # Info @@ -29,11 +29,11 @@ All templates are of this pattern: ## Unoptimized ``` -{{stdout}} +_{{stdout}}_ ``` ## Production mode ``` -{{production:stdout}} +_{{production:stdout}}_ ``` diff --git a/examples/code-splitting-native-import-context/webpack.config.js b/examples/code-splitting-native-import-context/webpack.config.js index 0d554bf62ea..6685d0c375d 100644 --- a/examples/code-splitting-native-import-context/webpack.config.js +++ b/examples/code-splitting-native-import-context/webpack.config.js @@ -1,5 +1,10 @@ -module.exports = { +"use strict"; + +/** @type {import("webpack").Configuration} */ +const config = { optimization: { - occurrenceOrder: true // To keep filename consistent between different modes (for example building only) + chunkIds: "deterministic" // To keep filename consistent between different modes (for example building only) } }; + +module.exports = config; diff --git a/examples/code-splitting-specify-chunk-name/README.md b/examples/code-splitting-specify-chunk-name/README.md index 16638fad95a..89f8f1e16e1 100644 --- a/examples/code-splitting-specify-chunk-name/README.md +++ b/examples/code-splitting-specify-chunk-name/README.md @@ -1,8 +1,8 @@ # example.js -This example illustrates how to specify chunk name in `require.ensure()` and `import()` to separated modules into separate chunks manually. +This example illustrates how to specify the chunk name in `require.ensure()` and `import()` to separated modules into separate chunks manually. -``` javascript +```javascript import("./templates/foo" /* webpackChunkName: "chunk-foo" */ ).then(function(foo) { console.log('foo:', foo); }) @@ -20,13 +20,13 @@ import("./templates/ba" + createContextVar /* webpackChunkName: "chunk-bar-baz" # templates/ -* foo.js -* baz.js -* bar.js +- foo.js +- baz.js +- bar.js All templates are of this pattern: -``` javascript +```javascript var foo = "foo"; export default foo; @@ -34,271 +34,329 @@ export default foo; # dist/output.js -
/******/ (function(modules) { /* webpackBootstrap */ }) +```javascript +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ([ +/* 0 */, +/* 1 */ +/*!**************************************************************************************************!*\ + !*** ./templates/ lazy ^\.\/ba.*$ referencedExports: chunkName: chunk-bar-baz namespace object ***! + \**************************************************************************************************/ +/*! default exports */ +/*! exports [not provided] [no usage info] */ +/*! runtime requirements: __webpack_require__.e, module, __webpack_require__.o, __webpack_require__, __webpack_require__.* */ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { -``` javascript -/******/ (function(modules) { // webpackBootstrap -/******/ // install a JSONP callback for chunk loading -/******/ function webpackJsonpCallback(data) { -/******/ var chunkIds = data[0]; -/******/ var moreModules = data[1]; -/******/ -/******/ // add "moreModules" to the modules object, -/******/ // then flag all "chunkIds" as loaded and fire callback -/******/ var moduleId, chunkId, i = 0, resolves = []; -/******/ for(;i < chunkIds.length; i++) { -/******/ chunkId = chunkIds[i]; -/******/ if(installedChunks[chunkId]) { -/******/ resolves.push(installedChunks[chunkId][0]); -/******/ } -/******/ installedChunks[chunkId] = 0; -/******/ } -/******/ for(moduleId in moreModules) { -/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { -/******/ modules[moduleId] = moreModules[moduleId]; -/******/ } -/******/ } -/******/ if(parentJsonpFunction) parentJsonpFunction(data); -/******/ while(resolves.length) { -/******/ resolves.shift()(); -/******/ } -/******/ -/******/ }; -/******/ -/******/ +const map = { + "./bar": [ + 3, + [ + 994 + ] + ], + "./bar.js": [ + 3, + [ + 994 + ] + ], + "./baz": [ + 4, + [ + 792 + ] + ], + "./baz.js": [ + 4, + [ + 792 + ] + ] +}; +function webpackAsyncContext(req) { + try { + if(!__webpack_require__.o(map, req)) { + return Promise.resolve().then(() => { + const e = new Error("Cannot find module '" + req + "'"); + e.code = 'MODULE_NOT_FOUND'; + throw e; +}); + } + } catch(err) { + return Promise.reject(err); + } + + const ids = map[req], id = ids[0]; + return __webpack_require__.e(ids[1][0]).then(() => (__webpack_require__(id))); +} +webpackAsyncContext.keys = () => (Object.keys(map)); +webpackAsyncContext.id = 1; +module.exports = webpackAsyncContext; + +/***/ }) +/******/ ]); +``` + +
/* webpack runtime code */ + +``` js +/************************************************************************/ /******/ // The module cache -/******/ var installedModules = {}; -/******/ -/******/ // object to store loaded and loading chunks -/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched -/******/ // Promise = chunk loading, 0 = chunk loaded -/******/ var installedChunks = { -/******/ 3: 0 -/******/ }; -/******/ -/******/ -/******/ -/******/ // script path function -/******/ function jsonpScriptSrc(chunkId) { -/******/ return __webpack_require__.p + "" + chunkId + ".output.js" -/******/ } -/******/ +/******/ const __webpack_module_cache__ = {}; +/******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { -/******/ /******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) { -/******/ return installedModules[moduleId].exports; +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, +/******/ const module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed /******/ exports: {} /******/ }; -/******/ +/******/ /******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } -/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = __webpack_modules__; +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/define property getters */ +/******/ // define getter/value functions for harmony exports +/******/ __webpack_require__.d = (exports, definition) => { +/******/ if(Array.isArray(definition)) { +/******/ var i = 0; +/******/ while(i < definition.length) { +/******/ var key = definition[i++]; +/******/ var binding = definition[i++]; +/******/ if(!__webpack_require__.o(exports, key)) { +/******/ if(binding === 0) { +/******/ Object.defineProperty(exports, key, { enumerable: true, value: definition[i++] }); +/******/ } else { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: binding }); +/******/ } +/******/ } else if(binding === 0) { i++; } +/******/ } +/******/ } else { +/******/ for(var key in definition) { +/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); +/******/ } +/******/ } +/******/ } +/******/ }; +/******/ +/******/ /* webpack/runtime/ensure chunk */ +/******/ __webpack_require__.f = {}; /******/ // This file contains only the entry chunk. /******/ // The chunk loading function for additional chunks -/******/ __webpack_require__.e = function requireEnsure(chunkId) { -/******/ var promises = []; -/******/ -/******/ -/******/ // JSONP chunk loading for javascript -/******/ -/******/ var installedChunkData = installedChunks[chunkId]; -/******/ if(installedChunkData !== 0) { // 0 means "already installed". -/******/ -/******/ // a Promise means "currently loading". -/******/ if(installedChunkData) { -/******/ promises.push(installedChunkData[2]); -/******/ } else { -/******/ // setup Promise in chunk cache -/******/ var promise = new Promise(function(resolve, reject) { -/******/ installedChunkData = installedChunks[chunkId] = [resolve, reject]; -/******/ }); -/******/ promises.push(installedChunkData[2] = promise); -/******/ -/******/ // start chunk loading -/******/ var head = document.getElementsByTagName('head')[0]; -/******/ var script = document.createElement('script'); -/******/ +/******/ __webpack_require__.e = (chunkId) => { +/******/ return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => { +/******/ __webpack_require__.f[key](chunkId, promises); +/******/ return promises; +/******/ }, [])); +/******/ }; +/******/ +/******/ /* webpack/runtime/get javascript chunk filename */ +/******/ // This function allow to reference async chunks +/******/ __webpack_require__.u = (chunkId) => { +/******/ // return url for filenames based on template +/******/ return "" + chunkId + ".output.js"; +/******/ }; +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)); +/******/ +/******/ /* webpack/runtime/load script */ +/******/ (() => { +/******/ const inProgress = {}; +/******/ // data-webpack is not used as build has no uniqueName +/******/ // loadScript function to load a script via script tag +/******/ __webpack_require__.l = (url, done, key, chunkId) => { +/******/ if(inProgress[url]) { inProgress[url].push(done); return; } +/******/ let script, needAttach; +/******/ if(key !== undefined) { +/******/ const scripts = document.getElementsByTagName("script"); +/******/ for(var i = 0; i < scripts.length; i++) { +/******/ const s = scripts[i]; +/******/ if(s.getAttribute("src") == url) { script = s; break; } +/******/ } +/******/ } +/******/ if(!script) { +/******/ needAttach = true; +/******/ script = document.createElement('script'); +/******/ /******/ script.charset = 'utf-8'; -/******/ script.timeout = 120; -/******/ /******/ if (__webpack_require__.nc) { /******/ script.setAttribute("nonce", __webpack_require__.nc); /******/ } -/******/ script.src = jsonpScriptSrc(chunkId); -/******/ var timeout = setTimeout(function(){ -/******/ onScriptComplete({ type: 'timeout', target: script }); -/******/ }, 120000); -/******/ script.onerror = script.onload = onScriptComplete; -/******/ function onScriptComplete(event) { -/******/ // avoid mem leaks in IE. -/******/ script.onerror = script.onload = null; -/******/ clearTimeout(timeout); -/******/ var chunk = installedChunks[chunkId]; -/******/ if(chunk !== 0) { -/******/ if(chunk) { -/******/ var errorType = event && (event.type === 'load' ? 'missing' : event.type); -/******/ var realSrc = event && event.target && event.target.src; -/******/ var error = new Error('Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'); -/******/ error.type = errorType; -/******/ error.request = realSrc; -/******/ chunk[1](error); -/******/ } -/******/ installedChunks[chunkId] = undefined; -/******/ } -/******/ }; -/******/ head.appendChild(script); +/******/ +/******/ +/******/ script.src = url; /******/ } -/******/ } -/******/ return Promise.all(promises); -/******/ }; -/******/ -/******/ // 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, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); -/******/ } -/******/ }; -/******/ +/******/ inProgress[url] = [done]; +/******/ const onScriptComplete = (prev, event) => { +/******/ // avoid mem leaks in IE. +/******/ script.onerror = script.onload = null; +/******/ clearTimeout(timeout); +/******/ const doneFns = inProgress[url]; +/******/ delete inProgress[url]; +/******/ script.parentNode?.removeChild(script); +/******/ doneFns?.forEach((fn) => (fn(event))); +/******/ if(prev) return prev(event); +/******/ } +/******/ const timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000); +/******/ script.onerror = onScriptComplete.bind(null, script.onerror); +/******/ script.onload = onScriptComplete.bind(null, script.onload); +/******/ needAttach && document.head.appendChild(script); +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/make namespace object */ /******/ // define __esModule on exports -/******/ __webpack_require__.r = function(exports) { +/******/ __webpack_require__.r = (exports) => { +/******/ if(Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } /******/ Object.defineProperty(exports, '__esModule', { value: true }); /******/ }; -/******/ -/******/ // 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/runtime/publicPath */ /******/ __webpack_require__.p = "dist/"; -/******/ -/******/ // on error function for async loading -/******/ __webpack_require__.oe = function(err) { console.error(err); throw err; }; -/******/ -/******/ var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || []; -/******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); -/******/ jsonpArray.push = webpackJsonpCallback; -/******/ jsonpArray = jsonpArray.slice(); -/******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]); -/******/ var parentJsonpFunction = oldJsonpFunction; -/******/ -/******/ -/******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 4); -/******/ }) +/******/ +/******/ /* webpack/runtime/jsonp chunk loading */ +/******/ (() => { +/******/ // no baseURI +/******/ +/******/ // object to store loaded and loading chunks +/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched +/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded +/******/ const installedChunks = { +/******/ 411: 0 +/******/ }; +/******/ +/******/ __webpack_require__.f.j = (chunkId, promises) => { +/******/ // JSONP chunk loading for javascript +/******/ let installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined; +/******/ if(installedChunkData !== 0) { // 0 means "already installed". +/******/ +/******/ // a Promise means "currently loading". +/******/ if(installedChunkData) { +/******/ promises.push(installedChunkData[2]); +/******/ } else { +/******/ if(true) { // all chunks have JS +/******/ // setup Promise in chunk cache +/******/ const promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject])); +/******/ promises.push(installedChunkData[2] = promise); +/******/ +/******/ // start chunk loading +/******/ const url = __webpack_require__.p + __webpack_require__.u(chunkId); +/******/ // create error before stack unwound to get useful stacktrace later +/******/ const error = new Error(); +/******/ const loadingEnded = (event) => { +/******/ if(__webpack_require__.o(installedChunks, chunkId)) { +/******/ installedChunkData = installedChunks[chunkId]; +/******/ if(installedChunkData !== 0) installedChunks[chunkId] = undefined; +/******/ if(installedChunkData) { +/******/ const errorType = event && (event.type === 'load' ? 'missing' : event.type); +/******/ const realSrc = event && event.target && event.target.src; +/******/ error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'; +/******/ error.name = 'ChunkLoadError'; +/******/ error.type = errorType; +/******/ error.request = realSrc; +/******/ error.event = event; +/******/ installedChunkData[1](error); +/******/ } +/******/ } +/******/ }; +/******/ __webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId); +/******/ } +/******/ } +/******/ } +/******/ }; +/******/ +/******/ // no prefetching +/******/ +/******/ // no preloaded +/******/ +/******/ // no HMR +/******/ +/******/ // no HMR manifest +/******/ +/******/ // no on chunks loaded +/******/ +/******/ // install a JSONP callback for chunk loading +/******/ const webpackJsonpCallback = (parentChunkLoadingFunction, data) => { +/******/ let [chunkIds, moreModules, runtime] = data; +/******/ // add "moreModules" to the modules object, +/******/ // then flag all "chunkIds" as loaded and fire callback +/******/ var moduleId, chunkId, i = 0; +/******/ if(chunkIds.some((id) => (installedChunks[id] !== 0))) { +/******/ for(moduleId in moreModules) { +/******/ if(__webpack_require__.o(moreModules, moduleId)) { +/******/ __webpack_require__.m[moduleId] = moreModules[moduleId]; +/******/ } +/******/ } +/******/ if(runtime) var result = runtime(__webpack_require__); +/******/ } +/******/ if(parentChunkLoadingFunction) parentChunkLoadingFunction(data); +/******/ for(;i < chunkIds.length; i++) { +/******/ chunkId = chunkIds[i]; +/******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) { +/******/ installedChunks[chunkId][0](); +/******/ } +/******/ installedChunks[chunkId] = 0; +/******/ } +/******/ +/******/ } +/******/ +/******/ const chunkLoadingGlobal = self["webpackChunk"] = self["webpackChunk"] || []; +/******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0)); +/******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal)); +/******/ })(); +/******/ /************************************************************************/ ```
-``` javascript -/******/ ([ -/* 0 */, -/* 1 */, -/* 2 */, -/* 3 */ -/*!****************************************************!*\ - !*** ./templates lazy ^\.\/ba.*$ namespace object ***! - \****************************************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -var map = { - "./bar": [ - 2, - 1 - ], - "./bar.js": [ - 2, - 1 - ], - "./baz": [ - 1, - 0 - ], - "./baz.js": [ - 1, - 0 - ] -}; -function webpackAsyncContext(req) { - var ids = map[req]; - if(!ids) { - return Promise.resolve().then(function() { - var e = new Error("Cannot find module '" + req + "'"); - e.code = 'MODULE_NOT_FOUND'; - throw e; - }); - } - return __webpack_require__.e(ids[1]).then(function() { - var module = __webpack_require__(ids[0]); - return module; - }); -} -webpackAsyncContext.keys = function webpackAsyncContextKeys() { - return Object.keys(map); -}; -webpackAsyncContext.id = 3; -module.exports = webpackAsyncContext; - -/***/ }), -/* 4 */ +``` js +let __webpack_exports__ = {}; +// This entry needs to be wrapped in an IIFE because it needs to be isolated against other modules in the chunk. +(() => { /*!********************!*\ !*** ./example.js ***! \********************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -__webpack_require__.e(/*! import() | chunk-foo */ 2).then(__webpack_require__.bind(null, /*! ./templates/foo */ 0)).then(function(foo) { +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: __webpack_require__, __webpack_require__.e, __webpack_require__.* */ +__webpack_require__.e(/*! import() | chunk-foo */ 45).then(__webpack_require__.bind(__webpack_require__, /*! ./templates/foo */ 2)).then(function(foo) { console.log('foo:', foo); }) -__webpack_require__.e(/*! require.ensure | chunk-foo1 */ 2).then((function(require) { - var foo = __webpack_require__(/*! ./templates/foo */ 0); +__webpack_require__.e(/*! require.ensure | chunk-foo1 */ 45).then((function(require) { + var foo = __webpack_require__(/*! ./templates/foo */ 2); console.log('foo:', foo); -}).bind(null, __webpack_require__)).catch(__webpack_require__.oe); +}).bind(null, __webpack_require__))['catch'](__webpack_require__.oe); var createContextVar = "r"; -__webpack_require__(3)("./ba" + createContextVar).then(function(bar) { +__webpack_require__(1)("./ba" + createContextVar).then(function(bar) { console.log('bar:', bar); }) +})(); -/***/ }) -/******/ ]); +/******/ })() +; ``` # Info @@ -306,79 +364,78 @@ __webpack_require__(3)("./ba" + createContextVar).then(function(bar) { ## Unoptimized ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 - Asset Size Chunks Chunk Names -0.output.js 445 bytes 0 [emitted] chunk-bar-baz2 -1.output.js 439 bytes 1 [emitted] chunk-bar-baz0 -2.output.js 436 bytes 2 [emitted] chunk-foo - output.js 8.5 KiB 3 [emitted] main -Entrypoint main = output.js -chunk {0} 0.output.js (chunk-bar-baz2) 41 bytes <{3}> [rendered] - > ./baz [3] ./templates lazy ^\.\/ba.*$ namespace object ./baz - > ./baz.js [3] ./templates lazy ^\.\/ba.*$ namespace object ./baz.js - [1] ./templates/baz.js 41 bytes {0} [optional] [built] - [exports: default] - context element ./baz.js [3] ./templates lazy ^\.\/ba.*$ namespace object ./baz.js - context element ./baz [3] ./templates lazy ^\.\/ba.*$ namespace object ./baz -chunk {1} 1.output.js (chunk-bar-baz0) 41 bytes <{3}> [rendered] - > ./bar [3] ./templates lazy ^\.\/ba.*$ namespace object ./bar - > ./bar.js [3] ./templates lazy ^\.\/ba.*$ namespace object ./bar.js - [2] ./templates/bar.js 41 bytes {1} [optional] [built] - [exports: default] - context element ./bar.js [3] ./templates lazy ^\.\/ba.*$ namespace object ./bar.js - context element ./bar [3] ./templates lazy ^\.\/ba.*$ namespace object ./bar -chunk {2} 2.output.js (chunk-foo) 41 bytes <{3}> [rendered] - > ./templates/foo [4] ./example.js 1:0-62 - > [4] ./example.js 5:0-8:16 - [0] ./templates/foo.js 41 bytes {2} [built] - [exports: default] - import() ./templates/foo [4] ./example.js 1:0-62 - cjs require ./templates/foo [4] ./example.js 6:11-37 -chunk {3} output.js (main) 580 bytes >{0}< >{1}< >{2}< [entry] [rendered] - > .\example.js main - [3] ./templates lazy ^\.\/ba.*$ namespace object 160 bytes {3} [built] - import() context lazy ./templates [4] ./example.js 11:0-84 - [4] ./example.js 420 bytes {3} [built] - single entry .\example.js main +asset output.js 11.8 KiB [emitted] (name: main) +asset 792.output.js 846 bytes [emitted] (name: chunk-bar-baz2) +asset 994.output.js 846 bytes [emitted] (name: chunk-bar-baz0) +asset 45.output.js 845 bytes [emitted] (name: chunk-foo) +chunk (runtime: main) 45.output.js (chunk-foo) 38 bytes [rendered] + > ./templates/foo ./example.js 1:0-62 + > ./example.js 5:0-8:16 + ./templates/foo.js 38 bytes [built] [code generated] + [exports: default] + [used exports unknown] + import() ./templates/foo ./example.js 1:0-62 + cjs require ./templates/foo ./example.js 6:11-37 +chunk (runtime: main) output.js (main) 565 bytes (javascript) 5.93 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 5.93 KiB 8 modules + dependent modules 160 bytes [dependent] 1 module + ./example.js 405 bytes [built] [code generated] + [used exports unknown] + entry ./example.js main +chunk (runtime: main) 792.output.js (chunk-bar-baz2) 38 bytes [rendered] + > ./baz ./templates/ lazy ^\.\/ba.*$ referencedExports: chunkName: chunk-bar-baz namespace object ./baz + > ./baz.js ./templates/ lazy ^\.\/ba.*$ referencedExports: chunkName: chunk-bar-baz namespace object ./baz.js + ./templates/baz.js 38 bytes [optional] [built] [code generated] + [exports: default] + [used exports unknown] + import() context element ./baz ./templates/ lazy ^\.\/ba.*$ referencedExports: chunkName: chunk-bar-baz namespace object ./baz + import() context element ./baz.js ./templates/ lazy ^\.\/ba.*$ referencedExports: chunkName: chunk-bar-baz namespace object ./baz.js +chunk (runtime: main) 994.output.js (chunk-bar-baz0) 38 bytes [rendered] + > ./bar ./templates/ lazy ^\.\/ba.*$ referencedExports: chunkName: chunk-bar-baz namespace object ./bar + > ./bar.js ./templates/ lazy ^\.\/ba.*$ referencedExports: chunkName: chunk-bar-baz namespace object ./bar.js + ./templates/bar.js 38 bytes [optional] [built] [code generated] + [exports: default] + [used exports unknown] + import() context element ./bar ./templates/ lazy ^\.\/ba.*$ referencedExports: chunkName: chunk-bar-baz namespace object ./bar + import() context element ./bar.js ./templates/ lazy ^\.\/ba.*$ referencedExports: chunkName: chunk-bar-baz namespace object ./bar.js +webpack X.X.X compiled successfully ``` ## Production mode ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 - Asset Size Chunks Chunk Names -0.output.js 114 bytes 0 [emitted] chunk-bar-baz2 -1.output.js 115 bytes 1 [emitted] chunk-bar-baz0 -2.output.js 113 bytes 2 [emitted] chunk-foo - output.js 2.14 KiB 3 [emitted] main -Entrypoint main = output.js -chunk {0} 0.output.js (chunk-bar-baz2) 41 bytes <{3}> [rendered] - > ./baz [3] ./templates lazy ^\.\/ba.*$ namespace object ./baz - > ./baz.js [3] ./templates lazy ^\.\/ba.*$ namespace object ./baz.js - [1] ./templates/baz.js 41 bytes {0} [optional] [built] - [exports: default] - context element ./baz.js [3] ./templates lazy ^\.\/ba.*$ namespace object ./baz.js - context element ./baz [3] ./templates lazy ^\.\/ba.*$ namespace object ./baz -chunk {1} 1.output.js (chunk-bar-baz0) 41 bytes <{3}> [rendered] - > ./bar [3] ./templates lazy ^\.\/ba.*$ namespace object ./bar - > ./bar.js [3] ./templates lazy ^\.\/ba.*$ namespace object ./bar.js - [2] ./templates/bar.js 41 bytes {1} [optional] [built] - [exports: default] - context element ./bar.js [3] ./templates lazy ^\.\/ba.*$ namespace object ./bar.js - context element ./bar [3] ./templates lazy ^\.\/ba.*$ namespace object ./bar -chunk {2} 2.output.js (chunk-foo) 41 bytes <{3}> [rendered] - > ./templates/foo [4] ./example.js 1:0-62 - > [4] ./example.js 5:0-8:16 - [0] ./templates/foo.js 41 bytes {2} [built] - [exports: default] - import() ./templates/foo [4] ./example.js 1:0-62 - cjs require ./templates/foo [4] ./example.js 6:11-37 -chunk {3} output.js (main) 580 bytes >{0}< >{1}< >{2}< [entry] [rendered] - > .\example.js main - [3] ./templates lazy ^\.\/ba.*$ namespace object 160 bytes {3} [built] - import() context lazy ./templates [4] ./example.js 11:0-84 - [4] ./example.js 420 bytes {3} [built] - single entry .\example.js main +asset output.js 2.69 KiB [emitted] [minimized] (name: main) +asset 994.output.js 117 bytes [emitted] [minimized] (name: chunk-bar-baz0) +asset 45.output.js 116 bytes [emitted] [minimized] (name: chunk-foo) +asset 792.output.js 116 bytes [emitted] [minimized] (name: chunk-bar-baz2) +chunk (runtime: main) 45.output.js (chunk-foo) 38 bytes [rendered] + > ./templates/foo ./example.js 1:0-62 + > ./example.js 5:0-8:16 + ./templates/foo.js 38 bytes [built] [code generated] + [exports: default] + import() ./templates/foo ./example.js 1:0-62 + cjs require ./templates/foo ./example.js 6:11-37 +chunk (runtime: main) output.js (main) 565 bytes (javascript) 5.93 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 5.93 KiB 8 modules + dependent modules 160 bytes [dependent] 1 module + ./example.js 405 bytes [built] [code generated] + [no exports used] + entry ./example.js main +chunk (runtime: main) 792.output.js (chunk-bar-baz2) 38 bytes [rendered] + > ./baz ./templates/ lazy ^\.\/ba.*$ referencedExports: chunkName: chunk-bar-baz namespace object ./baz + > ./baz.js ./templates/ lazy ^\.\/ba.*$ referencedExports: chunkName: chunk-bar-baz namespace object ./baz.js + ./templates/baz.js 38 bytes [optional] [built] [code generated] + [exports: default] + import() context element ./baz ./templates/ lazy ^\.\/ba.*$ referencedExports: chunkName: chunk-bar-baz namespace object ./baz + import() context element ./baz.js ./templates/ lazy ^\.\/ba.*$ referencedExports: chunkName: chunk-bar-baz namespace object ./baz.js +chunk (runtime: main) 994.output.js (chunk-bar-baz0) 38 bytes [rendered] + > ./bar ./templates/ lazy ^\.\/ba.*$ referencedExports: chunkName: chunk-bar-baz namespace object ./bar + > ./bar.js ./templates/ lazy ^\.\/ba.*$ referencedExports: chunkName: chunk-bar-baz namespace object ./bar.js + ./templates/bar.js 38 bytes [optional] [built] [code generated] + [exports: default] + import() context element ./bar ./templates/ lazy ^\.\/ba.*$ referencedExports: chunkName: chunk-bar-baz namespace object ./bar + import() context element ./bar.js ./templates/ lazy ^\.\/ba.*$ referencedExports: chunkName: chunk-bar-baz namespace object ./bar.js +webpack X.X.X compiled successfully ``` diff --git a/examples/code-splitting-specify-chunk-name/template.md b/examples/code-splitting-specify-chunk-name/template.md index b85d38e981d..0c63d10ca61 100644 --- a/examples/code-splitting-specify-chunk-name/template.md +++ b/examples/code-splitting-specify-chunk-name/template.md @@ -1,27 +1,27 @@ # example.js -This example illustrates how to specify chunk name in `require.ensure()` and `import()` to separated modules into separate chunks manually. +This example illustrates how to specify the chunk name in `require.ensure()` and `import()` to separated modules into separate chunks manually. -``` javascript -{{example.js}} +```javascript +_{{example.js}}_ ``` # templates/ -* foo.js -* baz.js -* bar.js +- foo.js +- baz.js +- bar.js All templates are of this pattern: -``` javascript -{{templates/foo.js}} +```javascript +_{{templates/foo.js}}_ ``` # dist/output.js -``` javascript -{{dist/output.js}} +```javascript +_{{dist/output.js}}_ ``` # Info @@ -29,11 +29,11 @@ All templates are of this pattern: ## Unoptimized ``` -{{stdout}} +_{{stdout}}_ ``` ## Production mode ``` -{{production:stdout}} +_{{production:stdout}}_ ``` diff --git a/examples/code-splitting-specify-chunk-name/webpack.config.js b/examples/code-splitting-specify-chunk-name/webpack.config.js index 0d554bf62ea..6685d0c375d 100644 --- a/examples/code-splitting-specify-chunk-name/webpack.config.js +++ b/examples/code-splitting-specify-chunk-name/webpack.config.js @@ -1,5 +1,10 @@ -module.exports = { +"use strict"; + +/** @type {import("webpack").Configuration} */ +const config = { optimization: { - occurrenceOrder: true // To keep filename consistent between different modes (for example building only) + chunkIds: "deterministic" // To keep filename consistent between different modes (for example building only) } }; + +module.exports = config; diff --git a/examples/code-splitting/README.md b/examples/code-splitting/README.md index c6dd1964083..7fef0083fe7 100644 --- a/examples/code-splitting/README.md +++ b/examples/code-splitting/README.md @@ -1,32 +1,31 @@ This example illustrates a very simple case of Code Splitting with `require.ensure`. -* `a` and `b` are required normally via CommonJS -* `c` is depended through the `require.ensure` array. - * This means: make it available, but don't execute it - * webpack will load it on demand -* `b` and `d` are required via CommonJs in the `require.ensure` callback - * webpack detects that these are in the on-demand-callback and - * will load them on demand - * webpacks optimizer can optimize `b` away - * as it is already available through the parent chunks +- `a` and `b` are required normally via CommonJS +- `c` is made available(,but doesn't get execute) through the `require.ensure` array. + - webpack will load it on demand +- `b` and `d` are required via CommonJs in the `require.ensure` callback + - webpack detects that these are in the on-demand-callback and + - will load them on demand + - webpack's optimizer can optimize `b` away + - as it is already available through the parent chunks You can see that webpack outputs two files/chunks: -* `output.js` is the entry chunk and contains - * the module system - * chunk loading logic - * the entry point `example.js` - * module `a` - * module `b` -* `1.js` is an additional chunk (on demand loaded) and contains - * module `c` - * module `d` +- `output.js` is the entry chunk and contains + - the module system + - chunk loading logic + - the entry point `example.js` + - module `a` + - module `b` +- `1.output.js` is an additional chunk (on-demand loaded) and contains + - module `c` + - module `d` You can see that chunks are loaded via JSONP. The additional chunks are pretty small and minimize well. # example.js -``` javascript +```javascript var a = require("a"); var b = require("b"); require.ensure(["c"], function(require) { @@ -35,262 +34,281 @@ require.ensure(["c"], function(require) { }); ``` - # dist/output.js -
/******/ (function(modules) { /* webpackBootstrap */ }) +```javascript +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ([ +/* 0 */, +/* 1 */ +/*!***************************!*\ + !*** ./node_modules/a.js ***! + \***************************/ +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: */ +/***/ (() => { -``` javascript -/******/ (function(modules) { // webpackBootstrap -/******/ // install a JSONP callback for chunk loading -/******/ function webpackJsonpCallback(data) { -/******/ var chunkIds = data[0]; -/******/ var moreModules = data[1]; -/******/ -/******/ // add "moreModules" to the modules object, -/******/ // then flag all "chunkIds" as loaded and fire callback -/******/ var moduleId, chunkId, i = 0, resolves = []; -/******/ for(;i < chunkIds.length; i++) { -/******/ chunkId = chunkIds[i]; -/******/ if(installedChunks[chunkId]) { -/******/ resolves.push(installedChunks[chunkId][0]); -/******/ } -/******/ installedChunks[chunkId] = 0; -/******/ } -/******/ for(moduleId in moreModules) { -/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { -/******/ modules[moduleId] = moreModules[moduleId]; -/******/ } -/******/ } -/******/ if(parentJsonpFunction) parentJsonpFunction(data); -/******/ while(resolves.length) { -/******/ resolves.shift()(); -/******/ } -/******/ -/******/ }; -/******/ -/******/ +// module a + +/***/ }), +/* 2 */ +/*!***************************!*\ + !*** ./node_modules/b.js ***! + \***************************/ +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: */ +/***/ (() => { + +// module b + +/***/ }) +/******/ ]); +``` + +
/* webpack runtime code */ + +``` js +/************************************************************************/ /******/ // The module cache -/******/ var installedModules = {}; -/******/ -/******/ // object to store loaded and loading chunks -/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched -/******/ // Promise = chunk loading, 0 = chunk loaded -/******/ var installedChunks = { -/******/ 1: 0 -/******/ }; -/******/ -/******/ -/******/ -/******/ // script path function -/******/ function jsonpScriptSrc(chunkId) { -/******/ return __webpack_require__.p + "" + chunkId + ".output.js" -/******/ } -/******/ +/******/ const __webpack_module_cache__ = {}; +/******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { -/******/ /******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) { -/******/ return installedModules[moduleId].exports; +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, +/******/ const module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed /******/ exports: {} /******/ }; -/******/ +/******/ /******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } -/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = __webpack_modules__; +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/ensure chunk */ +/******/ __webpack_require__.f = {}; /******/ // This file contains only the entry chunk. /******/ // The chunk loading function for additional chunks -/******/ __webpack_require__.e = function requireEnsure(chunkId) { -/******/ var promises = []; -/******/ -/******/ -/******/ // JSONP chunk loading for javascript -/******/ -/******/ var installedChunkData = installedChunks[chunkId]; -/******/ if(installedChunkData !== 0) { // 0 means "already installed". -/******/ -/******/ // a Promise means "currently loading". -/******/ if(installedChunkData) { -/******/ promises.push(installedChunkData[2]); -/******/ } else { -/******/ // setup Promise in chunk cache -/******/ var promise = new Promise(function(resolve, reject) { -/******/ installedChunkData = installedChunks[chunkId] = [resolve, reject]; -/******/ }); -/******/ promises.push(installedChunkData[2] = promise); -/******/ -/******/ // start chunk loading -/******/ var head = document.getElementsByTagName('head')[0]; -/******/ var script = document.createElement('script'); -/******/ +/******/ __webpack_require__.e = (chunkId) => { +/******/ return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => { +/******/ __webpack_require__.f[key](chunkId, promises); +/******/ return promises; +/******/ }, [])); +/******/ }; +/******/ +/******/ /* webpack/runtime/get javascript chunk filename */ +/******/ // This function allow to reference async chunks +/******/ __webpack_require__.u = (chunkId) => { +/******/ // return url for filenames based on template +/******/ return "" + chunkId + ".output.js"; +/******/ }; +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)); +/******/ +/******/ /* webpack/runtime/load script */ +/******/ (() => { +/******/ const inProgress = {}; +/******/ // data-webpack is not used as build has no uniqueName +/******/ // loadScript function to load a script via script tag +/******/ __webpack_require__.l = (url, done, key, chunkId) => { +/******/ if(inProgress[url]) { inProgress[url].push(done); return; } +/******/ let script, needAttach; +/******/ if(key !== undefined) { +/******/ const scripts = document.getElementsByTagName("script"); +/******/ for(var i = 0; i < scripts.length; i++) { +/******/ const s = scripts[i]; +/******/ if(s.getAttribute("src") == url) { script = s; break; } +/******/ } +/******/ } +/******/ if(!script) { +/******/ needAttach = true; +/******/ script = document.createElement('script'); +/******/ /******/ script.charset = 'utf-8'; -/******/ script.timeout = 120; -/******/ /******/ if (__webpack_require__.nc) { /******/ script.setAttribute("nonce", __webpack_require__.nc); /******/ } -/******/ script.src = jsonpScriptSrc(chunkId); -/******/ var timeout = setTimeout(function(){ -/******/ onScriptComplete({ type: 'timeout', target: script }); -/******/ }, 120000); -/******/ script.onerror = script.onload = onScriptComplete; -/******/ function onScriptComplete(event) { -/******/ // avoid mem leaks in IE. -/******/ script.onerror = script.onload = null; -/******/ clearTimeout(timeout); -/******/ var chunk = installedChunks[chunkId]; -/******/ if(chunk !== 0) { -/******/ if(chunk) { -/******/ var errorType = event && (event.type === 'load' ? 'missing' : event.type); -/******/ var realSrc = event && event.target && event.target.src; -/******/ var error = new Error('Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'); -/******/ error.type = errorType; -/******/ error.request = realSrc; -/******/ chunk[1](error); +/******/ +/******/ +/******/ script.src = url; +/******/ } +/******/ inProgress[url] = [done]; +/******/ const onScriptComplete = (prev, event) => { +/******/ // avoid mem leaks in IE. +/******/ script.onerror = script.onload = null; +/******/ clearTimeout(timeout); +/******/ const doneFns = inProgress[url]; +/******/ delete inProgress[url]; +/******/ script.parentNode?.removeChild(script); +/******/ doneFns?.forEach((fn) => (fn(event))); +/******/ if(prev) return prev(event); +/******/ } +/******/ const timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000); +/******/ script.onerror = onScriptComplete.bind(null, script.onerror); +/******/ script.onload = onScriptComplete.bind(null, script.onload); +/******/ needAttach && document.head.appendChild(script); +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/publicPath */ +/******/ __webpack_require__.p = "dist/"; +/******/ +/******/ /* webpack/runtime/jsonp chunk loading */ +/******/ (() => { +/******/ // no baseURI +/******/ +/******/ // object to store loaded and loading chunks +/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched +/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded +/******/ const installedChunks = { +/******/ "main": 0 +/******/ }; +/******/ +/******/ __webpack_require__.f.j = (chunkId, promises) => { +/******/ // JSONP chunk loading for javascript +/******/ let installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined; +/******/ if(installedChunkData !== 0) { // 0 means "already installed". +/******/ +/******/ // a Promise means "currently loading". +/******/ if(installedChunkData) { +/******/ promises.push(installedChunkData[2]); +/******/ } else { +/******/ if(true) { // all chunks have JS +/******/ // setup Promise in chunk cache +/******/ const promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject])); +/******/ promises.push(installedChunkData[2] = promise); +/******/ +/******/ // start chunk loading +/******/ const url = __webpack_require__.p + __webpack_require__.u(chunkId); +/******/ // create error before stack unwound to get useful stacktrace later +/******/ const error = new Error(); +/******/ const loadingEnded = (event) => { +/******/ if(__webpack_require__.o(installedChunks, chunkId)) { +/******/ installedChunkData = installedChunks[chunkId]; +/******/ if(installedChunkData !== 0) installedChunks[chunkId] = undefined; +/******/ if(installedChunkData) { +/******/ const errorType = event && (event.type === 'load' ? 'missing' : event.type); +/******/ const realSrc = event && event.target && event.target.src; +/******/ error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'; +/******/ error.name = 'ChunkLoadError'; +/******/ error.type = errorType; +/******/ error.request = realSrc; +/******/ error.event = event; +/******/ installedChunkData[1](error); +/******/ } +/******/ } +/******/ }; +/******/ __webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId); /******/ } -/******/ installedChunks[chunkId] = undefined; /******/ } -/******/ }; -/******/ head.appendChild(script); +/******/ } +/******/ }; +/******/ +/******/ // no prefetching +/******/ +/******/ // no preloaded +/******/ +/******/ // no HMR +/******/ +/******/ // no HMR manifest +/******/ +/******/ // no on chunks loaded +/******/ +/******/ // install a JSONP callback for chunk loading +/******/ const webpackJsonpCallback = (parentChunkLoadingFunction, data) => { +/******/ let [chunkIds, moreModules, runtime] = data; +/******/ // add "moreModules" to the modules object, +/******/ // then flag all "chunkIds" as loaded and fire callback +/******/ var moduleId, chunkId, i = 0; +/******/ if(chunkIds.some((id) => (installedChunks[id] !== 0))) { +/******/ for(moduleId in moreModules) { +/******/ if(__webpack_require__.o(moreModules, moduleId)) { +/******/ __webpack_require__.m[moduleId] = moreModules[moduleId]; +/******/ } +/******/ } +/******/ if(runtime) var result = runtime(__webpack_require__); /******/ } +/******/ if(parentChunkLoadingFunction) parentChunkLoadingFunction(data); +/******/ for(;i < chunkIds.length; i++) { +/******/ chunkId = chunkIds[i]; +/******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) { +/******/ installedChunks[chunkId][0](); +/******/ } +/******/ installedChunks[chunkId] = 0; +/******/ } +/******/ /******/ } -/******/ return Promise.all(promises); -/******/ }; -/******/ -/******/ // 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, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); -/******/ } -/******/ }; -/******/ -/******/ // define __esModule on exports -/******/ __webpack_require__.r = function(exports) { -/******/ Object.defineProperty(exports, '__esModule', { value: true }); -/******/ }; -/******/ -/******/ // 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 = "dist/"; -/******/ -/******/ // on error function for async loading -/******/ __webpack_require__.oe = function(err) { console.error(err); throw err; }; -/******/ -/******/ var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || []; -/******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); -/******/ jsonpArray.push = webpackJsonpCallback; -/******/ jsonpArray = jsonpArray.slice(); -/******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]); -/******/ var parentJsonpFunction = oldJsonpFunction; -/******/ -/******/ -/******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 2); -/******/ }) +/******/ +/******/ const chunkLoadingGlobal = self["webpackChunk"] = self["webpackChunk"] || []; +/******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0)); +/******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal)); +/******/ })(); +/******/ /************************************************************************/ ```
-``` javascript -/******/ ([ -/* 0 */ -/*!***************************!*\ - !*** ./node_modules/b.js ***! - \***************************/ -/*! no static exports found */ -/***/ (function(module, exports) { - -// module b - -/***/ }), -/* 1 */ -/*!***************************!*\ - !*** ./node_modules/a.js ***! - \***************************/ -/*! no static exports found */ -/***/ (function(module, exports) { - -// module a - -/***/ }), -/* 2 */ +``` js +// This entry needs to be wrapped in an IIFE because it needs to be isolated against other modules in the chunk. +(() => { /*!********************!*\ !*** ./example.js ***! \********************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: __webpack_require__, __webpack_require__.e, __webpack_require__.* */ var a = __webpack_require__(/*! a */ 1); -var b = __webpack_require__(/*! b */ 0); -__webpack_require__.e(/*! require.ensure */ 0).then((function(require) { - __webpack_require__(/*! b */ 0).xyz(); - var d = __webpack_require__(/*! d */ 3); -}).bind(null, __webpack_require__)).catch(__webpack_require__.oe); +var b = __webpack_require__(/*! b */ 2); +__webpack_require__.e(/*! require.ensure */ "node_modules_c_js-node_modules_d_js").then((function(require) { + (__webpack_require__(/*! b */ 2).xyz)(); + var d = __webpack_require__(/*! d */ 4); +}).bind(null, __webpack_require__))['catch'](__webpack_require__.oe); +})(); -/***/ }) -/******/ ]); +/******/ })() +; ``` -# dist/0.output.js +# dist/node_modules_c_js-node_modules_d_js.output.js -``` javascript -(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0],[ +```javascript +(self["webpackChunk"] = self["webpackChunk"] || []).push([["node_modules_c_js-node_modules_d_js"],[ /* 0 */, /* 1 */, /* 2 */, /* 3 */ /*!***************************!*\ - !*** ./node_modules/d.js ***! + !*** ./node_modules/c.js ***! \***************************/ -/*! no static exports found */ -/***/ (function(module, exports) { +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: */ +/***/ (() => { -// module d +// module c /***/ }), /* 4 */ /*!***************************!*\ - !*** ./node_modules/c.js ***! + !*** ./node_modules/d.js ***! \***************************/ -/*! no static exports found */ -/***/ (function(module, exports) { +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: */ +/***/ (() => { -// module c +// module d /***/ }) ]]); @@ -298,8 +316,8 @@ __webpack_require__.e(/*! require.ensure */ 0).then((function(require) { Minimized -``` javascript -(window.webpackJsonp=window.webpackJsonp||[]).push([[0],[,,,function(n,o){},function(n,o){}]]); +```javascript +(self.webpackChunk=self.webpackChunk||[]).push([["node_modules_c_js-node_modules_d_js"],{605(){},576(){}}]); ``` # Info @@ -307,37 +325,45 @@ Minimized ## Unoptimized ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 - Asset Size Chunks Chunk Names -0.output.js 490 bytes 0 [emitted] - output.js 7.73 KiB 1 [emitted] main -Entrypoint main = output.js -chunk {0} 0.output.js 22 bytes <{1}> [rendered] - > [2] ./example.js 3:0-6:2 - 2 modules -chunk {1} output.js (main) 166 bytes >{0}< [entry] [rendered] - > .\example.js main - [2] ./example.js 144 bytes {1} [built] - single entry .\example.js main - + 2 hidden modules +asset output.js 9.32 KiB [emitted] (name: main) +asset node_modules_c_js-node_modules_d_js.output.js 562 bytes [emitted] +chunk (runtime: main) output.js (main) 161 bytes (javascript) 4.95 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 4.95 KiB 6 modules + dependent modules 22 bytes [dependent] 2 modules + ./example.js 139 bytes [built] [code generated] + [used exports unknown] + entry ./example.js main +chunk (runtime: main) node_modules_c_js-node_modules_d_js.output.js 22 bytes [rendered] + > ./example.js 3:0-6:2 + ./node_modules/c.js 11 bytes [built] [code generated] + [used exports unknown] + require.ensure item c ./example.js 3:0-6:2 + ./node_modules/d.js 11 bytes [built] [code generated] + [used exports unknown] + cjs require d ./example.js 5:12-24 +webpack X.X.X compiled successfully ``` ## Production mode ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 - Asset Size Chunks Chunk Names -0.output.js 95 bytes 0 [emitted] - output.js 1.71 KiB 1 [emitted] main -Entrypoint main = output.js -chunk {0} 0.output.js 22 bytes <{1}> [rendered] - > [2] ./example.js 3:0-6:2 - 2 modules -chunk {1} output.js (main) 166 bytes >{0}< [entry] [rendered] - > .\example.js main - [2] ./example.js 144 bytes {1} [built] - single entry .\example.js main - + 2 hidden modules +asset output.js 1.8 KiB [emitted] [minimized] (name: main) +asset node_modules_c_js-node_modules_d_js.output.js 108 bytes [emitted] [minimized] +chunk (runtime: main) output.js (main) 161 bytes (javascript) 4.95 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 4.95 KiB 6 modules + dependent modules 22 bytes [dependent] 2 modules + ./example.js 139 bytes [built] [code generated] + [no exports used] + entry ./example.js main +chunk (runtime: main) node_modules_c_js-node_modules_d_js.output.js 22 bytes [rendered] + > ./example.js 3:0-6:2 + ./node_modules/c.js 11 bytes [built] [code generated] + [used exports unknown] + require.ensure item c ./example.js 3:0-6:2 + ./node_modules/d.js 11 bytes [built] [code generated] + [used exports unknown] + cjs require d ./example.js 5:12-24 +webpack X.X.X compiled successfully ``` diff --git a/examples/code-splitting/template.md b/examples/code-splitting/template.md index 7c21f7ae5f4..1b5e16233c4 100644 --- a/examples/code-splitting/template.md +++ b/examples/code-splitting/template.md @@ -1,52 +1,50 @@ This example illustrates a very simple case of Code Splitting with `require.ensure`. -* `a` and `b` are required normally via CommonJS -* `c` is depended through the `require.ensure` array. - * This means: make it available, but don't execute it - * webpack will load it on demand -* `b` and `d` are required via CommonJs in the `require.ensure` callback - * webpack detects that these are in the on-demand-callback and - * will load them on demand - * webpacks optimizer can optimize `b` away - * as it is already available through the parent chunks +- `a` and `b` are required normally via CommonJS +- `c` is made available(,but doesn't get execute) through the `require.ensure` array. + - webpack will load it on demand +- `b` and `d` are required via CommonJs in the `require.ensure` callback + - webpack detects that these are in the on-demand-callback and + - will load them on demand + - webpack's optimizer can optimize `b` away + - as it is already available through the parent chunks You can see that webpack outputs two files/chunks: -* `output.js` is the entry chunk and contains - * the module system - * chunk loading logic - * the entry point `example.js` - * module `a` - * module `b` -* `1.js` is an additional chunk (on demand loaded) and contains - * module `c` - * module `d` +- `output.js` is the entry chunk and contains + - the module system + - chunk loading logic + - the entry point `example.js` + - module `a` + - module `b` +- `1.output.js` is an additional chunk (on-demand loaded) and contains + - module `c` + - module `d` You can see that chunks are loaded via JSONP. The additional chunks are pretty small and minimize well. # example.js -``` javascript -{{example.js}} +```javascript +_{{example.js}}_ ``` - # dist/output.js -``` javascript -{{dist/output.js}} +```javascript +_{{dist/output.js}}_ ``` -# dist/0.output.js +# dist/node_modules_c_js-node_modules_d_js.output.js -``` javascript -{{dist/0.output.js}} +```javascript +_{{dist/node_modules_c_js-node_modules_d_js.output.js}}_ ``` Minimized -``` javascript -{{production:dist/0.output.js}} +```javascript +_{{production:dist/node_modules_c_js-node_modules_d_js.output.js}}_ ``` # Info @@ -54,11 +52,11 @@ Minimized ## Unoptimized ``` -{{stdout}} +_{{stdout}}_ ``` ## Production mode ``` -{{production:stdout}} +_{{production:stdout}}_ ``` diff --git a/examples/code-splitting/webpack.config.js b/examples/code-splitting/webpack.config.js index 0d554bf62ea..3a2046aae21 100644 --- a/examples/code-splitting/webpack.config.js +++ b/examples/code-splitting/webpack.config.js @@ -1,5 +1,10 @@ -module.exports = { +"use strict"; + +/** @type {import("webpack").Configuration} */ +const config = { optimization: { - occurrenceOrder: true // To keep filename consistent between different modes (for example building only) + chunkIds: "named" // To keep filename consistent between different modes (for example building only) } }; + +module.exports = config; diff --git a/examples/coffee-script/README.md b/examples/coffee-script/README.md index 3f9c31fa5af..acb48473029 100644 --- a/examples/coffee-script/README.md +++ b/examples/coffee-script/README.md @@ -1,13 +1,12 @@ - # example.js -``` javascript +```javascript console.log(require("./cup1")); ``` # cup1.coffee -``` coffee-script +```coffee-script module.exports = cool: "stuff" answer: 42 @@ -17,7 +16,7 @@ module.exports = # cup2.coffee -``` coffee-script +```coffee-script console.log "yeah coffee-script" module.exports = 42 @@ -25,102 +24,22 @@ module.exports = 42 # dist/output.js -
/******/ (function(modules) { /* webpackBootstrap */ }) - -``` javascript -/******/ (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, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); -/******/ } -/******/ }; -/******/ -/******/ // define __esModule on exports -/******/ __webpack_require__.r = function(exports) { -/******/ Object.defineProperty(exports, '__esModule', { value: true }); -/******/ }; -/******/ -/******/ // 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 = "dist/"; -/******/ -/******/ -/******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 0); -/******/ }) -/************************************************************************/ -``` - -
- -``` javascript -/******/ ([ -/* 0 */ -/*!********************!*\ - !*** ./example.js ***! - \********************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -console.log(__webpack_require__(/*! ./cup1 */ 1)); - -/***/ }), +```javascript +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ([ +/* 0 */, /* 1 */ /*!*********************!*\ !*** ./cup1.coffee ***! \*********************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! default exports */ +/*! export again [provided] [no usage info] [missing usage info prevents renaming] */ +/*! export answer [provided] [no usage info] [missing usage info prevents renaming] */ +/*! export cool [provided] [no usage info] [missing usage info prevents renaming] */ +/*! export external [provided] [no usage info] [missing usage info prevents renaming] */ +/*! other exports [not provided] [no usage info] */ +/*! runtime requirements: module, __webpack_require__ */ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { module.exports = { cool: "stuff", @@ -135,8 +54,10 @@ module.exports = { /*!*********************!*\ !*** ./cup2.coffee ***! \*********************/ -/*! no static exports found */ -/***/ (function(module, exports) { +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: module */ +/*! CommonJS bailout: module.exports is used directly at 3:0-14 */ +/***/ ((module) => { console.log("yeah coffee-script"); @@ -144,7 +65,55 @@ module.exports = 42; /***/ }) -/******/ ]); +/******/ ]); +``` + +
/* webpack runtime code */ + +``` js +/************************************************************************/ +/******/ // The module cache +/******/ const __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ const 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; +/******/ } +/******/ +/************************************************************************/ +``` + +
+ +``` js +// This entry needs to be wrapped in an IIFE because it needs to be isolated against other modules in the chunk. +(() => { +/*!********************!*\ + !*** ./example.js ***! + \********************/ +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: __webpack_require__ */ +console.log(__webpack_require__(/*! ./cup1 */ 1)); +})(); + +/******/ })() +; ``` # Info @@ -152,37 +121,25 @@ module.exports = 42; ## Unoptimized ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 - Asset Size Chunks Chunk Names -output.js 3.35 KiB 0 [emitted] main -Entrypoint main = output.js -chunk {0} output.js (main) 206 bytes [entry] [rendered] - > .\example.js main - [0] ./example.js 31 bytes {0} [built] - single entry .\example.js main - [1] ./cup1.coffee 118 bytes {0} [built] - cjs require ./cup1 [0] ./example.js 1:12-29 - [2] ./cup2.coffee 57 bytes {0} [built] - cjs require ./cup2.coffee [1] ./cup1.coffee 4:12-36 - cjs require ./cup2 [1] ./cup1.coffee 5:9-26 +asset output.js 2.55 KiB [emitted] (name: main) +chunk (runtime: main) output.js (main) 206 bytes [entry] [rendered] + > ./example.js main + dependent modules 175 bytes [dependent] 2 modules + ./example.js 31 bytes [built] [code generated] + [used exports unknown] + entry ./example.js main +webpack X.X.X compiled successfully ``` ## Production mode ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 - Asset Size Chunks Chunk Names -output.js 708 bytes 0 [emitted] main -Entrypoint main = output.js -chunk {0} output.js (main) 206 bytes [entry] [rendered] - > .\example.js main - [0] ./cup2.coffee 57 bytes {0} [built] - cjs require ./cup2.coffee [1] ./cup1.coffee 4:12-36 - cjs require ./cup2 [1] ./cup1.coffee 5:9-26 - [1] ./cup1.coffee 118 bytes {0} [built] - cjs require ./cup1 [2] ./example.js 1:12-29 - [2] ./example.js 31 bytes {0} [built] - single entry .\example.js main -``` \ No newline at end of file +asset output.js 300 bytes [emitted] [minimized] (name: main) +chunk (runtime: main) output.js (main) 206 bytes [entry] [rendered] + > ./example.js main + dependent modules 175 bytes [dependent] 2 modules + ./example.js 31 bytes [built] [code generated] + [no exports used] + entry ./example.js main +webpack X.X.X compiled successfully +``` diff --git a/examples/coffee-script/template.md b/examples/coffee-script/template.md index bef6e314e38..c2c60394cc9 100644 --- a/examples/coffee-script/template.md +++ b/examples/coffee-script/template.md @@ -1,26 +1,25 @@ - # example.js -``` javascript -{{example.js}} +```javascript +_{{example.js}}_ ``` # cup1.coffee -``` coffee-script -{{cup1.coffee}} +```coffee-script +_{{cup1.coffee}}_ ``` # cup2.coffee -``` coffee-script -{{cup2.coffee}} +```coffee-script +_{{cup2.coffee}}_ ``` # dist/output.js -``` javascript -{{dist/output.js}} +```javascript +_{{dist/output.js}}_ ``` # Info @@ -28,11 +27,11 @@ ## Unoptimized ``` -{{stdout}} +_{{stdout}}_ ``` ## Production mode ``` -{{production:stdout}} -``` \ No newline at end of file +_{{production:stdout}}_ +``` diff --git a/examples/coffee-script/webpack.config.js b/examples/coffee-script/webpack.config.js index 845f9f4c190..1e37325f590 100644 --- a/examples/coffee-script/webpack.config.js +++ b/examples/coffee-script/webpack.config.js @@ -1,5 +1,8 @@ -module.exports = { - // mode: "development || "production", +"use strict"; + +/** @type {import("webpack").Configuration} */ +const config = { + // mode: "development" || "production", module: { rules: [ { @@ -12,3 +15,5 @@ module.exports = { extensions: [".web.coffee", ".web.js", ".coffee", ".js"] } }; + +module.exports = config; diff --git a/examples/common-chunk-and-vendor-chunk/README.md b/examples/common-chunk-and-vendor-chunk/README.md index f6d5e46aa9c..0ec5a13cb69 100644 --- a/examples/common-chunk-and-vendor-chunk/README.md +++ b/examples/common-chunk-and-vendor-chunk/README.md @@ -33,10 +33,13 @@ With this bundle configuration, you would load your third party libraries, then # webpack.config.js -``` javascript -var path = require("path"); +```javascript +"use strict"; + +const path = require("path"); -module.exports = { +/** @type {import("webpack").Configuration} */ +const config = { // mode: "development" || "production", entry: { pageA: "./pageA", @@ -44,6 +47,7 @@ module.exports = { pageC: "./pageC" }, optimization: { + chunkIds: "named", splitChunks: { cacheGroups: { commons: { @@ -67,230 +71,99 @@ module.exports = { filename: "[name].js" } }; + +module.exports = config; ``` # dist/vendor.js -``` javascript -(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[3],{ +```javascript +(self["webpackChunk"] = self["webpackChunk"] || []).push([["vendor"],{ -/***/ 1: +/***/ 1 /*!*********************************!*\ !*** ./node_modules/vendor1.js ***! \*********************************/ -/*! no static exports found */ -/***/ (function(module, exports) { +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: module */ +/*! CommonJS bailout: module.exports is used directly at 1:0-14 */ +(module) { module.exports = "vendor1"; -/***/ }), +/***/ }, -/***/ 5: +/***/ 5 /*!*********************************!*\ !*** ./node_modules/vendor2.js ***! \*********************************/ -/*! no static exports found */ -/***/ (function(module, exports) { +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: module */ +/*! CommonJS bailout: module.exports is used directly at 1:0-14 */ +(module) { module.exports = "vendor2"; -/***/ }) +/***/ } }]); ``` -# dist/commons~pageA~pageB~pageC.js +# dist/commons-utility2_js.js ``` javascript -(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[4],{ +(self["webpackChunk"] = self["webpackChunk"] || []).push([["commons-utility2_js"],{ -/***/ 3: +/***/ 3 /*!*********************!*\ !*** ./utility2.js ***! \*********************/ -/*! no static exports found */ -/***/ (function(module, exports) { +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: module */ +/*! CommonJS bailout: module.exports is used directly at 1:0-14 */ +(module) { module.exports = "utility2"; -/***/ }) +/***/ } }]); ``` -# dist/commons~pageB~pageC.js +# dist/commons-utility3_js.js ``` javascript -(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[5],{ +(self["webpackChunk"] = self["webpackChunk"] || []).push([["commons-utility3_js"],{ -/***/ 6: +/***/ 6 /*!*********************!*\ !*** ./utility3.js ***! \*********************/ -/*! no static exports found */ -/***/ (function(module, exports) { +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: module */ +/*! CommonJS bailout: module.exports is used directly at 1:0-14 */ +(module) { module.exports = "utility3"; -/***/ }) +/***/ } }]); ``` # dist/pageA.js -
/******/ (function(modules) { /* webpackBootstrap */ }) - -``` javascript -/******/ (function(modules) { // webpackBootstrap -/******/ // install a JSONP callback for chunk loading -/******/ function webpackJsonpCallback(data) { -/******/ var chunkIds = data[0]; -/******/ var moreModules = data[1]; -/******/ var executeModules = data[2]; -/******/ // add "moreModules" to the modules object, -/******/ // then flag all "chunkIds" as loaded and fire callback -/******/ var moduleId, chunkId, i = 0, resolves = []; -/******/ for(;i < chunkIds.length; i++) { -/******/ chunkId = chunkIds[i]; -/******/ if(installedChunks[chunkId]) { -/******/ resolves.push(installedChunks[chunkId][0]); -/******/ } -/******/ installedChunks[chunkId] = 0; -/******/ } -/******/ for(moduleId in moreModules) { -/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { -/******/ modules[moduleId] = moreModules[moduleId]; -/******/ } -/******/ } -/******/ if(parentJsonpFunction) parentJsonpFunction(data); -/******/ while(resolves.length) { -/******/ resolves.shift()(); -/******/ } -/******/ -/******/ // add entry modules from loaded chunk to deferred list -/******/ deferredModules.push.apply(deferredModules, executeModules || []); -/******/ -/******/ // run deferred modules when all chunks ready -/******/ return checkDeferredModules(); -/******/ }; -/******/ function checkDeferredModules() { -/******/ var result; -/******/ for(var i = 0; i < deferredModules.length; i++) { -/******/ var deferredModule = deferredModules[i]; -/******/ var fulfilled = true; -/******/ for(var j = 1; j < deferredModule.length; j++) { -/******/ var depId = deferredModule[j]; -/******/ if(installedChunks[depId] !== 0) fulfilled = false; -/******/ } -/******/ if(fulfilled) { -/******/ deferredModules.splice(i--, 1); -/******/ result = __webpack_require__(__webpack_require__.s = deferredModule[0]); -/******/ } -/******/ } -/******/ return result; -/******/ } -/******/ -/******/ // The module cache -/******/ var installedModules = {}; -/******/ -/******/ // object to store loaded and loading chunks -/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched -/******/ // Promise = chunk loading, 0 = chunk loaded -/******/ var installedChunks = { -/******/ 0: 0 -/******/ }; -/******/ -/******/ var deferredModules = []; -/******/ -/******/ // 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, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); -/******/ } -/******/ }; -/******/ -/******/ // define __esModule on exports -/******/ __webpack_require__.r = function(exports) { -/******/ Object.defineProperty(exports, '__esModule', { value: true }); -/******/ }; -/******/ -/******/ // 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 = "dist/"; -/******/ -/******/ var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || []; -/******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); -/******/ jsonpArray.push = webpackJsonpCallback; -/******/ jsonpArray = jsonpArray.slice(); -/******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]); -/******/ var parentJsonpFunction = oldJsonpFunction; -/******/ -/******/ -/******/ // add entry module to deferred list -/******/ deferredModules.push([0,3,4]); -/******/ // run deferred modules when ready -/******/ return checkDeferredModules(); -/******/ }) -/************************************************************************/ -``` - -
- -``` javascript -/******/ ([ +```javascript +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ([ /* 0 */ /*!******************!*\ !*** ./pageA.js ***! \******************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: module, __webpack_require__ */ +/*! CommonJS bailout: module.exports is used directly at 5:0-14 */ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { var vendor1 = __webpack_require__(/*! vendor1 */ 1); var utility1 = __webpack_require__(/*! ./utility1 */ 2); @@ -305,163 +178,168 @@ module.exports = "pageA"; /*!*********************!*\ !*** ./utility1.js ***! \*********************/ -/*! no static exports found */ -/***/ (function(module, exports) { +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: module */ +/*! CommonJS bailout: module.exports is used directly at 1:0-14 */ +/***/ ((module) => { module.exports = "utility1"; /***/ }) -/******/ ]); +/******/ ]); ``` -# dist/pageB.js +
/* webpack runtime code */ -``` javascript -/******/ (function(modules) { // webpackBootstrap -/******/ // install a JSONP callback for chunk loading -/******/ function webpackJsonpCallback(data) { -/******/ var chunkIds = data[0]; -/******/ var moreModules = data[1]; -/******/ var executeModules = data[2]; -/******/ // add "moreModules" to the modules object, -/******/ // then flag all "chunkIds" as loaded and fire callback -/******/ var moduleId, chunkId, i = 0, resolves = []; -/******/ for(;i < chunkIds.length; i++) { -/******/ chunkId = chunkIds[i]; -/******/ if(installedChunks[chunkId]) { -/******/ resolves.push(installedChunks[chunkId][0]); -/******/ } -/******/ installedChunks[chunkId] = 0; -/******/ } -/******/ for(moduleId in moreModules) { -/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { -/******/ modules[moduleId] = moreModules[moduleId]; -/******/ } -/******/ } -/******/ if(parentJsonpFunction) parentJsonpFunction(data); -/******/ while(resolves.length) { -/******/ resolves.shift()(); -/******/ } -/******/ -/******/ // add entry modules from loaded chunk to deferred list -/******/ deferredModules.push.apply(deferredModules, executeModules || []); -/******/ -/******/ // run deferred modules when all chunks ready -/******/ return checkDeferredModules(); -/******/ }; -/******/ function checkDeferredModules() { -/******/ var result; -/******/ for(var i = 0; i < deferredModules.length; i++) { -/******/ var deferredModule = deferredModules[i]; -/******/ var fulfilled = true; -/******/ for(var j = 1; j < deferredModule.length; j++) { -/******/ var depId = deferredModule[j]; -/******/ if(installedChunks[depId] !== 0) fulfilled = false; -/******/ } -/******/ if(fulfilled) { -/******/ deferredModules.splice(i--, 1); -/******/ result = __webpack_require__(__webpack_require__.s = deferredModule[0]); -/******/ } -/******/ } -/******/ return result; -/******/ } -/******/ +``` js +/************************************************************************/ /******/ // The module cache -/******/ var installedModules = {}; -/******/ -/******/ // object to store loaded and loading chunks -/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched -/******/ // Promise = chunk loading, 0 = chunk loaded -/******/ var installedChunks = { -/******/ 1: 0 -/******/ }; -/******/ -/******/ var deferredModules = []; -/******/ +/******/ const __webpack_module_cache__ = {}; +/******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { -/******/ /******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) { -/******/ return installedModules[moduleId].exports; +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, +/******/ const module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed /******/ exports: {} /******/ }; -/******/ +/******/ /******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ /******/ // 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, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); +/******/ __webpack_require__.m = __webpack_modules__; +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/chunk loaded */ +/******/ (() => { +/******/ const deferred = []; +/******/ __webpack_require__.O = (result, chunkIds, fn, priority) => { +/******/ if(chunkIds) { +/******/ priority = priority || 0; +/******/ for(var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1]; +/******/ deferred[i] = [chunkIds, fn, priority]; +/******/ return; +/******/ } +/******/ let notFulfilled = Infinity; +/******/ for (var i = 0; i < deferred.length; i++) { +/******/ let [chunkIds, fn, priority] = deferred[i]; +/******/ let fulfilled = true; +/******/ for (var j = 0; j < chunkIds.length; j++) { +/******/ if ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every((key) => (__webpack_require__.O[key](chunkIds[j])))) { +/******/ chunkIds.splice(j--, 1); +/******/ } else { +/******/ fulfilled = false; +/******/ if(priority < notFulfilled) notFulfilled = priority; +/******/ } +/******/ } +/******/ if(fulfilled) { +/******/ deferred.splice(i--, 1) +/******/ const r = fn(); +/******/ if (r !== undefined) result = r; +/******/ } +/******/ } +/******/ return result; +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)); +/******/ +/******/ /* webpack/runtime/jsonp chunk loading */ +/******/ (() => { +/******/ // no baseURI +/******/ +/******/ // object to store loaded and loading chunks +/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched +/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded +/******/ const installedChunks = { +/******/ "pageA": 0 +/******/ }; +/******/ +/******/ // no chunk on demand loading +/******/ +/******/ // no prefetching +/******/ +/******/ // no preloaded +/******/ +/******/ // no HMR +/******/ +/******/ // no HMR manifest +/******/ +/******/ __webpack_require__.O.j = (chunkId) => (installedChunks[chunkId] === 0); +/******/ +/******/ // install a JSONP callback for chunk loading +/******/ const webpackJsonpCallback = (parentChunkLoadingFunction, data) => { +/******/ let [chunkIds, moreModules, runtime] = data; +/******/ // add "moreModules" to the modules object, +/******/ // then flag all "chunkIds" as loaded and fire callback +/******/ var moduleId, chunkId, i = 0; +/******/ if(chunkIds.some((id) => (installedChunks[id] !== 0))) { +/******/ for(moduleId in moreModules) { +/******/ if(__webpack_require__.o(moreModules, moduleId)) { +/******/ __webpack_require__.m[moduleId] = moreModules[moduleId]; +/******/ } +/******/ } +/******/ if(runtime) var result = runtime(__webpack_require__); +/******/ } +/******/ if(parentChunkLoadingFunction) parentChunkLoadingFunction(data); +/******/ for(;i < chunkIds.length; i++) { +/******/ chunkId = chunkIds[i]; +/******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) { +/******/ installedChunks[chunkId][0](); +/******/ } +/******/ installedChunks[chunkId] = 0; +/******/ } +/******/ return __webpack_require__.O(result); /******/ } -/******/ }; -/******/ -/******/ // define __esModule on exports -/******/ __webpack_require__.r = function(exports) { -/******/ Object.defineProperty(exports, '__esModule', { value: true }); -/******/ }; -/******/ -/******/ // 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 = "dist/"; -/******/ -/******/ var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || []; -/******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); -/******/ jsonpArray.push = webpackJsonpCallback; -/******/ jsonpArray = jsonpArray.slice(); -/******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]); -/******/ var parentJsonpFunction = oldJsonpFunction; -/******/ -/******/ -/******/ // add entry module to deferred list -/******/ deferredModules.push([4,3,4,5]); -/******/ // run deferred modules when ready -/******/ return checkDeferredModules(); -/******/ }) +/******/ +/******/ const chunkLoadingGlobal = self["webpackChunk"] = self["webpackChunk"] || []; +/******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0)); +/******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal)); +/******/ })(); +/******/ /************************************************************************/ -/******/ ({ +``` + +
+ +``` js +/******/ +/******/ // startup +/******/ // Load entry module and return exports +/******/ // This entry module depends on other loaded chunks and execution need to be delayed +/******/ let __webpack_exports__ = __webpack_require__.O(undefined, ["vendor","commons-utility2_js"], () => (__webpack_require__(0))) +/******/ __webpack_exports__ = __webpack_require__.O(__webpack_exports__); +/******/ +/******/ })() +; +``` -/***/ 4: +# dist/pageB.js + +```javascript +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ({ + +/***/ 4 /*!******************!*\ !*** ./pageB.js ***! \******************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: module, __webpack_require__ */ +/*! CommonJS bailout: module.exports is used directly at 5:0-14 */ +(module, __unused_webpack_exports, __webpack_require__) { var vendor2 = __webpack_require__(/*! vendor2 */ 5); var utility2 = __webpack_require__(/*! ./utility2 */ 3); @@ -470,168 +348,308 @@ var utility3 = __webpack_require__(/*! ./utility3 */ 6); module.exports = "pageB"; -/***/ }) +/***/ } -/******/ }); +/******/ }); ``` -# dist/pageC.js +
/* webpack runtime code */ -``` javascript -/******/ (function(modules) { // webpackBootstrap -/******/ // install a JSONP callback for chunk loading -/******/ function webpackJsonpCallback(data) { -/******/ var chunkIds = data[0]; -/******/ var moreModules = data[1]; -/******/ var executeModules = data[2]; -/******/ // add "moreModules" to the modules object, -/******/ // then flag all "chunkIds" as loaded and fire callback -/******/ var moduleId, chunkId, i = 0, resolves = []; -/******/ for(;i < chunkIds.length; i++) { -/******/ chunkId = chunkIds[i]; -/******/ if(installedChunks[chunkId]) { -/******/ resolves.push(installedChunks[chunkId][0]); -/******/ } -/******/ installedChunks[chunkId] = 0; -/******/ } -/******/ for(moduleId in moreModules) { -/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { -/******/ modules[moduleId] = moreModules[moduleId]; -/******/ } -/******/ } -/******/ if(parentJsonpFunction) parentJsonpFunction(data); -/******/ while(resolves.length) { -/******/ resolves.shift()(); -/******/ } -/******/ -/******/ // add entry modules from loaded chunk to deferred list -/******/ deferredModules.push.apply(deferredModules, executeModules || []); -/******/ -/******/ // run deferred modules when all chunks ready -/******/ return checkDeferredModules(); -/******/ }; -/******/ function checkDeferredModules() { -/******/ var result; -/******/ for(var i = 0; i < deferredModules.length; i++) { -/******/ var deferredModule = deferredModules[i]; -/******/ var fulfilled = true; -/******/ for(var j = 1; j < deferredModule.length; j++) { -/******/ var depId = deferredModule[j]; -/******/ if(installedChunks[depId] !== 0) fulfilled = false; -/******/ } -/******/ if(fulfilled) { -/******/ deferredModules.splice(i--, 1); -/******/ result = __webpack_require__(__webpack_require__.s = deferredModule[0]); -/******/ } -/******/ } -/******/ return result; -/******/ } -/******/ +``` js +/************************************************************************/ /******/ // The module cache -/******/ var installedModules = {}; -/******/ -/******/ // object to store loaded and loading chunks -/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched -/******/ // Promise = chunk loading, 0 = chunk loaded -/******/ var installedChunks = { -/******/ 2: 0 -/******/ }; -/******/ -/******/ var deferredModules = []; -/******/ +/******/ const __webpack_module_cache__ = {}; +/******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { -/******/ /******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) { -/******/ return installedModules[moduleId].exports; +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, +/******/ const module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed /******/ exports: {} /******/ }; -/******/ +/******/ /******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ /******/ // 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, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); +/******/ __webpack_require__.m = __webpack_modules__; +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/chunk loaded */ +/******/ (() => { +/******/ const deferred = []; +/******/ __webpack_require__.O = (result, chunkIds, fn, priority) => { +/******/ if(chunkIds) { +/******/ priority = priority || 0; +/******/ for(var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1]; +/******/ deferred[i] = [chunkIds, fn, priority]; +/******/ return; +/******/ } +/******/ let notFulfilled = Infinity; +/******/ for (var i = 0; i < deferred.length; i++) { +/******/ let [chunkIds, fn, priority] = deferred[i]; +/******/ let fulfilled = true; +/******/ for (var j = 0; j < chunkIds.length; j++) { +/******/ if ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every((key) => (__webpack_require__.O[key](chunkIds[j])))) { +/******/ chunkIds.splice(j--, 1); +/******/ } else { +/******/ fulfilled = false; +/******/ if(priority < notFulfilled) notFulfilled = priority; +/******/ } +/******/ } +/******/ if(fulfilled) { +/******/ deferred.splice(i--, 1) +/******/ const r = fn(); +/******/ if (r !== undefined) result = r; +/******/ } +/******/ } +/******/ return result; +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)); +/******/ +/******/ /* webpack/runtime/jsonp chunk loading */ +/******/ (() => { +/******/ // no baseURI +/******/ +/******/ // object to store loaded and loading chunks +/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched +/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded +/******/ const installedChunks = { +/******/ "pageB": 0 +/******/ }; +/******/ +/******/ // no chunk on demand loading +/******/ +/******/ // no prefetching +/******/ +/******/ // no preloaded +/******/ +/******/ // no HMR +/******/ +/******/ // no HMR manifest +/******/ +/******/ __webpack_require__.O.j = (chunkId) => (installedChunks[chunkId] === 0); +/******/ +/******/ // install a JSONP callback for chunk loading +/******/ const webpackJsonpCallback = (parentChunkLoadingFunction, data) => { +/******/ let [chunkIds, moreModules, runtime] = data; +/******/ // add "moreModules" to the modules object, +/******/ // then flag all "chunkIds" as loaded and fire callback +/******/ var moduleId, chunkId, i = 0; +/******/ if(chunkIds.some((id) => (installedChunks[id] !== 0))) { +/******/ for(moduleId in moreModules) { +/******/ if(__webpack_require__.o(moreModules, moduleId)) { +/******/ __webpack_require__.m[moduleId] = moreModules[moduleId]; +/******/ } +/******/ } +/******/ if(runtime) var result = runtime(__webpack_require__); +/******/ } +/******/ if(parentChunkLoadingFunction) parentChunkLoadingFunction(data); +/******/ for(;i < chunkIds.length; i++) { +/******/ chunkId = chunkIds[i]; +/******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) { +/******/ installedChunks[chunkId][0](); +/******/ } +/******/ installedChunks[chunkId] = 0; +/******/ } +/******/ return __webpack_require__.O(result); /******/ } -/******/ }; -/******/ -/******/ // define __esModule on exports -/******/ __webpack_require__.r = function(exports) { -/******/ Object.defineProperty(exports, '__esModule', { value: true }); -/******/ }; -/******/ -/******/ // 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 = "dist/"; -/******/ -/******/ var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || []; -/******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); -/******/ jsonpArray.push = webpackJsonpCallback; -/******/ jsonpArray = jsonpArray.slice(); -/******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]); -/******/ var parentJsonpFunction = oldJsonpFunction; -/******/ -/******/ -/******/ // add entry module to deferred list -/******/ deferredModules.push([7,4,5]); -/******/ // run deferred modules when ready -/******/ return checkDeferredModules(); -/******/ }) +/******/ +/******/ const chunkLoadingGlobal = self["webpackChunk"] = self["webpackChunk"] || []; +/******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0)); +/******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal)); +/******/ })(); +/******/ /************************************************************************/ -/******/ ({ +``` + +
+ +``` js +/******/ +/******/ // startup +/******/ // Load entry module and return exports +/******/ // This entry module depends on other loaded chunks and execution need to be delayed +/******/ let __webpack_exports__ = __webpack_require__.O(undefined, ["vendor","commons-utility2_js","commons-utility3_js"], () => (__webpack_require__(4))) +/******/ __webpack_exports__ = __webpack_require__.O(__webpack_exports__); +/******/ +/******/ })() +; +``` + +# dist/pageC.js + +```javascript +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ({ -/***/ 7: +/***/ 7 /*!******************!*\ !*** ./pageC.js ***! \******************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: module, __webpack_require__ */ +/*! CommonJS bailout: module.exports is used directly at 4:0-14 */ +(module, __unused_webpack_exports, __webpack_require__) { var utility2 = __webpack_require__(/*! ./utility2 */ 3); var utility3 = __webpack_require__(/*! ./utility3 */ 6); module.exports = "pageC"; -/***/ }) +/***/ } + +/******/ }); +``` + +
/* webpack runtime code */ + +``` js +/************************************************************************/ +/******/ // The module cache +/******/ const __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ const 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; +/******/ } +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = __webpack_modules__; +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/chunk loaded */ +/******/ (() => { +/******/ const deferred = []; +/******/ __webpack_require__.O = (result, chunkIds, fn, priority) => { +/******/ if(chunkIds) { +/******/ priority = priority || 0; +/******/ for(var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1]; +/******/ deferred[i] = [chunkIds, fn, priority]; +/******/ return; +/******/ } +/******/ let notFulfilled = Infinity; +/******/ for (var i = 0; i < deferred.length; i++) { +/******/ let [chunkIds, fn, priority] = deferred[i]; +/******/ let fulfilled = true; +/******/ for (var j = 0; j < chunkIds.length; j++) { +/******/ if ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every((key) => (__webpack_require__.O[key](chunkIds[j])))) { +/******/ chunkIds.splice(j--, 1); +/******/ } else { +/******/ fulfilled = false; +/******/ if(priority < notFulfilled) notFulfilled = priority; +/******/ } +/******/ } +/******/ if(fulfilled) { +/******/ deferred.splice(i--, 1) +/******/ const r = fn(); +/******/ if (r !== undefined) result = r; +/******/ } +/******/ } +/******/ return result; +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)); +/******/ +/******/ /* webpack/runtime/jsonp chunk loading */ +/******/ (() => { +/******/ // no baseURI +/******/ +/******/ // object to store loaded and loading chunks +/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched +/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded +/******/ const installedChunks = { +/******/ "pageC": 0 +/******/ }; +/******/ +/******/ // no chunk on demand loading +/******/ +/******/ // no prefetching +/******/ +/******/ // no preloaded +/******/ +/******/ // no HMR +/******/ +/******/ // no HMR manifest +/******/ +/******/ __webpack_require__.O.j = (chunkId) => (installedChunks[chunkId] === 0); +/******/ +/******/ // install a JSONP callback for chunk loading +/******/ const webpackJsonpCallback = (parentChunkLoadingFunction, data) => { +/******/ let [chunkIds, moreModules, runtime] = data; +/******/ // add "moreModules" to the modules object, +/******/ // then flag all "chunkIds" as loaded and fire callback +/******/ var moduleId, chunkId, i = 0; +/******/ if(chunkIds.some((id) => (installedChunks[id] !== 0))) { +/******/ for(moduleId in moreModules) { +/******/ if(__webpack_require__.o(moreModules, moduleId)) { +/******/ __webpack_require__.m[moduleId] = moreModules[moduleId]; +/******/ } +/******/ } +/******/ if(runtime) var result = runtime(__webpack_require__); +/******/ } +/******/ if(parentChunkLoadingFunction) parentChunkLoadingFunction(data); +/******/ for(;i < chunkIds.length; i++) { +/******/ chunkId = chunkIds[i]; +/******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) { +/******/ installedChunks[chunkId][0](); +/******/ } +/******/ installedChunks[chunkId] = 0; +/******/ } +/******/ return __webpack_require__.O(result); +/******/ } +/******/ +/******/ const chunkLoadingGlobal = self["webpackChunk"] = self["webpackChunk"] || []; +/******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0)); +/******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal)); +/******/ })(); +/******/ +/************************************************************************/ +``` + +
-/******/ }); +``` js +/******/ +/******/ // startup +/******/ // Load entry module and return exports +/******/ // This entry module depends on other loaded chunks and execution need to be delayed +/******/ let __webpack_exports__ = __webpack_require__.O(undefined, ["commons-utility2_js","commons-utility3_js"], () => (__webpack_require__(7))) +/******/ __webpack_exports__ = __webpack_require__.O(__webpack_exports__); +/******/ +/******/ })() +; ``` # Info @@ -639,97 +657,133 @@ module.exports = "pageC"; ## Unoptimized ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 - Asset Size Chunks Chunk Names - pageA.js 5.72 KiB 0 [emitted] pageA - pageB.js 5.52 KiB 1 [emitted] pageB - pageC.js 5.47 KiB 2 [emitted] pageC - vendor.js 536 bytes 3 [emitted] vendor -commons~pageA~pageB~pageC.js 269 bytes 4 [emitted] commons~pageA~pageB~pageC - commons~pageB~pageC.js 269 bytes 5 [emitted] commons~pageB~pageC -Entrypoint pageA = vendor.js commons~pageA~pageB~pageC.js pageA.js -Entrypoint pageB = vendor.js commons~pageA~pageB~pageC.js commons~pageB~pageC.js pageB.js -Entrypoint pageC = commons~pageA~pageB~pageC.js commons~pageB~pageC.js pageC.js -chunk {0} pageA.js (pageA) 165 bytes ={3}= ={4}= [entry] [rendered] - > ./pageA pageA - [0] ./pageA.js 137 bytes {0} [built] - single entry ./pageA pageA - [2] ./utility1.js 28 bytes {0} [built] - cjs require ./utility1 [0] ./pageA.js 2:15-36 -chunk {1} pageB.js (pageB) 137 bytes ={3}= ={4}= ={5}= [entry] [rendered] - > ./pageB pageB - [4] ./pageB.js 137 bytes {1} [built] - single entry ./pageB pageB -chunk {2} pageC.js (pageC) 105 bytes ={4}= ={5}= [entry] [rendered] - > ./pageC pageC - [7] ./pageC.js 105 bytes {2} [built] - single entry ./pageC pageC -chunk {3} vendor.js (vendor) 54 bytes ={0}= ={1}= ={4}= ={5}= [initial] [rendered] split chunk (cache group: vendor) (name: vendor) - > ./pageA pageA - > ./pageB pageB - 2 modules -chunk {4} commons~pageA~pageB~pageC.js (commons~pageA~pageB~pageC) 28 bytes ={0}= ={1}= ={2}= ={3}= ={5}= [initial] [rendered] split chunk (cache group: commons) (name: commons~pageA~pageB~pageC) - > ./pageA pageA - > ./pageB pageB - > ./pageC pageC - [3] ./utility2.js 28 bytes {4} [built] - cjs require ./utility2 [0] ./pageA.js 3:15-36 - cjs require ./utility2 [4] ./pageB.js 2:15-36 - cjs require ./utility2 [7] ./pageC.js 1:15-36 -chunk {5} commons~pageB~pageC.js (commons~pageB~pageC) 28 bytes ={1}= ={2}= ={3}= ={4}= [initial] [rendered] split chunk (cache group: commons) (name: commons~pageB~pageC) - > ./pageB pageB - > ./pageC pageC - [6] ./utility3.js 28 bytes {5} [built] - cjs require ./utility3 [4] ./pageB.js 3:15-36 - cjs require ./utility3 [7] ./pageC.js 2:15-36 +assets by chunk 744 bytes (id hint: commons) + asset commons-utility2_js.js 372 bytes [emitted] (id hint: commons) + asset commons-utility3_js.js 372 bytes [emitted] (id hint: commons) +asset pageA.js 6.05 KiB [emitted] (name: pageA) +asset pageB.js 5.77 KiB [emitted] (name: pageB) +asset pageC.js 5.71 KiB [emitted] (name: pageC) +asset vendor.js 713 bytes [emitted] (name: vendor) (id hint: vendor) +Entrypoint pageA 7.11 KiB = vendor.js 713 bytes commons-utility2_js.js 372 bytes pageA.js 6.05 KiB +Entrypoint pageB 7.19 KiB = vendor.js 713 bytes commons-utility2_js.js 372 bytes commons-utility3_js.js 372 bytes pageB.js 5.77 KiB +Entrypoint pageC 6.43 KiB = commons-utility2_js.js 372 bytes commons-utility3_js.js 372 bytes pageC.js 5.71 KiB +chunk (runtime: pageA, pageB, pageC) commons-utility2_js.js (id hint: commons) 28 bytes [initial] [rendered] split chunk (cache group: commons) + > ./pageA pageA + > ./pageB pageB + > ./pageC pageC + ./utility2.js 28 bytes [built] [code generated] + [used exports unknown] + cjs require ./utility2 ./pageA.js 3:15-36 + cjs require ./utility2 ./pageB.js 2:15-36 + cjs require ./utility2 ./pageC.js 1:15-36 + cjs self exports reference ./utility2.js 1:0-14 +chunk (runtime: pageB, pageC) commons-utility3_js.js (id hint: commons) 28 bytes [initial] [rendered] split chunk (cache group: commons) + > ./pageB pageB + > ./pageC pageC + ./utility3.js 28 bytes [built] [code generated] + [used exports unknown] + cjs require ./utility3 ./pageB.js 3:15-36 + cjs require ./utility3 ./pageC.js 2:15-36 + cjs self exports reference ./utility3.js 1:0-14 +chunk (runtime: pageA) pageA.js (pageA) 165 bytes (javascript) 2.47 KiB (runtime) [entry] [rendered] + > ./pageA pageA + runtime modules 2.47 KiB 3 modules + dependent modules 28 bytes [dependent] 1 module + ./pageA.js 137 bytes [built] [code generated] + [used exports unknown] + cjs self exports reference ./pageA.js 5:0-14 + entry ./pageA pageA +chunk (runtime: pageB) pageB.js (pageB) 137 bytes (javascript) 2.47 KiB (runtime) [entry] [rendered] + > ./pageB pageB + runtime modules 2.47 KiB 3 modules + ./pageB.js 137 bytes [built] [code generated] + [used exports unknown] + cjs self exports reference ./pageB.js 5:0-14 + entry ./pageB pageB +chunk (runtime: pageC) pageC.js (pageC) 102 bytes (javascript) 2.47 KiB (runtime) [entry] [rendered] + > ./pageC pageC + runtime modules 2.47 KiB 3 modules + ./pageC.js 102 bytes [built] [code generated] + [used exports unknown] + cjs self exports reference ./pageC.js 4:0-14 + entry ./pageC pageC +chunk (runtime: pageA, pageB) vendor.js (vendor) (id hint: vendor) 54 bytes [initial] [rendered] split chunk (cache group: vendor) (name: vendor) + > ./pageA pageA + > ./pageB pageB + ./node_modules/vendor1.js 27 bytes [built] [code generated] + [used exports unknown] + cjs self exports reference ./node_modules/vendor1.js 1:0-14 + cjs require vendor1 ./pageA.js 1:14-32 + ./node_modules/vendor2.js 27 bytes [built] [code generated] + [used exports unknown] + cjs self exports reference ./node_modules/vendor2.js 1:0-14 + cjs require vendor2 ./pageB.js 1:14-32 +webpack X.X.X compiled successfully ``` ## Production mode ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 - Asset Size Chunks Chunk Names -commons~pageA~pageB~pageC.js 96 bytes 0 [emitted] commons~pageA~pageB~pageC - commons~pageB~pageC.js 97 bytes 1 [emitted] commons~pageB~pageC - vendor.js 134 bytes 2 [emitted] vendor - pageC.js 1.1 KiB 3 [emitted] pageC - pageB.js 1.11 KiB 4 [emitted] pageB - pageA.js 1.15 KiB 5 [emitted] pageA -Entrypoint pageA = vendor.js commons~pageA~pageB~pageC.js pageA.js -Entrypoint pageB = vendor.js commons~pageA~pageB~pageC.js commons~pageB~pageC.js pageB.js -Entrypoint pageC = commons~pageA~pageB~pageC.js commons~pageB~pageC.js pageC.js -chunk {0} commons~pageA~pageB~pageC.js (commons~pageA~pageB~pageC) 28 bytes ={1}= ={2}= ={3}= ={4}= ={5}= [initial] [rendered] split chunk (cache group: commons) (name: commons~pageA~pageB~pageC) - > ./pageA pageA - > ./pageB pageB - > ./pageC pageC - [0] ./utility2.js 28 bytes {0} [built] - cjs require ./utility2 [2] ./pageC.js 1:15-36 - cjs require ./utility2 [4] ./pageB.js 2:15-36 - cjs require ./utility2 [7] ./pageA.js 3:15-36 -chunk {1} commons~pageB~pageC.js (commons~pageB~pageC) 28 bytes ={0}= ={2}= ={3}= ={4}= [initial] [rendered] split chunk (cache group: commons) (name: commons~pageB~pageC) - > ./pageB pageB - > ./pageC pageC - [1] ./utility3.js 28 bytes {1} [built] - cjs require ./utility3 [2] ./pageC.js 2:15-36 - cjs require ./utility3 [4] ./pageB.js 3:15-36 -chunk {2} vendor.js (vendor) 54 bytes ={0}= ={1}= ={4}= ={5}= [initial] [rendered] split chunk (cache group: vendor) (name: vendor) - > ./pageA pageA - > ./pageB pageB - 2 modules -chunk {3} pageC.js (pageC) 105 bytes ={0}= ={1}= [entry] [rendered] - > ./pageC pageC - [2] ./pageC.js 105 bytes {3} [built] - single entry ./pageC pageC -chunk {4} pageB.js (pageB) 137 bytes ={0}= ={1}= ={2}= [entry] [rendered] - > ./pageB pageB - [4] ./pageB.js 137 bytes {4} [built] - single entry ./pageB pageB -chunk {5} pageA.js (pageA) 165 bytes ={0}= ={2}= [entry] [rendered] - > ./pageA pageA - [5] ./utility1.js 28 bytes {5} [built] - cjs require ./utility1 [7] ./pageA.js 2:15-36 - [7] ./pageA.js 137 bytes {5} [built] - single entry ./pageA pageA +assets by chunk 210 bytes (id hint: commons) + asset commons-utility2_js.js 105 bytes [emitted] [minimized] (id hint: commons) + asset commons-utility3_js.js 105 bytes [emitted] [minimized] (id hint: commons) +asset pageA.js 1.04 KiB [emitted] [minimized] (name: pageA) +asset pageB.js 1.04 KiB [emitted] [minimized] (name: pageB) +asset pageC.js 1.02 KiB [emitted] [minimized] (name: pageC) +asset vendor.js 119 bytes [emitted] [minimized] (name: vendor) (id hint: vendor) +Entrypoint pageA 1.26 KiB = vendor.js 119 bytes commons-utility2_js.js 105 bytes pageA.js 1.04 KiB +Entrypoint pageB 1.36 KiB = vendor.js 119 bytes commons-utility2_js.js 105 bytes commons-utility3_js.js 105 bytes pageB.js 1.04 KiB +Entrypoint pageC 1.23 KiB = commons-utility2_js.js 105 bytes commons-utility3_js.js 105 bytes pageC.js 1.02 KiB +chunk (runtime: pageA, pageB, pageC) commons-utility2_js.js (id hint: commons) 28 bytes [initial] [rendered] split chunk (cache group: commons) + > ./pageA pageA + > ./pageB pageB + > ./pageC pageC + ./utility2.js 28 bytes [built] [code generated] + [used exports unknown] + cjs require ./utility2 ./pageA.js 3:15-36 + cjs require ./utility2 ./pageB.js 2:15-36 + cjs require ./utility2 ./pageC.js 1:15-36 + cjs self exports reference ./utility2.js 1:0-14 +chunk (runtime: pageB, pageC) commons-utility3_js.js (id hint: commons) 28 bytes [initial] [rendered] split chunk (cache group: commons) + > ./pageB pageB + > ./pageC pageC + ./utility3.js 28 bytes [built] [code generated] + [used exports unknown] + cjs require ./utility3 ./pageB.js 3:15-36 + cjs require ./utility3 ./pageC.js 2:15-36 + cjs self exports reference ./utility3.js 1:0-14 +chunk (runtime: pageA) pageA.js (pageA) 165 bytes (javascript) 2.47 KiB (runtime) [entry] [rendered] + > ./pageA pageA + runtime modules 2.47 KiB 3 modules + dependent modules 28 bytes [dependent] 1 module + ./pageA.js 137 bytes [built] [code generated] + [used exports unknown] + cjs self exports reference ./pageA.js 5:0-14 + entry ./pageA pageA +chunk (runtime: pageB) pageB.js (pageB) 137 bytes (javascript) 2.47 KiB (runtime) [entry] [rendered] + > ./pageB pageB + runtime modules 2.47 KiB 3 modules + ./pageB.js 137 bytes [built] [code generated] + [used exports unknown] + cjs self exports reference ./pageB.js 5:0-14 + entry ./pageB pageB +chunk (runtime: pageC) pageC.js (pageC) 102 bytes (javascript) 2.47 KiB (runtime) [entry] [rendered] + > ./pageC pageC + runtime modules 2.47 KiB 3 modules + ./pageC.js 102 bytes [built] [code generated] + [used exports unknown] + cjs self exports reference ./pageC.js 4:0-14 + entry ./pageC pageC +chunk (runtime: pageA, pageB) vendor.js (vendor) (id hint: vendor) 54 bytes [initial] [rendered] split chunk (cache group: vendor) (name: vendor) + > ./pageA pageA + > ./pageB pageB + ./node_modules/vendor1.js 27 bytes [built] [code generated] + [used exports unknown] + cjs self exports reference ./node_modules/vendor1.js 1:0-14 + cjs require vendor1 ./pageA.js 1:14-32 + ./node_modules/vendor2.js 27 bytes [built] [code generated] + [used exports unknown] + cjs self exports reference ./node_modules/vendor2.js 1:0-14 + cjs require vendor2 ./pageB.js 1:14-32 +webpack X.X.X compiled successfully ``` diff --git a/examples/common-chunk-and-vendor-chunk/template.md b/examples/common-chunk-and-vendor-chunk/template.md index bc7f590c76a..64de9808254 100644 --- a/examples/common-chunk-and-vendor-chunk/template.md +++ b/examples/common-chunk-and-vendor-chunk/template.md @@ -33,44 +33,44 @@ With this bundle configuration, you would load your third party libraries, then # webpack.config.js -``` javascript -{{webpack.config.js}} +```javascript +_{{webpack.config.js}}_ ``` # dist/vendor.js -``` javascript -{{dist/vendor.js}} +```javascript +_{{dist/vendor.js}}_ ``` -# dist/commons~pageA~pageB~pageC.js +# dist/commons-utility2_js.js ``` javascript -{{dist/commons~pageA~pageB~pageC.js}} +_{{dist/commons-utility2_js.js}}_ ``` -# dist/commons~pageB~pageC.js +# dist/commons-utility3_js.js ``` javascript -{{dist/commons~pageB~pageC.js}} +_{{dist/commons-utility3_js.js}}_ ``` # dist/pageA.js -``` javascript -{{dist/pageA.js}} +```javascript +_{{dist/pageA.js}}_ ``` # dist/pageB.js -``` javascript -{{dist/pageB.js}} +```javascript +_{{dist/pageB.js}}_ ``` # dist/pageC.js -``` javascript -{{dist/pageC.js}} +```javascript +_{{dist/pageC.js}}_ ``` # Info @@ -78,11 +78,11 @@ With this bundle configuration, you would load your third party libraries, then ## Unoptimized ``` -{{stdout}} +_{{stdout}}_ ``` ## Production mode ``` -{{production:stdout}} +_{{production:stdout}}_ ``` diff --git a/examples/common-chunk-and-vendor-chunk/webpack.config.js b/examples/common-chunk-and-vendor-chunk/webpack.config.js index 2cdc9599bf9..1f967f9345f 100644 --- a/examples/common-chunk-and-vendor-chunk/webpack.config.js +++ b/examples/common-chunk-and-vendor-chunk/webpack.config.js @@ -1,6 +1,9 @@ -var path = require("path"); +"use strict"; -module.exports = { +const path = require("path"); + +/** @type {import("webpack").Configuration} */ +const config = { // mode: "development" || "production", entry: { pageA: "./pageA", @@ -8,6 +11,7 @@ module.exports = { pageC: "./pageC" }, optimization: { + chunkIds: "named", splitChunks: { cacheGroups: { commons: { @@ -31,3 +35,5 @@ module.exports = { filename: "[name].js" } }; + +module.exports = config; diff --git a/examples/common-chunk-grandchildren/README.md b/examples/common-chunk-grandchildren/README.md index 9ce9e748da9..141591c84e5 100644 --- a/examples/common-chunk-grandchildren/README.md +++ b/examples/common-chunk-grandchildren/README.md @@ -1,28 +1,27 @@ This example illustrates how common modules from deep ancestors of an entry point can be split into a separate common chunk -* `pageA` and `pageB` are dynamically required -* `pageC` and `pageA` both require the `reusableComponent` -* `pageB` dynamically requires `PageC` +- `pageA` and `pageB` are dynamically required +- `pageC` and `pageA` both require the `reusableComponent` +- `pageB` dynamically requires `PageC` You can see that webpack outputs five files/chunks: -* `output.js` is the entry chunk and contains - * the module system - * chunk loading logic - * the entry point `example.js` -* `0.output.js` is an additional chunk - * module `reusableComponent` -* `1.output.js` is an additional chunk - * module `pageB` -* `2.output.js` is an additional chunk - * module `pageA` -* `3.output.js` is an additional chunk - * module `pageC` - +- `output.js` is the entry chunk and contains + - the module system + - chunk loading logic + - the entry point `example.js` +- `0.output.js` is an additional chunk + - module `reusableComponent` +- `1.output.js` is an additional chunk + - module `pageB` +- `2.output.js` is an additional chunk + - module `pageA` +- `3.output.js` is an additional chunk + - module `pageC` # example.js -``` javascript +```javascript var main = function() { console.log("Main class"); require.ensure([], () => { @@ -40,7 +39,7 @@ main(); # pageA.js -``` javascript +```javascript var reusableComponent = require("./reusableComponent"); module.exports = function() { @@ -51,7 +50,7 @@ module.exports = function() { # pageB.js -``` javascript +```javascript module.exports = function() { console.log("Page B"); require.ensure([], ()=>{ @@ -63,7 +62,7 @@ module.exports = function() { # pageC.js -``` javascript +```javascript var reusableComponent = require("./reusableComponent"); module.exports = function() { @@ -74,7 +73,7 @@ module.exports = function() { # reusableComponent.js -``` javascript +```javascript module.exports = function() { console.log("reusable Component"); }; @@ -82,12 +81,14 @@ module.exports = function() { # webpack.config.js -``` javascript +```javascript "use strict"; + const path = require("path"); -module.exports = { - // mode: "development || "production", +/** @type {import("webpack").Configuration} */ +const config = { + // mode: "development" || "production", entry: { main: ["./example.js"] }, @@ -95,338 +96,346 @@ module.exports = { splitChunks: { minSize: 0 // This example is too small, in practice you can use the defaults }, - occurrenceOrder: true // To keep filename consistent between different modes (for example building only) + chunkIds: "named" // To keep filename consistent between different modes (for example building only) }, output: { path: path.resolve(__dirname, "dist"), filename: "output.js" } }; + +module.exports = config; ``` # dist/output.js -
/******/ (function(modules) { /* webpackBootstrap */ }) - -``` javascript -/******/ (function(modules) { // webpackBootstrap -/******/ // install a JSONP callback for chunk loading -/******/ function webpackJsonpCallback(data) { -/******/ var chunkIds = data[0]; -/******/ var moreModules = data[1]; -/******/ -/******/ // add "moreModules" to the modules object, -/******/ // then flag all "chunkIds" as loaded and fire callback -/******/ var moduleId, chunkId, i = 0, resolves = []; -/******/ for(;i < chunkIds.length; i++) { -/******/ chunkId = chunkIds[i]; -/******/ if(installedChunks[chunkId]) { -/******/ resolves.push(installedChunks[chunkId][0]); -/******/ } -/******/ installedChunks[chunkId] = 0; -/******/ } -/******/ for(moduleId in moreModules) { -/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { -/******/ modules[moduleId] = moreModules[moduleId]; -/******/ } -/******/ } -/******/ if(parentJsonpFunction) parentJsonpFunction(data); -/******/ while(resolves.length) { -/******/ resolves.shift()(); -/******/ } -/******/ -/******/ }; -/******/ -/******/ +```javascript +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ({}); +``` + +
/* webpack runtime code */ + +``` js +/************************************************************************/ /******/ // The module cache -/******/ var installedModules = {}; -/******/ -/******/ // object to store loaded and loading chunks -/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched -/******/ // Promise = chunk loading, 0 = chunk loaded -/******/ var installedChunks = { -/******/ 4: 0 -/******/ }; -/******/ -/******/ -/******/ -/******/ // script path function -/******/ function jsonpScriptSrc(chunkId) { -/******/ return __webpack_require__.p + "" + chunkId + ".output.js" -/******/ } -/******/ +/******/ const __webpack_module_cache__ = {}; +/******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { -/******/ /******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) { -/******/ return installedModules[moduleId].exports; +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, +/******/ const module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed /******/ exports: {} /******/ }; -/******/ +/******/ /******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } -/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = __webpack_modules__; +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/ensure chunk */ +/******/ __webpack_require__.f = {}; /******/ // This file contains only the entry chunk. /******/ // The chunk loading function for additional chunks -/******/ __webpack_require__.e = function requireEnsure(chunkId) { -/******/ var promises = []; -/******/ -/******/ -/******/ // JSONP chunk loading for javascript -/******/ -/******/ var installedChunkData = installedChunks[chunkId]; -/******/ if(installedChunkData !== 0) { // 0 means "already installed". -/******/ -/******/ // a Promise means "currently loading". -/******/ if(installedChunkData) { -/******/ promises.push(installedChunkData[2]); -/******/ } else { -/******/ // setup Promise in chunk cache -/******/ var promise = new Promise(function(resolve, reject) { -/******/ installedChunkData = installedChunks[chunkId] = [resolve, reject]; -/******/ }); -/******/ promises.push(installedChunkData[2] = promise); -/******/ -/******/ // start chunk loading -/******/ var head = document.getElementsByTagName('head')[0]; -/******/ var script = document.createElement('script'); -/******/ +/******/ __webpack_require__.e = (chunkId) => { +/******/ return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => { +/******/ __webpack_require__.f[key](chunkId, promises); +/******/ return promises; +/******/ }, [])); +/******/ }; +/******/ +/******/ /* webpack/runtime/get javascript chunk filename */ +/******/ // This function allow to reference async chunks +/******/ __webpack_require__.u = (chunkId) => { +/******/ // return url for filenames based on template +/******/ return "" + chunkId + ".output.js"; +/******/ }; +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)); +/******/ +/******/ /* webpack/runtime/load script */ +/******/ (() => { +/******/ const inProgress = {}; +/******/ // data-webpack is not used as build has no uniqueName +/******/ // loadScript function to load a script via script tag +/******/ __webpack_require__.l = (url, done, key, chunkId) => { +/******/ if(inProgress[url]) { inProgress[url].push(done); return; } +/******/ let script, needAttach; +/******/ if(key !== undefined) { +/******/ const scripts = document.getElementsByTagName("script"); +/******/ for(var i = 0; i < scripts.length; i++) { +/******/ const s = scripts[i]; +/******/ if(s.getAttribute("src") == url) { script = s; break; } +/******/ } +/******/ } +/******/ if(!script) { +/******/ needAttach = true; +/******/ script = document.createElement('script'); +/******/ /******/ script.charset = 'utf-8'; -/******/ script.timeout = 120; -/******/ /******/ if (__webpack_require__.nc) { /******/ script.setAttribute("nonce", __webpack_require__.nc); /******/ } -/******/ script.src = jsonpScriptSrc(chunkId); -/******/ var timeout = setTimeout(function(){ -/******/ onScriptComplete({ type: 'timeout', target: script }); -/******/ }, 120000); -/******/ script.onerror = script.onload = onScriptComplete; -/******/ function onScriptComplete(event) { -/******/ // avoid mem leaks in IE. -/******/ script.onerror = script.onload = null; -/******/ clearTimeout(timeout); -/******/ var chunk = installedChunks[chunkId]; -/******/ if(chunk !== 0) { -/******/ if(chunk) { -/******/ var errorType = event && (event.type === 'load' ? 'missing' : event.type); -/******/ var realSrc = event && event.target && event.target.src; -/******/ var error = new Error('Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'); -/******/ error.type = errorType; -/******/ error.request = realSrc; -/******/ chunk[1](error); +/******/ +/******/ +/******/ script.src = url; +/******/ } +/******/ inProgress[url] = [done]; +/******/ const onScriptComplete = (prev, event) => { +/******/ // avoid mem leaks in IE. +/******/ script.onerror = script.onload = null; +/******/ clearTimeout(timeout); +/******/ const doneFns = inProgress[url]; +/******/ delete inProgress[url]; +/******/ script.parentNode?.removeChild(script); +/******/ doneFns?.forEach((fn) => (fn(event))); +/******/ if(prev) return prev(event); +/******/ } +/******/ const timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000); +/******/ script.onerror = onScriptComplete.bind(null, script.onerror); +/******/ script.onload = onScriptComplete.bind(null, script.onload); +/******/ needAttach && document.head.appendChild(script); +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/publicPath */ +/******/ __webpack_require__.p = "dist/"; +/******/ +/******/ /* webpack/runtime/jsonp chunk loading */ +/******/ (() => { +/******/ // no baseURI +/******/ +/******/ // object to store loaded and loading chunks +/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched +/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded +/******/ const installedChunks = { +/******/ "main": 0 +/******/ }; +/******/ +/******/ __webpack_require__.f.j = (chunkId, promises) => { +/******/ // JSONP chunk loading for javascript +/******/ let installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined; +/******/ if(installedChunkData !== 0) { // 0 means "already installed". +/******/ +/******/ // a Promise means "currently loading". +/******/ if(installedChunkData) { +/******/ promises.push(installedChunkData[2]); +/******/ } else { +/******/ if(true) { // all chunks have JS +/******/ // setup Promise in chunk cache +/******/ const promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject])); +/******/ promises.push(installedChunkData[2] = promise); +/******/ +/******/ // start chunk loading +/******/ const url = __webpack_require__.p + __webpack_require__.u(chunkId); +/******/ // create error before stack unwound to get useful stacktrace later +/******/ const error = new Error(); +/******/ const loadingEnded = (event) => { +/******/ if(__webpack_require__.o(installedChunks, chunkId)) { +/******/ installedChunkData = installedChunks[chunkId]; +/******/ if(installedChunkData !== 0) installedChunks[chunkId] = undefined; +/******/ if(installedChunkData) { +/******/ const errorType = event && (event.type === 'load' ? 'missing' : event.type); +/******/ const realSrc = event && event.target && event.target.src; +/******/ error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'; +/******/ error.name = 'ChunkLoadError'; +/******/ error.type = errorType; +/******/ error.request = realSrc; +/******/ error.event = event; +/******/ installedChunkData[1](error); +/******/ } +/******/ } +/******/ }; +/******/ __webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId); /******/ } -/******/ installedChunks[chunkId] = undefined; /******/ } -/******/ }; -/******/ head.appendChild(script); +/******/ } +/******/ }; +/******/ +/******/ // no prefetching +/******/ +/******/ // no preloaded +/******/ +/******/ // no HMR +/******/ +/******/ // no HMR manifest +/******/ +/******/ // no on chunks loaded +/******/ +/******/ // install a JSONP callback for chunk loading +/******/ const webpackJsonpCallback = (parentChunkLoadingFunction, data) => { +/******/ let [chunkIds, moreModules, runtime] = data; +/******/ // add "moreModules" to the modules object, +/******/ // then flag all "chunkIds" as loaded and fire callback +/******/ var moduleId, chunkId, i = 0; +/******/ if(chunkIds.some((id) => (installedChunks[id] !== 0))) { +/******/ for(moduleId in moreModules) { +/******/ if(__webpack_require__.o(moreModules, moduleId)) { +/******/ __webpack_require__.m[moduleId] = moreModules[moduleId]; +/******/ } +/******/ } +/******/ if(runtime) var result = runtime(__webpack_require__); /******/ } +/******/ if(parentChunkLoadingFunction) parentChunkLoadingFunction(data); +/******/ for(;i < chunkIds.length; i++) { +/******/ chunkId = chunkIds[i]; +/******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) { +/******/ installedChunks[chunkId][0](); +/******/ } +/******/ installedChunks[chunkId] = 0; +/******/ } +/******/ /******/ } -/******/ return Promise.all(promises); -/******/ }; -/******/ -/******/ // 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, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); -/******/ } -/******/ }; -/******/ -/******/ // define __esModule on exports -/******/ __webpack_require__.r = function(exports) { -/******/ Object.defineProperty(exports, '__esModule', { value: true }); -/******/ }; -/******/ -/******/ // 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 = "dist/"; -/******/ -/******/ // on error function for async loading -/******/ __webpack_require__.oe = function(err) { console.error(err); throw err; }; -/******/ -/******/ var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || []; -/******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); -/******/ jsonpArray.push = webpackJsonpCallback; -/******/ jsonpArray = jsonpArray.slice(); -/******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]); -/******/ var parentJsonpFunction = oldJsonpFunction; -/******/ -/******/ -/******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 1); -/******/ }) +/******/ +/******/ const chunkLoadingGlobal = self["webpackChunk"] = self["webpackChunk"] || []; +/******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0)); +/******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal)); +/******/ })(); +/******/ /************************************************************************/ ```
-``` javascript -/******/ ([ -/* 0 */ +``` js /*!********************!*\ !*** ./example.js ***! \********************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: __webpack_require__, __webpack_require__.e, __webpack_require__.* */ var main = function() { console.log("Main class"); - Promise.all(/*! require.ensure */[__webpack_require__.e(0), __webpack_require__.e(2)]).then((() => { - const page = __webpack_require__(/*! ./pageA */ 3); + Promise.all(/*! require.ensure */[__webpack_require__.e("reusableComponent_js"), __webpack_require__.e("pageA_js")]).then((() => { + const page = __webpack_require__(/*! ./pageA */ 1); page(); - }).bind(null, __webpack_require__)).catch(__webpack_require__.oe); - __webpack_require__.e(/*! require.ensure */ 1).then((() => { - const page = __webpack_require__(/*! ./pageB */ 2); + }).bind(null, __webpack_require__))['catch'](__webpack_require__.oe); + __webpack_require__.e(/*! require.ensure */ "pageB_js").then((() => { + const page = __webpack_require__(/*! ./pageB */ 3); page(); - }).bind(null, __webpack_require__)).catch(__webpack_require__.oe); + }).bind(null, __webpack_require__))['catch'](__webpack_require__.oe); }; main(); - -/***/ }), -/* 1 */ -/*!**************************!*\ - !*** multi ./example.js ***! - \**************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -module.exports = __webpack_require__(/*! ./example.js */0); - - -/***/ }) -/******/ ]); +/******/ })() +; ``` -# dist/0.output.js +# dist/pageA_js.output.js -``` javascript -(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0],{ +```javascript +(self["webpackChunk"] = self["webpackChunk"] || []).push([["pageA_js"],[ +/* 0 */, +/* 1 */ +/*!******************!*\ + !*** ./pageA.js ***! + \******************/ +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: module, __webpack_require__ */ +/*! CommonJS bailout: module.exports is used directly at 3:0-14 */ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { -/***/ 4: -/*!******************************!*\ - !*** ./reusableComponent.js ***! - \******************************/ -/*! no static exports found */ -/***/ (function(module, exports) { +var reusableComponent = __webpack_require__(/*! ./reusableComponent */ 2); module.exports = function() { - console.log("reusable Component"); + console.log("Page A"); + reusableComponent(); }; /***/ }) - -}]); +]]); ``` -# dist/1.output.js +# dist/pageB_js.output.js -``` javascript -(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[1],{ +```javascript +(self["webpackChunk"] = self["webpackChunk"] || []).push([["pageB_js"],{ -/***/ 2: +/***/ 3 /*!******************!*\ !*** ./pageB.js ***! \******************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: module, __webpack_require__, __webpack_require__.e, __webpack_require__.* */ +/*! CommonJS bailout: module.exports is used directly at 1:0-14 */ +(module, __unused_webpack_exports, __webpack_require__) { module.exports = function() { console.log("Page B"); - Promise.all(/*! require.ensure */[__webpack_require__.e(0), __webpack_require__.e(3)]).then((()=>{ - const page = __webpack_require__(/*! ./pageC */ 5); + Promise.all(/*! require.ensure */[__webpack_require__.e("reusableComponent_js"), __webpack_require__.e("pageC_js")]).then((()=>{ + const page = __webpack_require__(/*! ./pageC */ 4); page(); - }).bind(null, __webpack_require__)).catch(__webpack_require__.oe); + }).bind(null, __webpack_require__))['catch'](__webpack_require__.oe); }; -/***/ }) +/***/ } }]); ``` -# dist/2.output.js +# dist/pageC_js.output.js -``` javascript -(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[2],{ +```javascript +(self["webpackChunk"] = self["webpackChunk"] || []).push([["pageC_js"],{ -/***/ 3: +/***/ 4 /*!******************!*\ - !*** ./pageA.js ***! + !*** ./pageC.js ***! \******************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: module, __webpack_require__ */ +/*! CommonJS bailout: module.exports is used directly at 3:0-14 */ +(module, __unused_webpack_exports, __webpack_require__) { -var reusableComponent = __webpack_require__(/*! ./reusableComponent */ 4); +var reusableComponent = __webpack_require__(/*! ./reusableComponent */ 2); module.exports = function() { - console.log("Page A"); + console.log("Page C"); reusableComponent(); }; -/***/ }) +/***/ } }]); ``` -# dist/3.output.js +# dist/reusableComponent_js.output.js -``` javascript -(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[3],{ - -/***/ 5: -/*!******************!*\ - !*** ./pageC.js ***! - \******************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +```javascript +(self["webpackChunk"] = self["webpackChunk"] || []).push([["reusableComponent_js"],{ -var reusableComponent = __webpack_require__(/*! ./reusableComponent */ 4); +/***/ 2 +/*!******************************!*\ + !*** ./reusableComponent.js ***! + \******************************/ +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: module */ +/*! CommonJS bailout: module.exports is used directly at 1:0-14 */ +(module) { module.exports = function() { - console.log("Page C"); - reusableComponent(); + console.log("reusable Component"); }; -/***/ }) +/***/ } }]); ``` @@ -436,75 +445,85 @@ module.exports = function() { ## Unoptimized ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 - Asset Size Chunks Chunk Names -0.output.js 340 bytes 0 [emitted] -1.output.js 549 bytes 1 [emitted] -2.output.js 414 bytes 2 [emitted] -3.output.js 414 bytes 3 [emitted] - output.js 7.79 KiB 4 [emitted] main -Entrypoint main = output.js -chunk {0} 0.output.js 72 bytes <{1}> <{4}> ={2}= ={3}= [rendered] split chunk (cache group: default) - > [0] ./example.js 3:1-6:3 - > [2] ./pageB.js 3:1-6:3 - [4] ./reusableComponent.js 72 bytes {0} [built] - cjs require ./reusableComponent [3] ./pageA.js 1:24-54 - cjs require ./reusableComponent [5] ./pageC.js 1:24-54 -chunk {1} 1.output.js 140 bytes <{4}> >{0}< >{3}< [rendered] - > [0] ./example.js 7:1-10:3 - [2] ./pageB.js 140 bytes {1} [built] - cjs require ./pageB [0] ./example.js 8:15-33 -chunk {2} 2.output.js 142 bytes <{4}> ={0}= [rendered] - > [0] ./example.js 3:1-6:3 - [3] ./pageA.js 142 bytes {2} [built] - cjs require ./pageA [0] ./example.js 4:15-33 -chunk {3} 3.output.js 142 bytes <{1}> ={0}= [rendered] - > [2] ./pageB.js 3:1-6:3 - [5] ./pageC.js 142 bytes {3} [built] - cjs require ./pageC [2] ./pageB.js 4:15-33 -chunk {4} output.js (main) 261 bytes >{0}< >{1}< >{2}< [entry] [rendered] - > main - [0] ./example.js 233 bytes {4} [built] - single entry ./example.js [1] multi ./example.js main:100000 - [1] multi ./example.js 28 bytes {4} [built] - multi entry +asset output.js 8.94 KiB [emitted] (name: main) +asset pageB_js.output.js 760 bytes [emitted] +asset pageA_js.output.js 565 bytes [emitted] +asset pageC_js.output.js 547 bytes [emitted] +asset reusableComponent_js.output.js 441 bytes [emitted] +chunk (runtime: main) output.js (main) 220 bytes (javascript) 4.95 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 4.95 KiB 6 modules + ./example.js 220 bytes [built] [code generated] + [used exports unknown] + entry ./example.js main +chunk (runtime: main) pageA_js.output.js 136 bytes [rendered] + > ./example.js 3:1-6:3 + ./pageA.js 136 bytes [built] [code generated] + [used exports unknown] + cjs require ./pageA ./example.js 4:15-33 + cjs self exports reference ./pageA.js 3:0-14 +chunk (runtime: main) pageB_js.output.js 133 bytes [rendered] + > ./example.js 7:1-10:3 + ./pageB.js 133 bytes [built] [code generated] + [used exports unknown] + cjs require ./pageB ./example.js 8:15-33 + cjs self exports reference ./pageB.js 1:0-14 +chunk (runtime: main) pageC_js.output.js 136 bytes [rendered] + > ./pageB.js 3:1-6:3 + ./pageC.js 136 bytes [built] [code generated] + [used exports unknown] + cjs require ./pageC ./pageB.js 4:15-33 + cjs self exports reference ./pageC.js 3:0-14 +chunk (runtime: main) reusableComponent_js.output.js 69 bytes [rendered] split chunk (cache group: default) + > ./example.js 3:1-6:3 + > ./pageB.js 3:1-6:3 + ./reusableComponent.js 69 bytes [built] [code generated] + [used exports unknown] + cjs require ./reusableComponent ./pageA.js 1:24-54 + cjs require ./reusableComponent ./pageC.js 1:24-54 + cjs self exports reference ./reusableComponent.js 1:0-14 +webpack X.X.X compiled successfully ``` ## Production mode ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 - Asset Size Chunks Chunk Names -0.output.js 133 bytes 0 [emitted] -1.output.js 198 bytes 1 [emitted] -2.output.js 138 bytes 2 [emitted] -3.output.js 138 bytes 3 [emitted] - output.js 1.78 KiB 4 [emitted] main -Entrypoint main = output.js -chunk {0} 0.output.js 72 bytes <{1}> <{4}> ={2}= ={3}= [rendered] split chunk (cache group: default) - > [0] ./example.js 3:1-6:3 - > [2] ./pageB.js 3:1-6:3 - [4] ./reusableComponent.js 72 bytes {0} [built] - cjs require ./reusableComponent [3] ./pageA.js 1:24-54 - cjs require ./reusableComponent [5] ./pageC.js 1:24-54 -chunk {1} 1.output.js 140 bytes <{4}> >{0}< >{3}< [rendered] - > [0] ./example.js 7:1-10:3 - [2] ./pageB.js 140 bytes {1} [built] - cjs require ./pageB [0] ./example.js 8:15-33 -chunk {2} 2.output.js 142 bytes <{4}> ={0}= [rendered] - > [0] ./example.js 3:1-6:3 - [3] ./pageA.js 142 bytes {2} [built] - cjs require ./pageA [0] ./example.js 4:15-33 -chunk {3} 3.output.js 142 bytes <{1}> ={0}= [rendered] - > [2] ./pageB.js 3:1-6:3 - [5] ./pageC.js 142 bytes {3} [built] - cjs require ./pageC [2] ./pageB.js 4:15-33 -chunk {4} output.js (main) 261 bytes >{0}< >{1}< >{2}< [entry] [rendered] - > main - [0] ./example.js 233 bytes {4} [built] - single entry ./example.js [1] multi ./example.js main:100000 - [1] multi ./example.js 28 bytes {4} [built] - multi entry +asset output.js 1.86 KiB [emitted] [minimized] (name: main) +asset pageB_js.output.js 228 bytes [emitted] [minimized] +asset reusableComponent_js.output.js 141 bytes [emitted] [minimized] +asset pageC_js.output.js 138 bytes [emitted] [minimized] +asset pageA_js.output.js 137 bytes [emitted] [minimized] +chunk (runtime: main) output.js (main) 220 bytes (javascript) 4.95 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 4.95 KiB 6 modules + ./example.js 220 bytes [built] [code generated] + [no exports used] + entry ./example.js main +chunk (runtime: main) pageA_js.output.js 136 bytes [rendered] + > ./example.js 3:1-6:3 + ./pageA.js 136 bytes [built] [code generated] + [used exports unknown] + cjs require ./pageA ./example.js 4:15-33 + cjs self exports reference ./pageA.js 3:0-14 +chunk (runtime: main) pageB_js.output.js 133 bytes [rendered] + > ./example.js 7:1-10:3 + ./pageB.js 133 bytes [built] [code generated] + [used exports unknown] + cjs require ./pageB ./example.js 8:15-33 + cjs self exports reference ./pageB.js 1:0-14 +chunk (runtime: main) pageC_js.output.js 136 bytes [rendered] + > ./pageB.js 3:1-6:3 + ./pageC.js 136 bytes [built] [code generated] + [used exports unknown] + cjs require ./pageC ./pageB.js 4:15-33 + cjs self exports reference ./pageC.js 3:0-14 +chunk (runtime: main) reusableComponent_js.output.js 69 bytes [rendered] split chunk (cache group: default) + > ./pageB.js 3:1-6:3 + > ./example.js 3:1-6:3 + ./reusableComponent.js 69 bytes [built] [code generated] + [used exports unknown] + cjs require ./reusableComponent ./pageA.js 1:24-54 + cjs require ./reusableComponent ./pageC.js 1:24-54 + cjs self exports reference ./reusableComponent.js 1:0-14 +webpack X.X.X compiled successfully ``` diff --git a/examples/common-chunk-grandchildren/template.md b/examples/common-chunk-grandchildren/template.md index aa0aaeae9d2..7be73dfc7a9 100644 --- a/examples/common-chunk-grandchildren/template.md +++ b/examples/common-chunk-grandchildren/template.md @@ -1,89 +1,88 @@ This example illustrates how common modules from deep ancestors of an entry point can be split into a separate common chunk -* `pageA` and `pageB` are dynamically required -* `pageC` and `pageA` both require the `reusableComponent` -* `pageB` dynamically requires `PageC` +- `pageA` and `pageB` are dynamically required +- `pageC` and `pageA` both require the `reusableComponent` +- `pageB` dynamically requires `PageC` You can see that webpack outputs five files/chunks: -* `output.js` is the entry chunk and contains - * the module system - * chunk loading logic - * the entry point `example.js` -* `0.output.js` is an additional chunk - * module `reusableComponent` -* `1.output.js` is an additional chunk - * module `pageB` -* `2.output.js` is an additional chunk - * module `pageA` -* `3.output.js` is an additional chunk - * module `pageC` - +- `output.js` is the entry chunk and contains + - the module system + - chunk loading logic + - the entry point `example.js` +- `0.output.js` is an additional chunk + - module `reusableComponent` +- `1.output.js` is an additional chunk + - module `pageB` +- `2.output.js` is an additional chunk + - module `pageA` +- `3.output.js` is an additional chunk + - module `pageC` # example.js -``` javascript -{{example.js}} +```javascript +_{{example.js}}_ ``` # pageA.js -``` javascript -{{pageA.js}} +```javascript +_{{pageA.js}}_ ``` # pageB.js -``` javascript -{{pageB.js}} +```javascript +_{{pageB.js}}_ ``` # pageC.js -``` javascript -{{pageC.js}} +```javascript +_{{pageC.js}}_ ``` # reusableComponent.js -``` javascript -{{reusableComponent.js}} +```javascript +_{{reusableComponent.js}}_ ``` # webpack.config.js -``` javascript -{{webpack.config.js}} +```javascript +_{{webpack.config.js}}_ ``` # dist/output.js -``` javascript -{{dist/output.js}} +```javascript +_{{dist/output.js}}_ ``` -# dist/0.output.js +# dist/pageA_js.output.js -``` javascript -{{dist/0.output.js}} +```javascript +_{{dist/pageA_js.output.js}}_ ``` -# dist/1.output.js +# dist/pageB_js.output.js -``` javascript -{{dist/1.output.js}} +```javascript +_{{dist/pageB_js.output.js}}_ ``` -# dist/2.output.js +# dist/pageC_js.output.js -``` javascript -{{dist/2.output.js}} +```javascript +_{{dist/pageC_js.output.js}}_ ``` -# dist/3.output.js +# dist/reusableComponent_js.output.js -``` javascript -{{dist/3.output.js}} +```javascript +_{{dist/reusableComponent_js.output.js}}_ ``` # Info @@ -91,11 +90,11 @@ You can see that webpack outputs five files/chunks: ## Unoptimized ``` -{{stdout}} +_{{stdout}}_ ``` ## Production mode ``` -{{production:stdout}} +_{{production:stdout}}_ ``` diff --git a/examples/common-chunk-grandchildren/webpack.config.js b/examples/common-chunk-grandchildren/webpack.config.js index 189b94895b8..8b6b6df57a4 100644 --- a/examples/common-chunk-grandchildren/webpack.config.js +++ b/examples/common-chunk-grandchildren/webpack.config.js @@ -1,8 +1,10 @@ "use strict"; + const path = require("path"); -module.exports = { - // mode: "development || "production", +/** @type {import("webpack").Configuration} */ +const config = { + // mode: "development" || "production", entry: { main: ["./example.js"] }, @@ -10,10 +12,12 @@ module.exports = { splitChunks: { minSize: 0 // This example is too small, in practice you can use the defaults }, - occurrenceOrder: true // To keep filename consistent between different modes (for example building only) + chunkIds: "named" // To keep filename consistent between different modes (for example building only) }, output: { path: path.resolve(__dirname, "dist"), filename: "output.js" } }; + +module.exports = config; diff --git a/examples/commonjs/README.md b/examples/commonjs/README.md index fb85a0b1c08..b925f482c4a 100644 --- a/examples/commonjs/README.md +++ b/examples/commonjs/README.md @@ -1,23 +1,23 @@ -This very simple example shows usage of CommonJS. +This is a simple example that shows the usage of CommonJS. The three files `example.js`, `increment.js` and `math.js` form a dependency chain. They use `require(dependency)` to declare dependencies. -You can see the output file that webpack creates by bundling them together in one file. Keep in mind that webpack adds comments to make reading this file easier. These comments are removed when minimizing the file. +You can see the output file that webpack creates by bundling them together in one file. Keep in mind that webpack add comments to make reading this file easier. These comments are removed when minimizing the file. -You can also see the info messages webpack prints to console (for both normal and minimized build). +You can also see the info messages that webpack prints to console (for both normal and minimized build). # example.js -``` javascript -var inc = require('./increment').increment; -var a = 1; +```javascript +const inc = require('./increment').increment; +const a = 1; inc(a); // 2 ``` # increment.js -``` javascript -var add = require('./math').add; +```javascript +const add = require('./math').add; exports.increment = function(val) { return add(val, 1); }; @@ -25,7 +25,7 @@ exports.increment = function(val) { # math.js -``` javascript +```javascript exports.add = function() { var sum = 0, i = 0, args = arguments, l = args.length; while (i < l) { @@ -37,117 +37,36 @@ exports.add = function() { # dist/output.js -
/******/ (function(modules) { /* webpackBootstrap */ }) - -``` javascript -/******/ (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, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); -/******/ } -/******/ }; -/******/ -/******/ // define __esModule on exports -/******/ __webpack_require__.r = function(exports) { -/******/ Object.defineProperty(exports, '__esModule', { value: true }); -/******/ }; -/******/ -/******/ // 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 = "dist/"; -/******/ -/******/ -/******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 0); -/******/ }) -/************************************************************************/ -``` - -
- -``` javascript -/******/ ([ -/* 0 */ -/*!********************!*\ - !*** ./example.js ***! - \********************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -var inc = __webpack_require__(/*! ./increment */ 1).increment; -var a = 1; -inc(a); // 2 - -/***/ }), +```javascript +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ([ +/* 0 */, /* 1 */ /*!**********************!*\ !*** ./increment.js ***! \**********************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! default exports */ +/*! export increment [provided] [no usage info] [missing usage info prevents renaming] */ +/*! other exports [not provided] [no usage info] */ +/*! runtime requirements: __webpack_require__, __webpack_exports__ */ +/***/ ((__unused_webpack_module, exports, __webpack_require__) => { -var add = __webpack_require__(/*! ./math */ 2).add; +const add = (__webpack_require__(/*! ./math */ 2).add); exports.increment = function(val) { return add(val, 1); }; + /***/ }), /* 2 */ /*!*****************!*\ !*** ./math.js ***! \*****************/ -/*! no static exports found */ -/***/ (function(module, exports) { +/*! default exports */ +/*! export add [provided] [no usage info] [missing usage info prevents renaming] */ +/*! other exports [not provided] [no usage info] */ +/*! runtime requirements: __webpack_exports__ */ +/***/ ((__unused_webpack_module, exports) => { exports.add = function() { var sum = 0, i = 0, args = arguments, l = args.length; @@ -158,7 +77,59 @@ exports.add = function() { }; /***/ }) -/******/ ]); +/******/ ]); +``` + +
/* webpack runtime code */ + +``` js +/************************************************************************/ +/******/ // The module cache +/******/ const __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ const 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; +/******/ } +/******/ +/************************************************************************/ +``` + +
+ +``` js +let __webpack_exports__ = {}; +// This entry needs to be wrapped in an IIFE because it needs to be isolated against other modules in the chunk. +(() => { +/*!********************!*\ + !*** ./example.js ***! + \********************/ +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: __webpack_require__ */ +const inc = (__webpack_require__(/*! ./increment */ 1).increment); +const a = 1; +inc(a); // 2 + +})(); + +/******/ })() +; ``` # Info @@ -166,35 +137,25 @@ exports.add = function() { ## Unoptimized ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 - Asset Size Chunks Chunk Names -output.js 3.44 KiB 0 [emitted] main -Entrypoint main = output.js -chunk {0} output.js (main) 329 bytes [entry] [rendered] - > .\example.js main - [0] ./example.js 69 bytes {0} [built] - single entry .\example.js main - [1] ./increment.js 98 bytes {0} [built] - cjs require ./increment [0] ./example.js 1:10-32 - [2] ./math.js 162 bytes {0} [built] - cjs require ./math [1] ./increment.js 1:10-27 +asset output.js 2.53 KiB [emitted] (name: main) +chunk (runtime: main) output.js (main) 326 bytes [entry] [rendered] + > ./example.js main + dependent modules 254 bytes [dependent] 2 modules + ./example.js 72 bytes [built] [code generated] + [used exports unknown] + entry ./example.js main +webpack X.X.X compiled successfully ``` ## Production mode ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 - Asset Size Chunks Chunk Names -output.js 740 bytes 0 [emitted] main -Entrypoint main = output.js -chunk {0} output.js (main) 329 bytes [entry] [rendered] - > .\example.js main - [0] ./math.js 162 bytes {0} [built] - cjs require ./math [1] ./increment.js 1:10-27 - [1] ./increment.js 98 bytes {0} [built] - cjs require ./increment [2] ./example.js 1:10-32 - [2] ./example.js 69 bytes {0} [built] - single entry .\example.js main -``` \ No newline at end of file +asset output.js 314 bytes [emitted] [minimized] (name: main) +chunk (runtime: main) output.js (main) 326 bytes [entry] [rendered] + > ./example.js main + dependent modules 254 bytes [dependent] 2 modules + ./example.js 72 bytes [built] [code generated] + [no exports used] + entry ./example.js main +webpack X.X.X compiled successfully +``` diff --git a/examples/commonjs/example.js b/examples/commonjs/example.js index 5b87a398285..d56b3cb0040 100644 --- a/examples/commonjs/example.js +++ b/examples/commonjs/example.js @@ -1,3 +1,3 @@ -var inc = require('./increment').increment; -var a = 1; -inc(a); // 2 \ No newline at end of file +const inc = require('./increment').increment; +const a = 1; +inc(a); // 2 diff --git a/examples/commonjs/increment.js b/examples/commonjs/increment.js index df19980e68a..5b6497cade5 100644 --- a/examples/commonjs/increment.js +++ b/examples/commonjs/increment.js @@ -1,4 +1,4 @@ -var add = require('./math').add; +const add = require('./math').add; exports.increment = function(val) { return add(val, 1); -}; \ No newline at end of file +}; diff --git a/examples/commonjs/template.md b/examples/commonjs/template.md index 5ed0539fc77..8fa7cf29052 100644 --- a/examples/commonjs/template.md +++ b/examples/commonjs/template.md @@ -1,33 +1,33 @@ -This very simple example shows usage of CommonJS. +This is a simple example that shows the usage of CommonJS. The three files `example.js`, `increment.js` and `math.js` form a dependency chain. They use `require(dependency)` to declare dependencies. -You can see the output file that webpack creates by bundling them together in one file. Keep in mind that webpack adds comments to make reading this file easier. These comments are removed when minimizing the file. +You can see the output file that webpack creates by bundling them together in one file. Keep in mind that webpack add comments to make reading this file easier. These comments are removed when minimizing the file. -You can also see the info messages webpack prints to console (for both normal and minimized build). +You can also see the info messages that webpack prints to console (for both normal and minimized build). # example.js -``` javascript -{{example.js}} +```javascript +_{{example.js}}_ ``` # increment.js -``` javascript -{{increment.js}} +```javascript +_{{increment.js}}_ ``` # math.js -``` javascript -{{math.js}} +```javascript +_{{math.js}}_ ``` # dist/output.js -``` javascript -{{dist/output.js}} +```javascript +_{{dist/output.js}}_ ``` # Info @@ -35,11 +35,11 @@ You can also see the info messages webpack prints to console (for both normal an ## Unoptimized ``` -{{stdout}} +_{{stdout}}_ ``` ## Production mode ``` -{{production:stdout}} -``` \ No newline at end of file +_{{production:stdout}}_ +``` diff --git a/examples/css/README.md b/examples/css/README.md new file mode 100644 index 00000000000..bbc0da9ae28 --- /dev/null +++ b/examples/css/README.md @@ -0,0 +1,729 @@ +# example.js + +```javascript +import "./style.css"; +import "./style2.css"; +import styles from "./style.module.css"; +import("./lazy-style.css"); + +document.getElementsByTagName("main")[0].className = styles.main; +``` + +# style.css + +```css +@import "style-imported.css"; +@import "https://fonts.googleapis.com/css?family=Open+Sans"; + +body { + background: green; + font-family: "Open Sans"; +} +``` + +# style.module.css + +```css +:root { + --large: 72px; +} + +.main { + font-size: var(--large); + color: darkblue; +} + +@media (min-width: 1024px) { + .main { + color: green; + } +} + +@supports (display: grid) { + .main { + display: grid + } +} +``` + +# webpack.config.js + +The config also registers a small plugin that gives CSS modules **TypeScript +types**. It reads each CSS module's name map (original class/id name -> +generated scoped name) from `module.buildInfo.cssData.exports` — the same data +webpack uses to build the JS exports, and the same data Lightning CSS returns as +`exports` from `transform()` — and writes a `.d.ts` **next to the source file** +so editors and `tsc` type `import … from "./x.module.css"`. No bundler ships +this natively today; the map webpack already computes makes it a few lines. + +```javascript +"use strict"; + +const fs = require("fs"); + +/** @typedef {import("webpack").Compiler} Compiler */ + +/** + * Renders a CSS module's export map into a `.d.ts`, using the object form that + * typed-css-modules and similar tools emit: quoted keys handle every class name + * (kebab-case, reserved words like `default`) with no special-casing. + * Paired with `parser: { namedExports: false }` below, the CSS module's default + * export is that object, so `import styles from "./x.module.css"` is typed. + * @param {Map} exports the original-name -> scoped-name map + * @returns {string} the `.d.ts` contents + */ +const toDts = (exports) => { + const lines = [ + "// Generated by CssModuleTypesPlugin. Do not edit.", + "declare const styles: {" + ]; + for (const name of [...exports.keys()].sort()) { + lines.push(`\treadonly ${JSON.stringify(name)}: string;`); + } + lines.push("};", "export default styles;", ""); + return lines.join("\n"); +}; + +/** + * Writes a TypeScript declaration **next to each CSS module** (e.g. + * `style.module.css.d.ts` beside `style.module.css`) so editors and `tsc` type + * `import styles from "./x.module.css"`. The names come from + * `module.buildInfo.cssData.exports` (a `Map` of original name + * -> generated scoped name) — the same source webpack uses for the JS exports, + * and the same data Lightning CSS returns as `exports` from `transform()`, so no + * separate parse of the CSS is needed. Commit the generated `.d.ts` files, or + * add `*.module.css.d.ts` to `.gitignore` — either works. + */ +class CssModuleTypesPlugin { + /** + * @param {Compiler} compiler the compiler + * @returns {void} + */ + apply(compiler) { + const { Compilation } = compiler.webpack; + + compiler.hooks.thisCompilation.tap( + "CssModuleTypesPlugin", + (compilation) => { + compilation.hooks.processAssets.tap( + { + name: "CssModuleTypesPlugin", + stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL + }, + () => { + for (const module of compilation.modules) { + const cssData = + /** @type {{ exports?: Map }=} */ + (module.buildInfo && module.buildInfo.cssData); + if (!cssData || !cssData.exports || cssData.exports.size === 0) { + continue; + } + const { resource } = + /** @type {import("webpack").NormalModule} */ (module); + fs.writeFileSync(`${resource}.d.ts`, toDts(cssData.exports)); + } + } + ); + } + ); + } +} + +/** @type {import("webpack").Configuration} */ +const config = { + output: { + uniqueName: "app" + }, + module: { + rules: [ + { + test: /\.module\.css$/, + type: "css/module", + // Default export = the class map object, matching the `export default + // styles` shape of the generated `.d.ts` (and css-loader's default). + parser: { namedExports: false } + } + ] + }, + experiments: { + css: true + }, + plugins: [new CssModuleTypesPlugin()] +}; + +module.exports = config; +``` + +# dist/output.js + +```javascript +/******/ (() => { // webpackBootstrap +/******/ "use strict"; +/******/ var __webpack_modules__ = ({ + +/***/ 6 +/*!******************************!*\ + !*** css ./style.module.css ***! + \******************************/ +/*! default exports */ +/*! export default [not provided] [no usage info] [missing usage info prevents renaming] */ +/*! export large [provided] [no usage info] [missing usage info prevents renaming] */ +/*! export main [provided] [no usage info] [missing usage info prevents renaming] */ +/*! other exports [not provided] [no usage info] */ +/*! runtime requirements: __webpack_require__.r, module, has css modules, __webpack_require__.* */ +(module, __unused_webpack_exports, __webpack_require__) { + +__webpack_require__.r(module.exports = { + "large": "--QRIlVD", + "main": "zI6JBT" +}); + + +/***/ } + +/******/ }); +``` + +
/* webpack runtime code */ + +``` js +/************************************************************************/ +/******/ // The module cache +/******/ const __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ const 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; +/******/ } +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = __webpack_modules__; +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/ensure chunk */ +/******/ __webpack_require__.f = {}; +/******/ // This file contains only the entry chunk. +/******/ // The chunk loading function for additional chunks +/******/ __webpack_require__.e = (chunkId) => { +/******/ return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => { +/******/ __webpack_require__.f[key](chunkId, promises); +/******/ return promises; +/******/ }, [])); +/******/ }; +/******/ +/******/ /* webpack/runtime/get css chunk filename */ +/******/ // This function allow to reference async chunks +/******/ __webpack_require__.k = (chunkId) => { +/******/ // return url for filenames based on template +/******/ return "" + chunkId + ".output.css"; +/******/ }; +/******/ +/******/ /* webpack/runtime/get javascript chunk filename */ +/******/ // This function allow to reference async chunks +/******/ __webpack_require__.u = (chunkId) => { +/******/ // return url for filenames based on template +/******/ return "" + chunkId + ".output.js"; +/******/ }; +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)); +/******/ +/******/ /* webpack/runtime/load script */ +/******/ (() => { +/******/ const inProgress = {}; +/******/ const dataWebpackPrefix = "app:"; +/******/ // loadScript function to load a script via script tag +/******/ __webpack_require__.l = (url, done, key, chunkId) => { +/******/ if(inProgress[url]) { inProgress[url].push(done); return; } +/******/ let script, needAttach; +/******/ if(key !== undefined) { +/******/ const scripts = document.getElementsByTagName("script"); +/******/ for(var i = 0; i < scripts.length; i++) { +/******/ const s = scripts[i]; +/******/ if(s.getAttribute("src") == url || s.getAttribute("data-webpack") == dataWebpackPrefix + key) { script = s; break; } +/******/ } +/******/ } +/******/ if(!script) { +/******/ needAttach = true; +/******/ script = document.createElement('script'); +/******/ +/******/ script.charset = 'utf-8'; +/******/ if (__webpack_require__.nc) { +/******/ script.setAttribute("nonce", __webpack_require__.nc); +/******/ } +/******/ script.setAttribute("data-webpack", dataWebpackPrefix + key); +/******/ +/******/ script.src = url; +/******/ } +/******/ inProgress[url] = [done]; +/******/ const onScriptComplete = (prev, event) => { +/******/ // avoid mem leaks in IE. +/******/ script.onerror = script.onload = null; +/******/ clearTimeout(timeout); +/******/ const doneFns = inProgress[url]; +/******/ delete inProgress[url]; +/******/ script.parentNode?.removeChild(script); +/******/ doneFns?.forEach((fn) => (fn(event))); +/******/ if(prev) return prev(event); +/******/ } +/******/ const timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000); +/******/ script.onerror = onScriptComplete.bind(null, script.onerror); +/******/ script.onload = onScriptComplete.bind(null, script.onload); +/******/ needAttach && document.head.appendChild(script); +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/make namespace object */ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = (exports) => { +/******/ if(Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/******/ /* webpack/runtime/publicPath */ +/******/ __webpack_require__.p = "dist/"; +/******/ +/******/ /* webpack/runtime/css loading */ +/******/ (() => { +/******/ // object to store loaded and loading chunks +/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched +/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded +/******/ const installedChunks = { +/******/ 0: 0 +/******/ }; +/******/ +/******/ const uniqueName = "app"; +/******/ const loadingAttribute = "data-webpack-loading"; +/******/ const loadStylesheet = (chunkId, url, done) => { +/******/ let link, needAttach, key = "chunk-" + chunkId; +/******/ +/******/ const links = document.getElementsByTagName("link"); +/******/ for(var i = 0; i < links.length; i++) { +/******/ const l = links[i]; +/******/ if(l.rel == "stylesheet" && (l.href == url || l.getAttribute("href") == url || l.getAttribute("data-webpack") == uniqueName + ":" + key)) { link = l; break; } +/******/ } +/******/ if(!done) return link; +/******/ +/******/ if(!link) { +/******/ needAttach = true; +/******/ link = document.createElement('link'); +/******/ link.charset = 'utf-8'; +/******/ if (__webpack_require__.nc) { +/******/ link.setAttribute("nonce", __webpack_require__.nc); +/******/ } +/******/ link.setAttribute("data-webpack", uniqueName + ":" + key); +/******/ +/******/ link.setAttribute(loadingAttribute, 1); +/******/ link.rel = "stylesheet"; +/******/ link.href = url; +/******/ } +/******/ let timeout; +/******/ const onLinkComplete = (prev, event) => { +/******/ link.onerror = link.onload = null; +/******/ link.removeAttribute(loadingAttribute); +/******/ clearTimeout(timeout); +/******/ if(event && event.type != "load") link.parentNode.removeChild(link) +/******/ done(event); +/******/ if(prev) return prev(event); +/******/ }; +/******/ if(link.getAttribute(loadingAttribute)) { +/******/ timeout = setTimeout(onLinkComplete.bind(null, undefined, { type: 'timeout', target: link }), 120000); +/******/ link.onerror = onLinkComplete.bind(null, link.onerror); +/******/ link.onload = onLinkComplete.bind(null, link.onload); +/******/ } else onLinkComplete(undefined, { type: 'load', target: link }); +/******/ +/******/ if (needAttach) { +/******/ document.head.appendChild(link); +/******/ } +/******/ return link; +/******/ }; +/******/ __webpack_require__.f.css = (chunkId, promises) => { +/******/ // css chunk loading +/******/ let installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined; +/******/ if(installedChunkData !== 0) { // 0 means "already installed". +/******/ +/******/ // a Promise means "currently loading". +/******/ if(installedChunkData) { +/******/ promises.push(installedChunkData[2]); +/******/ } else { +/******/ if(true) { // all chunks have CSS +/******/ // setup Promise in chunk cache +/******/ const promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject])); +/******/ promises.push(installedChunkData[2] = promise); +/******/ +/******/ // start chunk loading +/******/ const url = __webpack_require__.p + __webpack_require__.k(chunkId); +/******/ // create error before stack unwound to get useful stacktrace later +/******/ const error = new Error(); +/******/ const loadingEnded = (event) => { +/******/ if(__webpack_require__.o(installedChunks, chunkId)) { +/******/ installedChunkData = installedChunks[chunkId]; +/******/ if(installedChunkData !== 0) installedChunks[chunkId] = undefined; +/******/ if(installedChunkData) { +/******/ if(event.type !== "load") { +/******/ const errorType = event && event.type; +/******/ const realHref = event && event.target && event.target.href; +/******/ error.message = 'Loading css chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realHref + ')'; +/******/ error.name = 'ChunkLoadError'; +/******/ error.type = errorType; +/******/ error.request = realHref; +/******/ error.event = event; +/******/ installedChunkData[1](error); +/******/ } else { +/******/ installedChunks[chunkId] = 0; +/******/ installedChunkData[0](); +/******/ } +/******/ } +/******/ } +/******/ }; +/******/ +/******/ loadStylesheet(chunkId, url, loadingEnded); +/******/ } else installedChunks[chunkId] = 0; +/******/ } +/******/ } +/******/ }; +/******/ +/******/ // no prefetching +/******/ +/******/ // no preloaded +/******/ // no hmr +/******/ })(); +/******/ +/******/ /* webpack/runtime/jsonp chunk loading */ +/******/ (() => { +/******/ // no baseURI +/******/ +/******/ // object to store loaded and loading chunks +/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched +/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded +/******/ const installedChunks = { +/******/ 0: 0 +/******/ }; +/******/ +/******/ __webpack_require__.f.j = (chunkId, promises) => { +/******/ // JSONP chunk loading for javascript +/******/ let installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined; +/******/ if(installedChunkData !== 0) { // 0 means "already installed". +/******/ +/******/ // a Promise means "currently loading". +/******/ if(installedChunkData) { +/******/ promises.push(installedChunkData[2]); +/******/ } else { +/******/ if(true) { // all chunks have JS +/******/ // setup Promise in chunk cache +/******/ const promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject])); +/******/ promises.push(installedChunkData[2] = promise); +/******/ +/******/ // start chunk loading +/******/ const url = __webpack_require__.p + __webpack_require__.u(chunkId); +/******/ // create error before stack unwound to get useful stacktrace later +/******/ const error = new Error(); +/******/ const loadingEnded = (event) => { +/******/ if(__webpack_require__.o(installedChunks, chunkId)) { +/******/ installedChunkData = installedChunks[chunkId]; +/******/ if(installedChunkData !== 0) installedChunks[chunkId] = undefined; +/******/ if(installedChunkData) { +/******/ const errorType = event && (event.type === 'load' ? 'missing' : event.type); +/******/ const realSrc = event && event.target && event.target.src; +/******/ error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'; +/******/ error.name = 'ChunkLoadError'; +/******/ error.type = errorType; +/******/ error.request = realSrc; +/******/ error.event = event; +/******/ installedChunkData[1](error); +/******/ } +/******/ } +/******/ }; +/******/ __webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId); +/******/ } +/******/ } +/******/ } +/******/ }; +/******/ +/******/ // no prefetching +/******/ +/******/ // no preloaded +/******/ +/******/ // no HMR +/******/ +/******/ // no HMR manifest +/******/ +/******/ // no on chunks loaded +/******/ +/******/ // install a JSONP callback for chunk loading +/******/ const webpackJsonpCallback = (parentChunkLoadingFunction, data) => { +/******/ let [chunkIds, moreModules, runtime] = data; +/******/ // add "moreModules" to the modules object, +/******/ // then flag all "chunkIds" as loaded and fire callback +/******/ var moduleId, chunkId, i = 0; +/******/ if(chunkIds.some((id) => (installedChunks[id] !== 0))) { +/******/ for(moduleId in moreModules) { +/******/ if(__webpack_require__.o(moreModules, moduleId)) { +/******/ __webpack_require__.m[moduleId] = moreModules[moduleId]; +/******/ } +/******/ } +/******/ if(runtime) var result = runtime(__webpack_require__); +/******/ } +/******/ if(parentChunkLoadingFunction) parentChunkLoadingFunction(data); +/******/ for(;i < chunkIds.length; i++) { +/******/ chunkId = chunkIds[i]; +/******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) { +/******/ installedChunks[chunkId][0](); +/******/ } +/******/ installedChunks[chunkId] = 0; +/******/ } +/******/ +/******/ } +/******/ +/******/ const chunkLoadingGlobal = self["webpackChunkapp"] = self["webpackChunkapp"] || []; +/******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0)); +/******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal)); +/******/ })(); +/******/ +/************************************************************************/ +``` + +
+ +``` js +let __webpack_exports__ = {}; +// This entry needs to be wrapped in an IIFE because it needs to be isolated against other modules in the chunk. +(() => { +/*!********************!*\ + !*** ./example.js ***! + \********************/ +/*! namespace exports */ +/*! exports [not provided] [no usage info] */ +/*! runtime requirements: __webpack_require__, __webpack_require__.r, __webpack_exports__, __webpack_require__.e, __webpack_require__.* */ +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var _style_module_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./style.module.css */ 6); + + + +__webpack_require__.e(/*! import() */ 1).then(__webpack_require__.bind(__webpack_require__, /*! ./lazy-style.css */ 7)); + +document.getElementsByTagName("main")[0].className = _style_module_css__WEBPACK_IMPORTED_MODULE_0__.main; + +})(); + +/******/ })() +; +``` + +# dist/output.css + +```javascript +/*!********************************************************************!*\ + !*** external "https://fonts.googleapis.com/css?family=Open+Sans" ***! + \********************************************************************/ +@import url("https://fonts.googleapis.com/css?family=Open+Sans"); +/*!********************************!*\ + !*** css ./style-imported.css ***! + \********************************/ +.img { + width: 150px; + height: 150px; + background: url(dist/89a353e9c515885abd8e.png); +} + +/*!***********************!*\ + !*** css ./style.css ***! + \***********************/ + +body { + background: green; + font-family: "Open Sans"; +} + +/*!************************!*\ + !*** css ./style2.css ***! + \************************/ +body { + background: red; +} + +/*!******************************!*\ + !*** css ./style.module.css ***! + \******************************/ +:root { + --QRIlVD: 72px; +} + +.zI6JBT { + font-size: var(--QRIlVD); + color: darkblue; +} + +@media (min-width: 1024px) { + .zI6JBT { + color: green; + } +} + +@supports (display: grid) { + .zI6JBT { + display: grid + } +} +``` + +## production + +```javascript +@import url(https://fonts.googleapis.com/css?family=Open+Sans);.img{width:150px;height:150px;background:url(dist/89a353e9c515885abd8e.png)}body{background:green;font-family:Open Sans}body{background:red}:root{--QRIlVD:72px}.zI6JBT{font-size:var(--QRIlVD);color:#00008b}@media (width>=1024px){.zI6JBT{color:green}}@supports (display:grid){.zI6JBT{display:grid}} +``` + +# dist/1.output.css + +```javascript +/*!****************************!*\ + !*** css ./lazy-style.css ***! + \****************************/ +body { + color: blue; +} +``` + +# style.module.css.d.ts + +The plugin writes this declaration **next to the CSS source** (not into `dist`), +so it is picked up automatically by your editor and by `tsc`. It uses the same +object shape as `typed-css-modules` / `css-modules-typescript-loader` — quoted +keys, so kebab-case names and reserved words like `default` need no special +handling — paired with `parser: { namedExports: false }` so the module's default +export is that object: + +```typescript +// Generated by CssModuleTypesPlugin. Do not edit. +declare const styles: { + readonly "large": string; + readonly "main": string; +}; +export default styles; +``` + +With it in place, the import in `example.js` is fully typed — `styles.main` is a +`string`, `styles.nope` is a compile error, and the editor autocompletes the +class names: + +```typescript +import styles from "./style.module.css"; // styles.main: string +``` + +Wiring it into a real project: run the build (or `webpack --watch`) so the +`.d.ts` files stay in sync with the CSS, then either commit them or add +`*.module.css.d.ts` to `.gitignore`. Because the declaration lives beside the +source, no `paths`/ambient-module setup is needed. + +# What native CSS scopes (CSS Modules) + +webpack's native CSS localizes more identifiers than any classic loader. For a +`css/module` (or auto-detected `*.module.css`): + +- **Always:** class (`.foo`) and id (`#foo`) selectors. +- **Explicit, per parser option (all default `true`):** `@keyframes` + + `animation-name` (`animation`), grid line/area names (`grid`), + `@counter-style` + `list-style` (`customIdents`), `@container` + + `container-name` (`container`), `@function` names + calls (`function`), + `view-transition-name`/`-group`/`-class` + `::view-transition-*()` pseudo + arguments (`customIdents`), counter names (`counter-reset`/`-increment`/`-set` + + `counter()` / `counters()` / `target-counter()`, `customIdents`). +- **Auto (any `--foo` dashed ident, via `dashedIdents`, default `true`):** custom + properties and `var(--foo)` incl. cross-file `var(--foo from "./x.css")` and + `from global`; `@property` / `@font-palette-values` / `@color-profile` names; + anchor positioning (`anchor-name`, `position-anchor`, `anchor()`, + `@position-try`, `anchor-scope`); scroll-driven-animation names; and + `@container style(--foo)` queries. New dashed-ident CSS features are covered + automatically, with no feature-specific code. +- **Composition / values:** `composes` (same-file, `from "./x.css"`, + `from global`), `@value` (incl. cross-file), ICSS `:import` / `:export`. + +Intentionally left **global** (they coordinate across documents or the whole +app, so scoping would break them): `@layer` and `@page` names, +`@font-feature-values` family names, `@view-transition` `types`, and +`:global(...)` selectors. + +# Info + +## Unoptimized + +``` +assets by path *.js 15.4 KiB + asset output.js 15.1 KiB [emitted] (name: main) + asset 1.output.js 332 bytes [emitted] +assets by path *.css 1.16 KiB + asset output.css 1.04 KiB [emitted] (name: main) + asset 1.output.css 125 bytes [emitted] +asset 89a353e9c515885abd8e.png 14.6 KiB [emitted] [immutable] [from: images/file.png] (auxiliary name: main) +Entrypoint main 16.2 KiB (14.6 KiB) = output.js 15.1 KiB output.css 1.04 KiB 1 auxiliary asset +chunk (runtime: main) output.js, output.css (main) 259 bytes (javascript) 14.6 KiB (asset) 42 bytes (asset-url) 454 bytes (css) 42 bytes (css-import) 8.87 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 8.87 KiB 9 modules + dependent modules 14.6 KiB (asset) 42 bytes (asset-url) 454 bytes (css) 78 bytes (javascript) 42 bytes (css-import) [dependent] 6 modules + ./example.js 181 bytes [built] [code generated] + [no exports] + [used exports unknown] + entry ./example.js main +chunk (runtime: main) 1.output.js, 1.output.css 1 bytes (javascript) 23 bytes (css) [rendered] + > ./lazy-style.css ./example.js 4:0-26 + css ./lazy-style.css 1 bytes (javascript) 23 bytes (css) [built] [code generated] + [no exports] + [used exports unknown] + import() ./lazy-style.css ./example.js 4:0-26 +webpack X.X.X compiled successfully +``` + +## Production mode + +``` +assets by path *.js 3.21 KiB + asset output.js 3.13 KiB [emitted] [minimized] (name: main) + asset 822.output.js 85 bytes [emitted] [minimized] +assets by path *.css 376 bytes + asset output.css 360 bytes [emitted] [minimized] (name: main) + asset 822.output.css 16 bytes [emitted] [minimized] +asset 89a353e9c515885abd8e.png 14.6 KiB [emitted] [immutable] [from: images/file.png] (auxiliary name: main) +Entrypoint main 3.48 KiB (14.6 KiB) = output.js 3.13 KiB output.css 360 bytes 1 auxiliary asset +chunk (runtime: main) output.js, output.css (main) 467 bytes (javascript) 14.6 KiB (asset) 42 bytes (asset-url) 454 bytes (css) 42 bytes (css-import) 8.64 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 8.64 KiB 8 modules + dependent modules 14.6 KiB (asset) 42 bytes (asset-url) 79 bytes (css) 42 bytes (css-import) [dependent] 3 modules + built modules 467 bytes (javascript) 375 bytes (css) [built] + ./example.js + 5 modules 408 bytes [not cacheable] [built] [code generated] + [no exports] + [no exports used] + entry ./example.js main + css ./style.css 148 bytes [built] [code generated] + [no exports] + [no exports used] + css ./style.module.css 59 bytes (javascript) 200 bytes (css) [built] [code generated] + [exports: large, main] + [only some exports used: main] + css ./style2.css 27 bytes [built] [code generated] + [no exports] + [no exports used] +chunk (runtime: main) 822.output.js, 822.output.css 1 bytes (javascript) 23 bytes (css) [rendered] + > ./lazy-style.css ./example.js 4:0-26 + css ./lazy-style.css 1 bytes (javascript) 23 bytes (css) [built] [code generated] + [no exports] + import() ./lazy-style.css ./example.js + 5 modules ./example.js 4:0-26 +webpack X.X.X compiled successfully +``` diff --git a/examples/web-worker/build.js b/examples/css/build.js similarity index 100% rename from examples/web-worker/build.js rename to examples/css/build.js diff --git a/examples/css/example.js b/examples/css/example.js new file mode 100644 index 00000000000..7ea321bc7bd --- /dev/null +++ b/examples/css/example.js @@ -0,0 +1,6 @@ +import "./style.css"; +import "./style2.css"; +import styles from "./style.module.css"; +import("./lazy-style.css"); + +document.getElementsByTagName("main")[0].className = styles.main; diff --git a/examples/css/images/file.png b/examples/css/images/file.png new file mode 100644 index 00000000000..fb53b9dedd3 Binary files /dev/null and b/examples/css/images/file.png differ diff --git a/examples/css/index.html b/examples/css/index.html new file mode 100644 index 00000000000..9b3f06397ab --- /dev/null +++ b/examples/css/index.html @@ -0,0 +1,10 @@ + + + + + +
Hello World
+

+ + + diff --git a/examples/css/lazy-style.css b/examples/css/lazy-style.css new file mode 100644 index 00000000000..36505138bc9 --- /dev/null +++ b/examples/css/lazy-style.css @@ -0,0 +1,3 @@ +body { + color: blue; +} diff --git a/examples/css/style-imported.css b/examples/css/style-imported.css new file mode 100644 index 00000000000..83989315ce2 --- /dev/null +++ b/examples/css/style-imported.css @@ -0,0 +1,5 @@ +.img { + width: 150px; + height: 150px; + background: url("./images/file.png"); +} diff --git a/examples/css/style.css b/examples/css/style.css new file mode 100644 index 00000000000..8b855420284 --- /dev/null +++ b/examples/css/style.css @@ -0,0 +1,7 @@ +@import "style-imported.css"; +@import "https://fonts.googleapis.com/css?family=Open+Sans"; + +body { + background: green; + font-family: "Open Sans"; +} diff --git a/examples/css/style.module.css b/examples/css/style.module.css new file mode 100644 index 00000000000..a788746a1a3 --- /dev/null +++ b/examples/css/style.module.css @@ -0,0 +1,20 @@ +:root { + --large: 72px; +} + +.main { + font-size: var(--large); + color: darkblue; +} + +@media (min-width: 1024px) { + .main { + color: green; + } +} + +@supports (display: grid) { + .main { + display: grid + } +} diff --git a/examples/css/style.module.css.d.ts b/examples/css/style.module.css.d.ts new file mode 100644 index 00000000000..4d564c6f98e --- /dev/null +++ b/examples/css/style.module.css.d.ts @@ -0,0 +1,6 @@ +// Generated by CssModuleTypesPlugin. Do not edit. +declare const styles: { + readonly "large": string; + readonly "main": string; +}; +export default styles; diff --git a/examples/css/style2.css b/examples/css/style2.css new file mode 100644 index 00000000000..f0d5b13bffd --- /dev/null +++ b/examples/css/style2.css @@ -0,0 +1,3 @@ +body { + background: red; +} diff --git a/examples/css/template.md b/examples/css/template.md new file mode 100644 index 00000000000..9693967c524 --- /dev/null +++ b/examples/css/template.md @@ -0,0 +1,123 @@ +# example.js + +```javascript +_{{example.js}}_ +``` + +# style.css + +```css +_{{style.css}}_ +``` + +# style.module.css + +```css +_{{style.module.css}}_ +``` + +# webpack.config.js + +The config also registers a small plugin that gives CSS modules **TypeScript +types**. It reads each CSS module's name map (original class/id name -> +generated scoped name) from `module.buildInfo.cssData.exports` — the same data +webpack uses to build the JS exports, and the same data Lightning CSS returns as +`exports` from `transform()` — and writes a `.d.ts` **next to the source file** +so editors and `tsc` type `import … from "./x.module.css"`. No bundler ships +this natively today; the map webpack already computes makes it a few lines. + +```javascript +_{{webpack.config.js}}_ +``` + +# dist/output.js + +```javascript +_{{dist/output.js}}_ +``` + +# dist/output.css + +```javascript +_{{dist/output.css}}_ +``` + +## production + +```javascript +_{{production:dist/output.css}}_ +``` + +# dist/1.output.css + +```javascript +_{{dist/1.output.css}}_ +``` + +# style.module.css.d.ts + +The plugin writes this declaration **next to the CSS source** (not into `dist`), +so it is picked up automatically by your editor and by `tsc`. It uses the same +object shape as `typed-css-modules` / `css-modules-typescript-loader` — quoted +keys, so kebab-case names and reserved words like `default` need no special +handling — paired with `parser: { namedExports: false }` so the module's default +export is that object: + +```typescript +_{{style.module.css.d.ts}}_ +``` + +With it in place, the import in `example.js` is fully typed — `styles.main` is a +`string`, `styles.nope` is a compile error, and the editor autocompletes the +class names: + +```typescript +import styles from "./style.module.css"; // styles.main: string +``` + +Wiring it into a real project: run the build (or `webpack --watch`) so the +`.d.ts` files stay in sync with the CSS, then either commit them or add +`*.module.css.d.ts` to `.gitignore`. Because the declaration lives beside the +source, no `paths`/ambient-module setup is needed. + +# What native CSS scopes (CSS Modules) + +webpack's native CSS localizes more identifiers than any classic loader. For a +`css/module` (or auto-detected `*.module.css`): + +- **Always:** class (`.foo`) and id (`#foo`) selectors. +- **Explicit, per parser option (all default `true`):** `@keyframes` + + `animation-name` (`animation`), grid line/area names (`grid`), + `@counter-style` + `list-style` (`customIdents`), `@container` + + `container-name` (`container`), `@function` names + calls (`function`), + `view-transition-name`/`-group`/`-class` + `::view-transition-*()` pseudo + arguments (`customIdents`), counter names (`counter-reset`/`-increment`/`-set` + + `counter()` / `counters()` / `target-counter()`, `customIdents`). +- **Auto (any `--foo` dashed ident, via `dashedIdents`, default `true`):** custom + properties and `var(--foo)` incl. cross-file `var(--foo from "./x.css")` and + `from global`; `@property` / `@font-palette-values` / `@color-profile` names; + anchor positioning (`anchor-name`, `position-anchor`, `anchor()`, + `@position-try`, `anchor-scope`); scroll-driven-animation names; and + `@container style(--foo)` queries. New dashed-ident CSS features are covered + automatically, with no feature-specific code. +- **Composition / values:** `composes` (same-file, `from "./x.css"`, + `from global`), `@value` (incl. cross-file), ICSS `:import` / `:export`. + +Intentionally left **global** (they coordinate across documents or the whole +app, so scoping would break them): `@layer` and `@page` names, +`@font-feature-values` family names, `@view-transition` `types`, and +`:global(...)` selectors. + +# Info + +## Unoptimized + +``` +_{{stdout}}_ +``` + +## Production mode + +``` +_{{production:stdout}}_ +``` diff --git a/examples/css/webpack.config.js b/examples/css/webpack.config.js new file mode 100644 index 00000000000..f5716edcddb --- /dev/null +++ b/examples/css/webpack.config.js @@ -0,0 +1,95 @@ +"use strict"; + +const fs = require("fs"); + +/** @typedef {import("webpack").Compiler} Compiler */ + +/** + * Renders a CSS module's export map into a `.d.ts`, using the object form that + * typed-css-modules and similar tools emit: quoted keys handle every class name + * (kebab-case, reserved words like `default`) with no special-casing. + * Paired with `parser: { namedExports: false }` below, the CSS module's default + * export is that object, so `import styles from "./x.module.css"` is typed. + * @param {Map} exports the original-name -> scoped-name map + * @returns {string} the `.d.ts` contents + */ +const toDts = (exports) => { + const lines = [ + "// Generated by CssModuleTypesPlugin. Do not edit.", + "declare const styles: {" + ]; + for (const name of [...exports.keys()].sort()) { + lines.push(`\treadonly ${JSON.stringify(name)}: string;`); + } + lines.push("};", "export default styles;", ""); + return lines.join("\n"); +}; + +/** + * Writes a TypeScript declaration **next to each CSS module** (e.g. + * `style.module.css.d.ts` beside `style.module.css`) so editors and `tsc` type + * `import styles from "./x.module.css"`. The names come from + * `module.buildInfo.cssData.exports` (a `Map` of original name + * -> generated scoped name) — the same source webpack uses for the JS exports, + * and the same data Lightning CSS returns as `exports` from `transform()`, so no + * separate parse of the CSS is needed. Commit the generated `.d.ts` files, or + * add `*.module.css.d.ts` to `.gitignore` — either works. + */ +class CssModuleTypesPlugin { + /** + * @param {Compiler} compiler the compiler + * @returns {void} + */ + apply(compiler) { + const { Compilation } = compiler.webpack; + + compiler.hooks.thisCompilation.tap( + "CssModuleTypesPlugin", + (compilation) => { + compilation.hooks.processAssets.tap( + { + name: "CssModuleTypesPlugin", + stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL + }, + () => { + for (const module of compilation.modules) { + const cssData = + /** @type {{ exports?: Map }=} */ + (module.buildInfo && module.buildInfo.cssData); + if (!cssData || !cssData.exports || cssData.exports.size === 0) { + continue; + } + const { resource } = + /** @type {import("webpack").NormalModule} */ (module); + fs.writeFileSync(`${resource}.d.ts`, toDts(cssData.exports)); + } + } + ); + } + ); + } +} + +/** @type {import("webpack").Configuration} */ +const config = { + output: { + uniqueName: "app" + }, + module: { + rules: [ + { + test: /\.module\.css$/, + type: "css/module", + // Default export = the class map object, matching the `export default + // styles` shape of the generated `.d.ts` (and css-loader's default). + parser: { namedExports: false } + } + ] + }, + experiments: { + css: true + }, + plugins: [new CssModuleTypesPlugin()] +}; + +module.exports = config; diff --git a/examples/custom-javascript-parser/README.md b/examples/custom-javascript-parser/README.md new file mode 100644 index 00000000000..082c2aaa7fa --- /dev/null +++ b/examples/custom-javascript-parser/README.md @@ -0,0 +1,368 @@ +# Custom javascript parser + +## Source code + +```javascript +import { increment as inc } from "./increment"; +var a = 1; +inc(a); // 2 + +// async loading +import("./async-loaded").then(function (asyncLoaded) { + console.log(asyncLoaded); +}); +``` + +## Parsers + +### acorn (default) + +```javascript +"use strict"; + +const acorn = require("acorn"); + +/** @typedef {import("estree").Program} Program */ +/** @typedef {import("estree").Comment} Comment */ +/** @typedef {import("estree").SourceLocation} SourceLocation */ +/** @typedef {import("../../../lib/javascript/JavascriptParser").ParseOptions} ParseOptions */ +/** @typedef {import("../../../lib/javascript/JavascriptParser").ParseResult} ParseResult */ + +/** + * @param {string} sourceCode the source code + * @param {ParseOptions} options options + * @returns {ParseResult} the parsed result + */ +const acornParse = (sourceCode, options) => { + /** @type {(Comment & { start: number, end: number, loc: SourceLocation })[]} */ + const comments = []; + + const ast = + /** @type {import("estree").Program} */ + ( + acorn.parse(sourceCode, { + ...options, + onComment: options.comments ? comments : undefined + }) + ); + + return { ast, comments }; +}; + +module.exports = acornParse; +``` + +### oxc + +Implementation example: + +```javascript +"use strict"; + +const oxc = require("oxc-parser"); + +/** @typedef {import("estree").Program} Program */ +/** @typedef {import("estree").Comment} Comment */ +/** @typedef {import("../../../lib/javascript/JavascriptParser").ParseOptions} ParseOptions */ +/** @typedef {import("../../../lib/javascript/JavascriptParser").ParseResult} ParseResult */ + +/** + * Oxc has no location API — none is needed: webpack derives line/column + * locations from node offsets and the source text itself. ASI positions are + * likewise read from the source, so no semicolon collection is required. + * @param {string} sourceCode the source code + * @param {ParseOptions} options options + * @returns {ParseResult} the parsed result + */ +const oxcParse = (sourceCode, options) => { + const result = oxc.parseSync("file.js", sourceCode, { + astType: "js", + range: true, + sourceType: options.sourceType === "module" ? "module" : "script", + // @ts-expect-error no types + experimentalRawTransfer: true + }); + + const comments = + /** @type {(Comment & { start: number, end: number })[]} */ + (result.comments); + + // webpack's magic-comment lookup reads `comment.range` + for (const comment of comments) { + if (!comment.range) comment.range = [comment.start, comment.end]; + } + + return { + ast: /** @type {Program} */ (/** @type {unknown} */ (result.program)), + comments + }; +}; + +module.exports = oxcParse; +``` + +### meriyah + +Implementation example: + +```javascript +"use strict"; + +const meriyah = require("meriyah"); + +/** @typedef {import("estree").Program} Program */ +/** @typedef {import("estree").Node} Node */ +/** @typedef {import("estree").Comment} Comment */ +/** @typedef {import("estree").SourceLocation} SourceLocation */ +/** @typedef {import("../../../lib/javascript/JavascriptParser").ParseOptions} ParseOptions */ +/** @typedef {import("../../../lib/javascript/JavascriptParser").ParseResult} ParseResult */ + +/** + * @param {string} sourceCode the source code + * @param {ParseOptions} options options + * @returns {ParseResult} the parsed result + */ +const meriyahParse = (sourceCode, options) => { + /** @type {(Comment & { start: number, end: number, loc: SourceLocation })[]} */ + const comments = []; + + const ast = + /** @type {import("estree").Program} */ + ( + meriyah.parse(sourceCode, { + ...options, + module: options.sourceType === "module", + loc: options.locations, + onComment: options.comments + ? (type, value, start, end, loc) => { + if (type === "SingleLine" || type === "MultiLine") { + comments.push({ + type: type === "SingleLine" ? "Line" : "Block", + value, + start, + end, + range: [start, end], + loc + }); + } + } + : undefined + }) + ); + + return { ast, comments }; +}; + +module.exports = meriyahParse; +``` + +## Configuration example + +```javascript +"use strict"; + +const acornParse = require("./internals/acorn-parse.js"); +const meriyahParse = require("./internals/meriyah-parse.js"); +const oxcParse = require("./internals/oxc-parse.js"); + +/** @type {import("webpack").Configuration[]} */ +const config = [ + // oxc + { + mode: "production", + optimization: { + chunkIds: "deterministic" // To keep filename consistent between different modes (for example building only) + }, + output: { + filename: "oxc.[name].js" + }, + module: { + // Global override + parser: { + javascript: { + parse: oxcParse + } + } + // Override on the module level, only for modules which match the `test` + // rules: [ + // { + // test: /\.js$/, + // parser: { + // parse: oxcParse + // } + // } + // ] + } + }, + // meriyah + { + mode: "production", + optimization: { + chunkIds: "deterministic" // To keep filename consistent between different modes (for example building only) + }, + output: { + filename: "meriyah.[name].js" + }, + module: { + // Global override + parser: { + javascript: { + parse: meriyahParse + } + } + // Override on the module level, only for modules which match the `test` + // rules: [ + // { + // test: /\.js$/, + // parser: { + // parse: meriyahParse + // } + // } + // ] + } + }, + // acorn + { + mode: "production", + output: { + filename: "acorn.[name].js" + }, + optimization: { + chunkIds: "deterministic" // To keep filename consistent between different modes (for example building only) + }, + module: { + // Global override + parser: { + javascript: { + parse: acornParse + } + } + // Override on the module level, only for modules which match the `test` + // rules: [ + // { + // test: /\.js$/, + // parser: { + // parse: acornParse + // } + // } + // ] + } + } +]; + +module.exports = config; +``` + +Implementation example: + +# Info + +## Unoptimized + +``` +asset output.js 12.2 KiB [compared for emit] (name: main) +asset 655.output.js 761 bytes [compared for emit] +chunk (runtime: main) 655.output.js 24 bytes [rendered] + > ./async-loaded ./example.js 6:0-24 + ./async-loaded.js 24 bytes [built] [code generated] + [exports: answer] + [used exports unknown] + import() ./async-loaded ./example.js 6:0-24 +chunk (runtime: main) output.js (main) 457 bytes (javascript) 5.93 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 5.93 KiB 8 modules + dependent modules 281 bytes [dependent] 2 modules + ./example.js 176 bytes [built] [code generated] + [no exports] + [used exports unknown] + entry ./example.js main +webpack X.X.X compiled successfully + +asset output.js 12.2 KiB [emitted] (name: main) +asset 655.output.js 761 bytes [emitted] +chunk (runtime: main) 655.output.js 24 bytes [rendered] + > ./async-loaded ./example.js 6:0-24 + ./async-loaded.js 24 bytes [built] [code generated] + [exports: answer] + [used exports unknown] + import() ./async-loaded ./example.js 6:0-24 +chunk (runtime: main) output.js (main) 457 bytes (javascript) 5.93 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 5.93 KiB 8 modules + dependent modules 281 bytes [dependent] 2 modules + ./example.js 176 bytes [built] [code generated] + [no exports] + [used exports unknown] + entry ./example.js main +webpack X.X.X compiled successfully + +asset output.js 12.2 KiB [emitted] (name: main) +asset 655.output.js 761 bytes [emitted] +chunk (runtime: main) 655.output.js 24 bytes [rendered] + > ./async-loaded ./example.js 6:0-24 + ./async-loaded.js 24 bytes [built] [code generated] + [exports: answer] + [used exports unknown] + import() ./async-loaded ./example.js 6:0-24 +chunk (runtime: main) output.js (main) 457 bytes (javascript) 5.93 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 5.93 KiB 8 modules + dependent modules 281 bytes [dependent] 2 modules + ./example.js 176 bytes [built] [code generated] + [no exports] + [used exports unknown] + entry ./example.js main +webpack X.X.X compiled successfully +``` + +## Production mode + +``` +asset output.js 2.24 KiB [compared for emit] [minimized] (name: main) +asset 655.output.js 121 bytes [compared for emit] [minimized] +chunk (runtime: main) 655.output.js 24 bytes [rendered] + > ./async-loaded ./example.js 6:0-24 + ./async-loaded.js 24 bytes [built] [code generated] + [exports: answer] + import() ./async-loaded ./example.js + 2 modules ./example.js 6:0-24 +chunk (runtime: main) output.js (main) 457 bytes (javascript) 5.93 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 5.93 KiB 8 modules + ./example.js + 2 modules 457 bytes [built] [code generated] + [no exports] + [no exports used] + entry ./example.js main +webpack X.X.X compiled successfully + +asset output.js 2.24 KiB [emitted] [minimized] (name: main) +asset 655.output.js 121 bytes [emitted] [minimized] +chunk (runtime: main) 655.output.js 24 bytes [rendered] + > ./async-loaded ./example.js 6:0-24 + ./async-loaded.js 24 bytes [built] [code generated] + [exports: answer] + import() ./async-loaded ./example.js + 2 modules ./example.js 6:0-24 +chunk (runtime: main) output.js (main) 457 bytes (javascript) 5.93 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 5.93 KiB 8 modules + ./example.js + 2 modules 457 bytes [built] [code generated] + [no exports] + [no exports used] + entry ./example.js main +webpack X.X.X compiled successfully + +asset output.js 2.24 KiB [compared for emit] [minimized] (name: main) +asset 655.output.js 121 bytes [compared for emit] [minimized] +chunk (runtime: main) 655.output.js 24 bytes [rendered] + > ./async-loaded ./example.js 6:0-24 + ./async-loaded.js 24 bytes [built] [code generated] + [exports: answer] + import() ./async-loaded ./example.js + 2 modules ./example.js 6:0-24 +chunk (runtime: main) output.js (main) 457 bytes (javascript) 5.93 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 5.93 KiB 8 modules + ./example.js + 2 modules 457 bytes [built] [code generated] + [no exports] + [no exports used] + entry ./example.js main +webpack X.X.X compiled successfully +``` diff --git a/examples/custom-javascript-parser/async-loaded.js b/examples/custom-javascript-parser/async-loaded.js new file mode 100644 index 00000000000..96100aefeb9 --- /dev/null +++ b/examples/custom-javascript-parser/async-loaded.js @@ -0,0 +1 @@ +export var answer = 42; diff --git a/examples/custom-javascript-parser/build.js b/examples/custom-javascript-parser/build.js new file mode 100644 index 00000000000..2e93fe5a3e1 --- /dev/null +++ b/examples/custom-javascript-parser/build.js @@ -0,0 +1 @@ +require("../build-common"); diff --git a/examples/custom-javascript-parser/example.js b/examples/custom-javascript-parser/example.js new file mode 100644 index 00000000000..d8b21149d67 --- /dev/null +++ b/examples/custom-javascript-parser/example.js @@ -0,0 +1,8 @@ +import { increment as inc } from "./increment"; +var a = 1; +inc(a); // 2 + +// async loading +import("./async-loaded").then(function (asyncLoaded) { + console.log(asyncLoaded); +}); diff --git a/examples/custom-javascript-parser/increment.js b/examples/custom-javascript-parser/increment.js new file mode 100644 index 00000000000..4d249274f01 --- /dev/null +++ b/examples/custom-javascript-parser/increment.js @@ -0,0 +1,4 @@ +import { add } from "./math"; +export function increment(val) { + return add(val, 1); +} diff --git a/examples/custom-javascript-parser/internals/acorn-parse.js b/examples/custom-javascript-parser/internals/acorn-parse.js new file mode 100644 index 00000000000..0d3f4769cb9 --- /dev/null +++ b/examples/custom-javascript-parser/internals/acorn-parse.js @@ -0,0 +1,32 @@ +"use strict"; + +const acorn = require("acorn"); + +/** @typedef {import("estree").Program} Program */ +/** @typedef {import("estree").Comment} Comment */ +/** @typedef {import("estree").SourceLocation} SourceLocation */ +/** @typedef {import("../../../lib/javascript/JavascriptParser").ParseOptions} ParseOptions */ +/** @typedef {import("../../../lib/javascript/JavascriptParser").ParseResult} ParseResult */ + +/** + * @param {string} sourceCode the source code + * @param {ParseOptions} options options + * @returns {ParseResult} the parsed result + */ +const acornParse = (sourceCode, options) => { + /** @type {(Comment & { start: number, end: number, loc: SourceLocation })[]} */ + const comments = []; + + const ast = + /** @type {import("estree").Program} */ + ( + acorn.parse(sourceCode, { + ...options, + onComment: options.comments ? comments : undefined + }) + ); + + return { ast, comments }; +}; + +module.exports = acornParse; diff --git a/examples/custom-javascript-parser/internals/bench.mjs b/examples/custom-javascript-parser/internals/bench.mjs new file mode 100644 index 00000000000..40e8bc57adf --- /dev/null +++ b/examples/custom-javascript-parser/internals/bench.mjs @@ -0,0 +1,43 @@ +import fs from "node:fs"; +import path from "node:path"; +import { Bench } from "tinybench"; + +import oxcParse from "./oxc-parse.js"; +import meriyahParse from "./meriyah-parse.js"; +import acornParse from "./acorn-parse.js"; + +const options = { + sourceType: "module", + ecmaVersion: "latest", + ranges: true, + locations: true, + comments: true, + allowHashBang: true, + allowReturnOutsideFunction: false +}; + +const bench = new Bench({ name: "simple benchmark", time: 100 }); + +const sourceCode = fs.readFileSync( + path.resolve( + import.meta.dirname, + "../../../node_modules/three/build/three.module.js" + ), + "utf8" +); + +bench + .add("oxc", () => { + oxcParse(sourceCode, options); + }) + .add("meriyah", () => { + meriyahParse(sourceCode, options); + }) + .add("acorn", () => { + acornParse(sourceCode, options); + }); + +await bench.run(); + +console.log(bench.name); +console.table(bench.table()); diff --git a/examples/custom-javascript-parser/internals/meriyah-parse.js b/examples/custom-javascript-parser/internals/meriyah-parse.js new file mode 100644 index 00000000000..eabaa44ac1e --- /dev/null +++ b/examples/custom-javascript-parser/internals/meriyah-parse.js @@ -0,0 +1,48 @@ +"use strict"; + +const meriyah = require("meriyah"); + +/** @typedef {import("estree").Program} Program */ +/** @typedef {import("estree").Node} Node */ +/** @typedef {import("estree").Comment} Comment */ +/** @typedef {import("estree").SourceLocation} SourceLocation */ +/** @typedef {import("../../../lib/javascript/JavascriptParser").ParseOptions} ParseOptions */ +/** @typedef {import("../../../lib/javascript/JavascriptParser").ParseResult} ParseResult */ + +/** + * @param {string} sourceCode the source code + * @param {ParseOptions} options options + * @returns {ParseResult} the parsed result + */ +const meriyahParse = (sourceCode, options) => { + /** @type {(Comment & { start: number, end: number, loc: SourceLocation })[]} */ + const comments = []; + + const ast = + /** @type {import("estree").Program} */ + ( + meriyah.parse(sourceCode, { + ...options, + module: options.sourceType === "module", + loc: options.locations, + onComment: options.comments + ? (type, value, start, end, loc) => { + if (type === "SingleLine" || type === "MultiLine") { + comments.push({ + type: type === "SingleLine" ? "Line" : "Block", + value, + start, + end, + range: [start, end], + loc + }); + } + } + : undefined + }) + ); + + return { ast, comments }; +}; + +module.exports = meriyahParse; diff --git a/examples/custom-javascript-parser/internals/oxc-parse.js b/examples/custom-javascript-parser/internals/oxc-parse.js new file mode 100644 index 00000000000..835bc0d861c --- /dev/null +++ b/examples/custom-javascript-parser/internals/oxc-parse.js @@ -0,0 +1,42 @@ +"use strict"; + +const oxc = require("oxc-parser"); + +/** @typedef {import("estree").Program} Program */ +/** @typedef {import("estree").Comment} Comment */ +/** @typedef {import("../../../lib/javascript/JavascriptParser").ParseOptions} ParseOptions */ +/** @typedef {import("../../../lib/javascript/JavascriptParser").ParseResult} ParseResult */ + +/** + * Oxc has no location API — none is needed: webpack derives line/column + * locations from node offsets and the source text itself. ASI positions are + * likewise read from the source, so no semicolon collection is required. + * @param {string} sourceCode the source code + * @param {ParseOptions} options options + * @returns {ParseResult} the parsed result + */ +const oxcParse = (sourceCode, options) => { + const result = oxc.parseSync("file.js", sourceCode, { + astType: "js", + range: true, + sourceType: options.sourceType === "module" ? "module" : "script", + // @ts-expect-error no types + experimentalRawTransfer: true + }); + + const comments = + /** @type {(Comment & { start: number, end: number })[]} */ + (result.comments); + + // webpack's magic-comment lookup reads `comment.range` + for (const comment of comments) { + if (!comment.range) comment.range = [comment.start, comment.end]; + } + + return { + ast: /** @type {Program} */ (/** @type {unknown} */ (result.program)), + comments + }; +}; + +module.exports = oxcParse; diff --git a/examples/custom-javascript-parser/math.js b/examples/custom-javascript-parser/math.js new file mode 100644 index 00000000000..796c1fae0dc --- /dev/null +++ b/examples/custom-javascript-parser/math.js @@ -0,0 +1,16 @@ +// Single-line Comment + +/* + * Multi-line comment + */ + +export function add() { + var sum = 0, + i = 0, + args = arguments, + l = args.length; + while (i < l) { + sum += args[i++]; + } + return sum; +} diff --git a/examples/custom-javascript-parser/template.md b/examples/custom-javascript-parser/template.md new file mode 100644 index 00000000000..1c43b597758 --- /dev/null +++ b/examples/custom-javascript-parser/template.md @@ -0,0 +1,53 @@ +# Custom javascript parser + +## Source code + +```javascript +_{{example.js}}_ +``` + +## Parsers + +### acorn (default) + +```javascript +_{{internals/acorn-parse.js}}_ +``` + +### oxc + +Implementation example: + +```javascript +_{{internals/oxc-parse.js}}_ +``` + +### meriyah + +Implementation example: + +```javascript +_{{internals/meriyah-parse.js}}_ +``` + +## Configuration example + +```javascript +_{{webpack.config.js}}_ +``` + +Implementation example: + +# Info + +## Unoptimized + +``` +_{{stdout}}_ +``` + +## Production mode + +``` +_{{production:stdout}}_ +``` diff --git a/examples/custom-javascript-parser/test.filter.js b/examples/custom-javascript-parser/test.filter.js new file mode 100644 index 00000000000..432ffc6131f --- /dev/null +++ b/examples/custom-javascript-parser/test.filter.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = () => { + const [major] = process.versions.node.split(".").map(Number); + + return major >= 20; +}; diff --git a/examples/custom-javascript-parser/webpack.config.js b/examples/custom-javascript-parser/webpack.config.js new file mode 100644 index 00000000000..99d038b8998 --- /dev/null +++ b/examples/custom-javascript-parser/webpack.config.js @@ -0,0 +1,92 @@ +"use strict"; + +const acornParse = require("./internals/acorn-parse.js"); +const meriyahParse = require("./internals/meriyah-parse.js"); +const oxcParse = require("./internals/oxc-parse.js"); + +/** @type {import("webpack").Configuration[]} */ +const config = [ + // oxc + { + mode: "production", + optimization: { + chunkIds: "deterministic" // To keep filename consistent between different modes (for example building only) + }, + output: { + filename: "oxc.[name].js" + }, + module: { + // Global override + parser: { + javascript: { + parse: oxcParse + } + } + // Override on the module level, only for modules which match the `test` + // rules: [ + // { + // test: /\.js$/, + // parser: { + // parse: oxcParse + // } + // } + // ] + } + }, + // meriyah + { + mode: "production", + optimization: { + chunkIds: "deterministic" // To keep filename consistent between different modes (for example building only) + }, + output: { + filename: "meriyah.[name].js" + }, + module: { + // Global override + parser: { + javascript: { + parse: meriyahParse + } + } + // Override on the module level, only for modules which match the `test` + // rules: [ + // { + // test: /\.js$/, + // parser: { + // parse: meriyahParse + // } + // } + // ] + } + }, + // acorn + { + mode: "production", + output: { + filename: "acorn.[name].js" + }, + optimization: { + chunkIds: "deterministic" // To keep filename consistent between different modes (for example building only) + }, + module: { + // Global override + parser: { + javascript: { + parse: acornParse + } + } + // Override on the module level, only for modules which match the `test` + // rules: [ + // { + // test: /\.js$/, + // parser: { + // parse: acornParse + // } + // } + // ] + } + } +]; + +module.exports = config; diff --git a/examples/custom-json-modules/README.md b/examples/custom-json-modules/README.md new file mode 100644 index 00000000000..04abab5b9b5 --- /dev/null +++ b/examples/custom-json-modules/README.md @@ -0,0 +1,266 @@ +This is a simple example that shows the usage of a custom parser for json-modules. + +Toml, yaml and json5 files can be imported like other modules without toml-loader. + +# data.toml + +```toml +title = "TOML Example" + +[owner] +name = "Tom Preston-Werner" +organization = "GitHub" +bio = "GitHub Cofounder & CEO\nLikes tater tots and beer." +dob = 1979-05-27T07:32:00Z +``` + +# data.yaml + +```yaml +title: YAML Example +owner: + name: Tom Preston-Werner + organization: GitHub + bio: |- + GitHub Cofounder & CEO + Likes tater tots and beer. + dob: 1979-05-27T07:32:00.000Z +``` + +# data.json5 + +```json5 +{ + // comment + title: "JSON5 Example", + owner: { + name: "Tom Preston-Werner", + organization: "GitHub", + bio: "GitHub Cofounder & CEO\n\ +Likes tater tots and beer.", + dob: "1979-05-27T07:32:00.000Z" + } +} +``` + +# example.js + +```javascript +import toml from "./data.toml"; +import yaml from "./data.yaml"; +import json from "./data.json5"; + +document.querySelector('#app').innerHTML = [toml, yaml, json].map(data => ` +

${data.title}

+
${data.owner.name}
+
${data.owner.organization}
+
${data.owner.bio}
+
${data.owner.dob}
+`).join('

'); +``` + +# webpack.config.js + +```javascript +"use strict"; + +const json5 = require("json5"); +const toml = require("toml"); +// @ts-expect-error no types for yamljs +const yaml = require("yamljs"); + +/** @type {import("webpack").Configuration} */ +const config = { + module: { + rules: [ + { + test: /\.toml$/, + type: "json", + parser: { + parse: toml.parse + } + }, + { + test: /\.json5$/, + type: "json", + parser: { + parse: json5.parse + } + }, + { + test: /\.yaml$/, + type: "json", + parser: { + parse: yaml.parse + } + } + ] + } +}; + +module.exports = config; +``` + +# js/output.js + +```javascript +/******/ (() => { // webpackBootstrap +/******/ "use strict"; +/******/ var __webpack_modules__ = ([ +/* 0 */, +/* 1 */ +/*!*******************!*\ + !*** ./data.toml ***! + \*******************/ +/*! default exports */ +/*! export default [not provided] [no usage info] [missing usage info prevents renaming] */ +/*! export owner [provided] [no usage info] [missing usage info prevents renaming] */ +/*! export bio [provided] [no usage info] [missing usage info prevents renaming] */ +/*! export dob [provided] [no usage info] [missing usage info prevents renaming] */ +/*! exports [not provided] [no usage info] */ +/*! export name [provided] [no usage info] [missing usage info prevents renaming] */ +/*! export organization [provided] [no usage info] [missing usage info prevents renaming] */ +/*! other exports [not provided] [no usage info] */ +/*! export title [provided] [no usage info] [missing usage info prevents renaming] */ +/*! other exports [not provided] [no usage info] */ +/*! runtime requirements: module */ +/***/ ((module) => { + +module.exports = /*#__PURE__*/JSON.parse('{"title":"TOML Example","owner":{"name":"Tom Preston-Werner","organization":"GitHub","bio":"GitHub Cofounder & CEO\\nLikes tater tots and beer.","dob":"1979-05-27T07:32:00.000Z"}}'); + +/***/ }), +/* 2 */ +/*!*******************!*\ + !*** ./data.yaml ***! + \*******************/ +/*! default exports */ +/*! export default [not provided] [no usage info] [missing usage info prevents renaming] */ +/*! export owner [provided] [no usage info] [missing usage info prevents renaming] */ +/*! export bio [provided] [no usage info] [missing usage info prevents renaming] */ +/*! export dob [provided] [no usage info] [missing usage info prevents renaming] */ +/*! exports [not provided] [no usage info] */ +/*! export name [provided] [no usage info] [missing usage info prevents renaming] */ +/*! export organization [provided] [no usage info] [missing usage info prevents renaming] */ +/*! other exports [not provided] [no usage info] */ +/*! export title [provided] [no usage info] [missing usage info prevents renaming] */ +/*! other exports [not provided] [no usage info] */ +/*! runtime requirements: module */ +/***/ ((module) => { + +module.exports = /*#__PURE__*/JSON.parse('{"title":"YAML Example","owner":{"name":"Tom Preston-Werner","organization":"GitHub","bio":"GitHub Cofounder & CEO\\nLikes tater tots and beer.","dob":"1979-05-27T07:32:00.000Z"}}'); + +/***/ }), +/* 3 */ +/*!********************!*\ + !*** ./data.json5 ***! + \********************/ +/*! default exports */ +/*! export default [not provided] [no usage info] [missing usage info prevents renaming] */ +/*! export owner [provided] [no usage info] [missing usage info prevents renaming] */ +/*! export bio [provided] [no usage info] [missing usage info prevents renaming] */ +/*! export dob [provided] [no usage info] [missing usage info prevents renaming] */ +/*! export name [provided] [no usage info] [missing usage info prevents renaming] */ +/*! export organization [provided] [no usage info] [missing usage info prevents renaming] */ +/*! other exports [not provided] [no usage info] */ +/*! export title [provided] [no usage info] [missing usage info prevents renaming] */ +/*! other exports [not provided] [no usage info] */ +/*! runtime requirements: module */ +/***/ ((module) => { + +module.exports = /*#__PURE__*/JSON.parse('{"title":"JSON5 Example","owner":{"name":"Tom Preston-Werner","organization":"GitHub","bio":"GitHub Cofounder & CEO\\nLikes tater tots and beer.","dob":"1979-05-27T07:32:00.000Z"}}'); + +/***/ }) +/******/ ]); +``` + +
/* webpack runtime code */ + +``` js +/************************************************************************/ +/******/ // The module cache +/******/ const __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ const 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; +/******/ } +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/make namespace object */ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = (exports) => { +/******/ if(Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/************************************************************************/ +``` + +
+ +``` js +let __webpack_exports__ = {}; +// This entry needs to be wrapped in an IIFE because it needs to be isolated against other modules in the chunk. +(() => { +/*!********************!*\ + !*** ./example.js ***! + \********************/ +/*! namespace exports */ +/*! exports [not provided] [no usage info] */ +/*! runtime requirements: __webpack_require__, __webpack_require__.r, __webpack_exports__, __webpack_require__.* */ +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var _data_toml__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./data.toml */ 1); +/* harmony import */ var _data_yaml__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./data.yaml */ 2); +/* harmony import */ var _data_json5__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./data.json5 */ 3); + + + + +document.querySelector('#app').innerHTML = [_data_toml__WEBPACK_IMPORTED_MODULE_0__, _data_yaml__WEBPACK_IMPORTED_MODULE_1__, _data_json5__WEBPACK_IMPORTED_MODULE_2__].map(data => ` +

${data.title}

+
${data.owner.name}
+
${data.owner.organization}
+
${data.owner.bio}
+
${data.owner.dob}
+`).join('

'); + +})(); + +/******/ })() +; +``` + +# Info + +## webpack output + +``` +asset output.js 6.11 KiB [emitted] (name: main) +chunk (runtime: main) output.js (main) 919 bytes (javascript) 241 bytes (runtime) [entry] [rendered] + > ./example.js main + dependent modules 565 bytes [dependent] 3 modules + runtime modules 241 bytes 1 module + ./example.js 354 bytes [built] [code generated] + [no exports] + [used exports unknown] + entry ./example.js main +webpack X.X.X compiled successfully +``` diff --git a/examples/custom-json-modules/build.js b/examples/custom-json-modules/build.js new file mode 100644 index 00000000000..2e93fe5a3e1 --- /dev/null +++ b/examples/custom-json-modules/build.js @@ -0,0 +1 @@ +require("../build-common"); diff --git a/examples/custom-json-modules/data.json5 b/examples/custom-json-modules/data.json5 new file mode 100644 index 00000000000..37fb5e6f70e --- /dev/null +++ b/examples/custom-json-modules/data.json5 @@ -0,0 +1,11 @@ +{ + // comment + title: "JSON5 Example", + owner: { + name: "Tom Preston-Werner", + organization: "GitHub", + bio: "GitHub Cofounder & CEO\n\ +Likes tater tots and beer.", + dob: "1979-05-27T07:32:00.000Z" + } +} diff --git a/examples/custom-json-modules/data.toml b/examples/custom-json-modules/data.toml new file mode 100644 index 00000000000..bf6c9dd3363 --- /dev/null +++ b/examples/custom-json-modules/data.toml @@ -0,0 +1,7 @@ +title = "TOML Example" + +[owner] +name = "Tom Preston-Werner" +organization = "GitHub" +bio = "GitHub Cofounder & CEO\nLikes tater tots and beer." +dob = 1979-05-27T07:32:00Z diff --git a/examples/custom-json-modules/data.yaml b/examples/custom-json-modules/data.yaml new file mode 100644 index 00000000000..d0b37096a12 --- /dev/null +++ b/examples/custom-json-modules/data.yaml @@ -0,0 +1,8 @@ +title: YAML Example +owner: + name: Tom Preston-Werner + organization: GitHub + bio: |- + GitHub Cofounder & CEO + Likes tater tots and beer. + dob: 1979-05-27T07:32:00.000Z diff --git a/examples/custom-json-modules/example.js b/examples/custom-json-modules/example.js new file mode 100644 index 00000000000..d628768fe9c --- /dev/null +++ b/examples/custom-json-modules/example.js @@ -0,0 +1,11 @@ +import toml from "./data.toml"; +import yaml from "./data.yaml"; +import json from "./data.json5"; + +document.querySelector('#app').innerHTML = [toml, yaml, json].map(data => ` +

${data.title}

+
${data.owner.name}
+
${data.owner.organization}
+
${data.owner.bio}
+
${data.owner.dob}
+`).join('

'); diff --git a/examples/custom-json-modules/index.html b/examples/custom-json-modules/index.html new file mode 100644 index 00000000000..8e22e079147 --- /dev/null +++ b/examples/custom-json-modules/index.html @@ -0,0 +1,6 @@ + + +
+ + + diff --git a/examples/custom-json-modules/template.md b/examples/custom-json-modules/template.md new file mode 100644 index 00000000000..5c5507d7d21 --- /dev/null +++ b/examples/custom-json-modules/template.md @@ -0,0 +1,47 @@ +This is a simple example that shows the usage of a custom parser for json-modules. + +Toml, yaml and json5 files can be imported like other modules without toml-loader. + +# data.toml + +```toml +_{{data.toml}}_ +``` + +# data.yaml + +```yaml +_{{data.yaml}}_ +``` + +# data.json5 + +```json5 +_{{data.json5}}_ +``` + +# example.js + +```javascript +_{{example.js}}_ +``` + +# webpack.config.js + +```javascript +_{{webpack.config.js}}_ +``` + +# js/output.js + +```javascript +_{{dist/output.js}}_ +``` + +# Info + +## webpack output + +``` +_{{stdout}}_ +``` diff --git a/examples/custom-json-modules/webpack.config.js b/examples/custom-json-modules/webpack.config.js new file mode 100644 index 00000000000..3c888bfad2f --- /dev/null +++ b/examples/custom-json-modules/webpack.config.js @@ -0,0 +1,37 @@ +"use strict"; + +const json5 = require("json5"); +const toml = require("toml"); +// @ts-expect-error no types for yamljs +const yaml = require("yamljs"); + +/** @type {import("webpack").Configuration} */ +const config = { + module: { + rules: [ + { + test: /\.toml$/, + type: "json", + parser: { + parse: toml.parse + } + }, + { + test: /\.json5$/, + type: "json", + parser: { + parse: json5.parse + } + }, + { + test: /\.yaml$/, + type: "json", + parser: { + parse: yaml.parse + } + } + ] + } +}; + +module.exports = config; diff --git a/examples/define-config/README.md b/examples/define-config/README.md new file mode 100644 index 00000000000..dce8ec30f1f --- /dev/null +++ b/examples/define-config/README.md @@ -0,0 +1,130 @@ +# defineConfig + +`webpack.defineConfig` wraps a configuration so editors give you full type-checking +and autocompletion, with no effect at runtime. It accepts a configuration object, an +array of configurations (multi-compiler), a `(env, argv) => configuration` function, +or a `Promise` resolving to any of those. + +# webpack.config.js + +```javascript +"use strict"; + +const { defineConfig } = require("webpack"); + +// `defineConfig` is an identity function at runtime; it exists so editors type-check +// the configuration. It also accepts an array, a `(env, argv) => config` function, +// or a `Promise` of any of those. +module.exports = defineConfig({ + mode: "none" +}); +``` + +# example.js + +```javascript +const value = require("./value"); + +console.log(value); +``` + +# dist/output.js + +```javascript +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ([ +/* 0 */, +/* 1 */ +/*!******************!*\ + !*** ./value.js ***! + \******************/ +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: module */ +/*! CommonJS bailout: module.exports is used directly at 1:0-14 */ +/***/ ((module) => { + +module.exports = "webpack.defineConfig keeps this configuration typed"; + + +/***/ }) +/******/ ]); +``` + +
/* webpack runtime code */ + +``` js +/************************************************************************/ +/******/ // The module cache +/******/ const __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ const 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; +/******/ } +/******/ +/************************************************************************/ +``` + +
+ +``` js +// This entry needs to be wrapped in an IIFE because it needs to be isolated against other modules in the chunk. +(() => { +/*!********************!*\ + !*** ./example.js ***! + \********************/ +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: __webpack_require__ */ +const value = __webpack_require__(/*! ./value */ 1); + +console.log(value); + +})(); + +/******/ })() +; +``` + +# Info + +## Unoptimized + +``` +asset output.js 1.8 KiB [emitted] (name: main) +chunk (runtime: main) output.js (main) 127 bytes [entry] [rendered] + > ./example.js main + dependent modules 72 bytes [dependent] 1 module + ./example.js 55 bytes [built] [code generated] + [used exports unknown] + entry ./example.js main +webpack X.X.X compiled successfully +``` + +## Production mode + +``` +asset output.js 245 bytes [emitted] [minimized] (name: main) +chunk (runtime: main) output.js (main) 127 bytes [entry] [rendered] + > ./example.js main + dependent modules 72 bytes [dependent] 1 module + ./example.js 55 bytes [built] [code generated] + [no exports used] + entry ./example.js main +webpack X.X.X compiled successfully +``` diff --git a/examples/define-config/build.js b/examples/define-config/build.js new file mode 100644 index 00000000000..2e93fe5a3e1 --- /dev/null +++ b/examples/define-config/build.js @@ -0,0 +1 @@ +require("../build-common"); diff --git a/examples/define-config/example.js b/examples/define-config/example.js new file mode 100644 index 00000000000..3160bb96b9a --- /dev/null +++ b/examples/define-config/example.js @@ -0,0 +1,3 @@ +const value = require("./value"); + +console.log(value); diff --git a/examples/define-config/template.md b/examples/define-config/template.md new file mode 100644 index 00000000000..7075b8a10dd --- /dev/null +++ b/examples/define-config/template.md @@ -0,0 +1,38 @@ +# defineConfig + +`webpack.defineConfig` wraps a configuration so editors give you full type-checking +and autocompletion, with no effect at runtime. It accepts a configuration object, an +array of configurations (multi-compiler), a `(env, argv) => configuration` function, +or a `Promise` resolving to any of those. + +# webpack.config.js + +```javascript +_{{webpack.config.js}}_ +``` + +# example.js + +```javascript +_{{example.js}}_ +``` + +# dist/output.js + +```javascript +_{{dist/output.js}}_ +``` + +# Info + +## Unoptimized + +``` +_{{stdout}}_ +``` + +## Production mode + +``` +_{{production:stdout}}_ +``` diff --git a/examples/define-config/value.js b/examples/define-config/value.js new file mode 100644 index 00000000000..6d280b18537 --- /dev/null +++ b/examples/define-config/value.js @@ -0,0 +1 @@ +module.exports = "webpack.defineConfig keeps this configuration typed"; diff --git a/examples/define-config/webpack.config.js b/examples/define-config/webpack.config.js new file mode 100644 index 00000000000..3aa2eda04ab --- /dev/null +++ b/examples/define-config/webpack.config.js @@ -0,0 +1,10 @@ +"use strict"; + +const { defineConfig } = require("webpack"); + +// `defineConfig` is an identity function at runtime; it exists so editors type-check +// the configuration. It also accepts an array, a `(env, argv) => config` function, +// or a `Promise` of any of those. +module.exports = defineConfig({ + mode: "none" +}); diff --git a/examples/dll-app-and-vendor/0-vendor/README.md b/examples/dll-app-and-vendor/0-vendor/README.md index 0db675ca871..a67f09e133f 100644 --- a/examples/dll-app-and-vendor/0-vendor/README.md +++ b/examples/dll-app-and-vendor/0-vendor/README.md @@ -4,35 +4,40 @@ It's built separately from the app part. The vendors dll is only built when the The DllPlugin in combination with the `output.library` option exposes the internal require function as global variable in the target environment. -A manifest is creates which includes mappings from module names to internal ids. +A manifest is created which includes mappings from module names to internal ids. ### webpack.config.js -``` javascript -var path = require("path"); -var webpack = require("../../../"); +```javascript +"use strict"; + +const path = require("path"); +const webpack = require("../../../"); -module.exports = { - // mode: "development || "production", +/** @type {import("webpack").Configuration} */ +const config = { + // mode: "development" || "production", context: __dirname, entry: ["example-vendor"], output: { - filename: "vendor.js", // best use [hash] here too + filename: "vendor.js", // best use [fullhash] here too path: path.resolve(__dirname, "dist"), - library: "vendor_lib_[hash]" + library: "vendor_lib_[fullhash]" }, plugins: [ new webpack.DllPlugin({ - name: "vendor_lib_[hash]", + name: "vendor_lib_[fullhash]", path: path.resolve(__dirname, "dist/vendor-manifest.json") }) ] }; + +module.exports = config; ``` # example-vendor -``` javascript +```javascript export function square(n) { return n * n; } @@ -40,124 +45,132 @@ export function square(n) { # dist/vendor.js -``` javascript -var vendor_lib_f3fbcfb4ec389ba5bbf0 = +```javascript +var vendor_lib_e53dcd1aae7c233a8156; +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ([ +/* 0 */ +/*!****************!*\ + !*** dll main ***! + \****************/ +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: __webpack_require__, module */ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +module.exports = __webpack_require__; + +/***/ }), +/* 1 */ +/*!*****************************************!*\ + !*** ../node_modules/example-vendor.js ***! + \*****************************************/ +/*! namespace exports */ +/*! export square [provided] [no usage info] [missing usage info prevents renaming] */ +/*! other exports [not provided] [no usage info] */ +/*! runtime requirements: __webpack_require__.r, __webpack_exports__, __webpack_require__.d, __webpack_require__.* */ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ square: () => (/* binding */ square) +/* harmony export */ }); +function square(n) { + return n * n; +} + + +/***/ }) +/******/ ]); ``` -
/******/ (function(modules) { /* webpackBootstrap */ }) + +
/* webpack runtime code */ ``` js -/******/ (function(modules) { // webpackBootstrap +/************************************************************************/ /******/ // The module cache -/******/ var installedModules = {}; -/******/ +/******/ const __webpack_module_cache__ = {}; +/******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { -/******/ /******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) { -/******/ return installedModules[moduleId].exports; +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, +/******/ const module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed /******/ exports: {} /******/ }; -/******/ +/******/ /******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ /******/ // 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, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/define property getters */ +/******/ // define getter/value functions for harmony exports +/******/ __webpack_require__.d = (exports, definition) => { +/******/ if(Array.isArray(definition)) { +/******/ var i = 0; +/******/ while(i < definition.length) { +/******/ var key = definition[i++]; +/******/ var binding = definition[i++]; +/******/ if(!__webpack_require__.o(exports, key)) { +/******/ if(binding === 0) { +/******/ Object.defineProperty(exports, key, { enumerable: true, value: definition[i++] }); +/******/ } else { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: binding }); +/******/ } +/******/ } else if(binding === 0) { i++; } +/******/ } +/******/ } else { +/******/ for(var key in definition) { +/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); +/******/ } +/******/ } /******/ } /******/ }; -/******/ +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)); +/******/ +/******/ /* webpack/runtime/make namespace object */ /******/ // define __esModule on exports -/******/ __webpack_require__.r = function(exports) { +/******/ __webpack_require__.r = (exports) => { +/******/ if(Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } /******/ Object.defineProperty(exports, '__esModule', { value: true }); /******/ }; -/******/ -/******/ // 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 = "dist/"; -/******/ -/******/ -/******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 0); -/******/ }) +/******/ /************************************************************************/ ```
``` js -/******/ ([ -/* 0 */ -/*!****************!*\ - !*** dll main ***! - \****************/ -/*! no static exports found */ -/*! all exports used */ -/***/ (function(module, exports, __webpack_require__) { - -module.exports = __webpack_require__; - -/***/ }), -/* 1 */ -/*!*****************************************!*\ - !*** ../node_modules/example-vendor.js ***! - \*****************************************/ -/*! exports provided: square */ -/*! all exports used */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "square", function() { return square; }); -function square(n) { - return n * n; -} - - -/***/ }) -/******/ ]); +/******/ +/******/ // startup +/******/ // Load entry module and return exports +/******/ // This entry module doesn't tell about it's top-level declarations so it can't be inlined +/******/ let __webpack_exports__ = __webpack_require__(0); +/******/ vendor_lib_e53dcd1aae7c233a8156 = __webpack_exports__; +/******/ +/******/ })() +; ``` # dist/vendor-manifest.json -``` javascript -{"name":"vendor_lib_f3fbcfb4ec389ba5bbf0","content":{"../node_modules/example-vendor.js":{"id":1,"buildMeta":{"exportsType":"namespace","providedExports":["square"]}}}} +```javascript +{"name":"vendor_lib_e53dcd1aae7c233a8156","content":{"../node_modules/example-vendor.js":{"id":1,"buildMeta":{"exportsType":"namespace"},"exports":["square"]}}} ``` # Info @@ -165,32 +178,28 @@ function square(n) { ## Unoptimized ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 - Asset Size Chunks Chunk Names -vendor.js 3.32 KiB 0 [emitted] main -Entrypoint main = vendor.js -chunk {0} vendor.js (main) 60 bytes [entry] [rendered] - > main - [0] dll main 12 bytes {0} [built] - dll entry - - + 1 hidden module +asset vendor.js 4.14 KiB [emitted] (name: main) +chunk (runtime: main) vendor.js (main) 57 bytes (javascript) 1.08 KiB (runtime) [entry] [rendered] + > main + runtime modules 1.08 KiB 3 modules + dependent modules 45 bytes [dependent] 1 module + dll main 12 bytes [built] [code generated] + [used exports unknown] + dll entry + used as library export +webpack X.X.X compiled successfully ``` ## Production mode ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 - Asset Size Chunks Chunk Names -vendor.js 704 bytes 0 [emitted] main -Entrypoint main = vendor.js -chunk {0} vendor.js (main) 60 bytes [entry] [rendered] - > main - [1] dll main 12 bytes {0} [built] - dll entry - - + 1 hidden module +asset vendor.js 832 bytes [emitted] [minimized] (name: main) +chunk (runtime: main) vendor.js (main) 57 bytes (javascript) 1.08 KiB (runtime) [entry] [rendered] + > main + runtime modules 1.08 KiB 3 modules + dependent modules 45 bytes [dependent] 1 module + dll main 12 bytes [built] [code generated] + dll entry + used as library export +webpack X.X.X compiled successfully ``` - diff --git a/examples/dll-app-and-vendor/0-vendor/template.md b/examples/dll-app-and-vendor/0-vendor/template.md index 662ea88e909..73abe44e338 100644 --- a/examples/dll-app-and-vendor/0-vendor/template.md +++ b/examples/dll-app-and-vendor/0-vendor/template.md @@ -4,30 +4,30 @@ It's built separately from the app part. The vendors dll is only built when the The DllPlugin in combination with the `output.library` option exposes the internal require function as global variable in the target environment. -A manifest is creates which includes mappings from module names to internal ids. +A manifest is created which includes mappings from module names to internal ids. ### webpack.config.js -``` javascript -{{webpack.config.js}} +```javascript +_{{webpack.config.js}}_ ``` # example-vendor -``` javascript -{{../node_modules/example-vendor.js}} +```javascript +_{{../node_modules/example-vendor.js}}_ ``` # dist/vendor.js -``` javascript -{{dist/vendor.js}} +```javascript +_{{dist/vendor.js}}_ ``` # dist/vendor-manifest.json -``` javascript -{{dist/vendor-manifest.json}} +```javascript +_{{dist/vendor-manifest.json}}_ ``` # Info @@ -35,12 +35,11 @@ A manifest is creates which includes mappings from module names to internal ids. ## Unoptimized ``` -{{stdout}} +_{{stdout}}_ ``` ## Production mode ``` -{{production:stdout}} +_{{production:stdout}}_ ``` - diff --git a/examples/dll-app-and-vendor/0-vendor/webpack.config.js b/examples/dll-app-and-vendor/0-vendor/webpack.config.js index ec5f19b19c2..d0ebb75e4c7 100644 --- a/examples/dll-app-and-vendor/0-vendor/webpack.config.js +++ b/examples/dll-app-and-vendor/0-vendor/webpack.config.js @@ -1,19 +1,24 @@ -var path = require("path"); -var webpack = require("../../../"); +"use strict"; -module.exports = { - // mode: "development || "production", +const path = require("path"); +const webpack = require("../../../"); + +/** @type {import("webpack").Configuration} */ +const config = { + // mode: "development" || "production", context: __dirname, entry: ["example-vendor"], output: { - filename: "vendor.js", // best use [hash] here too + filename: "vendor.js", // best use [fullhash] here too path: path.resolve(__dirname, "dist"), - library: "vendor_lib_[hash]" + library: "vendor_lib_[fullhash]" }, plugins: [ new webpack.DllPlugin({ - name: "vendor_lib_[hash]", + name: "vendor_lib_[fullhash]", path: path.resolve(__dirname, "dist/vendor-manifest.json") }) ] }; + +module.exports = config; diff --git a/examples/dll-app-and-vendor/1-app/README.md b/examples/dll-app-and-vendor/1-app/README.md index d8b1e628fe2..743504cbdca 100644 --- a/examples/dll-app-and-vendor/1-app/README.md +++ b/examples/dll-app-and-vendor/1-app/README.md @@ -4,11 +4,16 @@ The previously built vendor dll is used. The DllReferencePlugin reads the conten # webpack.config.js -``` javascript -var path = require("path"); -var webpack = require("../../../"); +```javascript +"use strict"; + +const path = require("path"); +const webpack = require("../../../"); + +const manifest = "../0-vendor/dist/vendor-manifest.json"; -module.exports = { +/** @type {import("webpack").Configuration} */ +const config = { // mode: "development" || "production", context: __dirname, entry: "./example-app", @@ -18,16 +23,17 @@ module.exports = { }, plugins: [ new webpack.DllReferencePlugin({ - context: ".", - manifest: require("../0-vendor/dist/vendor-manifest.json") // eslint-disable-line + manifest: require(manifest) }) ] }; + +module.exports = config; ``` # example-app.js -``` javascript +```javascript import { square } from "example-vendor"; console.log(square(7)); @@ -36,7 +42,7 @@ console.log(new square(7)); # example.html -``` html +```html @@ -48,124 +54,104 @@ console.log(new square(7)); # dist/app.js -
/******/ (function(modules) { /* webpackBootstrap */ }) +```javascript +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ([ +/* 0 */, +/* 1 */ +/*!******************************************************************************************************!*\ + !*** delegated ../node_modules/example-vendor.js from dll-reference vendor_lib_e53dcd1aae7c233a8156 ***! + \******************************************************************************************************/ +/*! namespace exports */ +/*! export square [provided] [no usage info] [provision prevents renaming (no use info)] */ +/*! other exports [not provided] [no usage info] */ +/*! runtime requirements: module, __webpack_require__ */ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +module.exports = (__webpack_require__(/*! dll-reference vendor_lib_e53dcd1aae7c233a8156 */ 2))(1); -``` javascript -/******/ (function(modules) { // webpackBootstrap +/***/ }), +/* 2 */ +/*!**************************************************!*\ + !*** external "vendor_lib_e53dcd1aae7c233a8156" ***! + \**************************************************/ +/*! dynamic exports */ +/*! exports [maybe provided (runtime-defined)] [no usage info] */ +/*! runtime requirements: module */ +/***/ ((module) => { + +"use strict"; +module.exports = vendor_lib_e53dcd1aae7c233a8156; + +/***/ }) +/******/ ]); +``` + +
/* webpack runtime code */ + +``` js +/************************************************************************/ /******/ // The module cache -/******/ var installedModules = {}; -/******/ +/******/ const __webpack_module_cache__ = {}; +/******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { -/******/ /******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) { -/******/ return installedModules[moduleId].exports; +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, +/******/ const module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed /******/ exports: {} /******/ }; -/******/ +/******/ /******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ /******/ // 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, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); -/******/ } -/******/ }; -/******/ +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/make namespace object */ /******/ // define __esModule on exports -/******/ __webpack_require__.r = function(exports) { +/******/ __webpack_require__.r = (exports) => { +/******/ if(Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } /******/ Object.defineProperty(exports, '__esModule', { value: true }); /******/ }; -/******/ -/******/ // 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 = "dist/"; -/******/ -/******/ -/******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 0); -/******/ }) +/******/ /************************************************************************/ ```
-``` javascript -/******/ ([ -/* 0 */ +``` js +let __webpack_exports__ = {}; +// This entry needs to be wrapped in an IIFE because it needs to be in strict mode. +(() => { +"use strict"; /*!************************!*\ !*** ./example-app.js ***! \************************/ -/*! no exports provided */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; +/*! namespace exports */ +/*! exports [not provided] [no usage info] */ +/*! runtime requirements: __webpack_require__, __webpack_require__.r, __webpack_exports__, __webpack_require__.* */ __webpack_require__.r(__webpack_exports__); /* harmony import */ var example_vendor__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! example-vendor */ 1); -console.log(Object(example_vendor__WEBPACK_IMPORTED_MODULE_0__["square"])(7)); -console.log(new example_vendor__WEBPACK_IMPORTED_MODULE_0__["square"](7)); - - -/***/ }), -/* 1 */ -/*!******************************************************************************************************!*\ - !*** delegated ../node_modules/example-vendor.js from dll-reference vendor_lib_f3fbcfb4ec389ba5bbf0 ***! - \******************************************************************************************************/ -/*! exports provided: square */ -/***/ (function(module, exports, __webpack_require__) { - -module.exports = (__webpack_require__(/*! dll-reference vendor_lib_f3fbcfb4ec389ba5bbf0 */ 2))(1); - -/***/ }), -/* 2 */ -/*!**************************************************!*\ - !*** external "vendor_lib_f3fbcfb4ec389ba5bbf0" ***! - \**************************************************/ -/*! no static exports found */ -/***/ (function(module, exports) { +console.log((0,example_vendor__WEBPACK_IMPORTED_MODULE_0__.square)(7)); +console.log(new example_vendor__WEBPACK_IMPORTED_MODULE_0__.square(7)); -module.exports = vendor_lib_f3fbcfb4ec389ba5bbf0; +})(); -/***/ }) -/******/ ]); +/******/ })() +; ``` # Info @@ -173,45 +159,30 @@ module.exports = vendor_lib_f3fbcfb4ec389ba5bbf0; ## Unoptimized ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 - Asset Size Chunks Chunk Names -app.js 3.9 KiB 0 [emitted] main -Entrypoint main = app.js -chunk {0} app.js (main) 182 bytes [entry] [rendered] - > ./example-app main - [0] ./example-app.js 98 bytes {0} [built] - [no exports] - single entry ./example-app main - [1] delegated ../node_modules/example-vendor.js from dll-reference vendor_lib_f3fbcfb4ec389ba5bbf0 42 bytes {0} [built] - [exports: square] - harmony side effect evaluation example-vendor [0] ./example-app.js 1:0-40 - harmony import specifier example-vendor [0] ./example-app.js 3:12-18 - harmony import specifier example-vendor [0] ./example-app.js 4:16-22 - [2] external "vendor_lib_f3fbcfb4ec389ba5bbf0" 42 bytes {0} [built] - delegated source dll-reference vendor_lib_f3fbcfb4ec389ba5bbf0 [1] delegated ../node_modules/example-vendor.js from dll-reference vendor_lib_f3fbcfb4ec389ba5bbf0 +asset app.js 3.37 KiB [emitted] (name: main) +chunk (runtime: main) app.js (main) 178 bytes (javascript) 241 bytes (runtime) [entry] [rendered] + > ./example-app main + dependent modules 84 bytes [dependent] 2 modules + runtime modules 241 bytes 1 module + ./example-app.js 94 bytes [built] [code generated] + [no exports] + [used exports unknown] + entry ./example-app main +webpack X.X.X compiled successfully ``` ## Production mode ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 - Asset Size Chunks Chunk Names -app.js 736 bytes 0 [emitted] main -Entrypoint main = app.js -chunk {0} app.js (main) 182 bytes [entry] [rendered] - > ./example-app main - [0] delegated ../node_modules/example-vendor.js from dll-reference vendor_lib_f3fbcfb4ec389ba5bbf0 42 bytes {0} [built] - [exports: square] - harmony side effect evaluation example-vendor [2] ./example-app.js 1:0-40 - harmony import specifier example-vendor [2] ./example-app.js 3:12-18 - harmony import specifier example-vendor [2] ./example-app.js 4:16-22 - [1] external "vendor_lib_f3fbcfb4ec389ba5bbf0" 42 bytes {0} [built] - delegated source dll-reference vendor_lib_f3fbcfb4ec389ba5bbf0 [0] delegated ../node_modules/example-vendor.js from dll-reference vendor_lib_f3fbcfb4ec389ba5bbf0 - [2] ./example-app.js 98 bytes {0} [built] - [no exports] - single entry ./example-app main +asset app.js 337 bytes [emitted] [minimized] (name: main) +chunk (runtime: main) app.js (main) 178 bytes [entry] [rendered] + > ./example-app main + dependent modules 84 bytes [dependent] 2 modules + ./example-app.js 94 bytes [built] [code generated] + [no exports] + [no exports used] + entry ./example-app main +webpack X.X.X compiled successfully ``` + + + + + + +

Content-Security-Policy

+ + + + + +``` + +# src/app.js + +```javascript +document.querySelector("h1").classList.add("ready"); +console.log("bundled + inlined script, covered by a CSP hash"); +``` + +# src/styles.css + +```css +h1 { + color: #2b3a42; +} + +h1.ready { + color: #8ed6fb; +} +``` + +# dist/index.html + +The emitted page: every script/style inlined, with the generated CSP `` +carrying a hash for each one. + +```html + + + + + CSP + + + + + + + + +

Content-Security-Policy

+ + + + + +``` + +# Info + +## Unoptimized + +``` +asset index.js 2.64 KiB [emitted] (name: index) +asset index.html 1.59 KiB [emitted] (auxiliary name: index) +Entrypoint index 2.64 KiB (1.59 KiB) = index.js 1 auxiliary asset +Entrypoint __html_6d047296_1 = +Entrypoint __html_6d047296_0 = +chunk (runtime: __html_6d047296_0) (__html_6d047296_0) 55 bytes (css) 0 bytes (runtime) [entry] [rendered] + > ./styles.css __html_6d047296_0 + runtime modules 0 bytes 1 module + css ./src/styles.css 55 bytes [built] [code generated] + [no exports] + [used exports unknown] + entry ./styles.css __html_6d047296_0 +chunk (runtime: __html_6d047296_1) (__html_6d047296_1) 117 bytes [entry] [rendered] + > ./app.js __html_6d047296_1 + ./src/app.js 117 bytes [built] [code generated] + [used exports unknown] + entry ./app.js __html_6d047296_1 +chunk (runtime: index) index.js (index) 47 bytes (css-text) 559 bytes (javascript) 549 bytes (html) [entry] [rendered] + > ./src/index.html index + dependent modules 47 bytes [dependent] 1 module + ./src/index.html 559 bytes (javascript) 549 bytes (html) [built] [code generated] + [exports: default] + [used exports unknown] + entry ./src/index.html index +webpack X.X.X compiled successfully +``` + +## Production mode + +``` +asset index.js 776 bytes [emitted] [minimized] (name: index) +asset index.html 569 bytes [emitted] [minimized] (auxiliary name: index) +asset __html_6d047296_0.css 40 bytes [emitted] [minimized] (name: __html_6d047296_0) +Entrypoint index 776 bytes (569 bytes) = index.js 1 auxiliary asset +Entrypoint __html_6d047296_1 = +Entrypoint __html_6d047296_0 40 bytes = __html_6d047296_0.css +chunk (runtime: index) index.js (index) 47 bytes (css-text) 559 bytes (javascript) 549 bytes (html) [entry] [rendered] + > ./src/index.html index + dependent modules 47 bytes [dependent] 1 module + ./src/index.html 559 bytes (javascript) 549 bytes (html) [built] [code generated] + [exports: default] + [no exports used] + entry ./src/index.html index +chunk (runtime: __html_6d047296_0) __html_6d047296_0.css (__html_6d047296_0) 55 bytes (css) 0 bytes (runtime) [entry] [rendered] + > ./styles.css __html_6d047296_0 + runtime modules 0 bytes 1 module + css ./src/styles.css 55 bytes [built] [code generated] + [no exports] + [no exports used] + entry ./styles.css __html_6d047296_0 +chunk (runtime: __html_6d047296_1) (__html_6d047296_1) 117 bytes [entry] [rendered] + > ./app.js __html_6d047296_1 + ./src/app.js 117 bytes [built] [code generated] + [no exports used] + entry ./app.js __html_6d047296_1 +webpack X.X.X compiled successfully +``` diff --git a/examples/html-csp/build.js b/examples/html-csp/build.js new file mode 100644 index 00000000000..5768b058787 --- /dev/null +++ b/examples/html-csp/build.js @@ -0,0 +1,3 @@ +global.NO_TARGET_ARGS = true; +global.NO_PUBLIC_PATH = true; +require("../build-common"); diff --git a/examples/html-csp/src/app.js b/examples/html-csp/src/app.js new file mode 100644 index 00000000000..453cf977ac4 --- /dev/null +++ b/examples/html-csp/src/app.js @@ -0,0 +1,2 @@ +document.querySelector("h1").classList.add("ready"); +console.log("bundled + inlined script, covered by a CSP hash"); diff --git a/examples/html-csp/src/index.html b/examples/html-csp/src/index.html new file mode 100644 index 00000000000..c9544687083 --- /dev/null +++ b/examples/html-csp/src/index.html @@ -0,0 +1,24 @@ + + + + + CSP + + + + + + + + +

Content-Security-Policy

+ + + + + diff --git a/examples/html-csp/src/styles.css b/examples/html-csp/src/styles.css new file mode 100644 index 00000000000..6eca966dd6c --- /dev/null +++ b/examples/html-csp/src/styles.css @@ -0,0 +1,7 @@ +h1 { + color: #2b3a42; +} + +h1.ready { + color: #8ed6fb; +} diff --git a/examples/html-csp/template.md b/examples/html-csp/template.md new file mode 100644 index 00000000000..e4afcf1a3a0 --- /dev/null +++ b/examples/html-csp/template.md @@ -0,0 +1,66 @@ +This example demonstrates automatic **Content-Security-Policy** generation for +the experimental HTML modules support (`experiments.html`), via +`output.html.csp`. + +Two `output.html` options combine: + +- **`inline: true`** inlines every emitted chunk into the page, so the external + `` and ` + + +``` + +# src/app.js + +```javascript +// Modern source. In a real project the legacy build runs this through a +// transpiling loader (babel/swc) with old `targets`; the modern build ships it +// as-is over ` + + +

Differential serving

+ + + + +``` + +# Info + +## Unoptimized + +``` +modern: + asset page.modern.js 2.02 KiB [emitted] [javascript module] (name: page) + asset index.html 759 bytes [emitted] (auxiliary name: page) + asset __html_6d047296_0.modern.js 449 bytes [emitted] [javascript module] (name: __html_6d047296_0) + chunk (runtime: __html_6d047296_0) __html_6d047296_0.modern.js (__html_6d047296_0) 300 bytes [entry] [rendered] + > ./app.js __html_6d047296_0 + ./src/app.js 300 bytes [built] [code generated] + [used exports unknown] + entry ./app.js __html_6d047296_0 + chunk (runtime: page) page.modern.js (page) 369 bytes (javascript) 359 bytes (html) [entry] [rendered] + > ./src/index.html page + ./src/index.html 369 bytes (javascript) 359 bytes (html) [built] [code generated] + [exports: default] + [used exports unknown] + entry ./src/index.html page + modern (webpack X.X.X) compiled successfully + +legacy: + asset app.legacy.js 502 bytes [emitted] (name: app) + chunk (runtime: app) app.legacy.js (app) 300 bytes [entry] [rendered] + > ./src/app.js app + ./src/app.js 300 bytes [built] [code generated] + [used exports unknown] + entry ./src/app.js app + legacy (webpack X.X.X) compiled successfully +``` + +## Production mode + +``` +modern: + asset index.html 598 bytes [emitted] [minimized] (auxiliary name: page) + asset page.modern.js 582 bytes [emitted] [javascript module] [minimized] (name: page) + asset __html_6d047296_0.modern.js 90 bytes [emitted] [javascript module] [minimized] (name: __html_6d047296_0) + chunk (runtime: __html_6d047296_0) __html_6d047296_0.modern.js (__html_6d047296_0) 300 bytes [entry] [rendered] + > ./app.js __html_6d047296_0 + ./src/app.js 300 bytes [built] [code generated] + [no exports used] + entry ./app.js __html_6d047296_0 + chunk (runtime: page) page.modern.js (page) 369 bytes (javascript) 359 bytes (html) [entry] [rendered] + > ./src/index.html page + ./src/index.html 369 bytes (javascript) 359 bytes (html) [built] [code generated] + [exports: default] + [no exports used] + entry ./src/index.html page + modern (webpack X.X.X) compiled successfully + +legacy: + asset app.legacy.js 100 bytes [emitted] [minimized] (name: app) + chunk (runtime: app) app.legacy.js (app) 300 bytes [entry] [rendered] + > ./src/app.js app + ./src/app.js 300 bytes [built] [code generated] + [no exports used] + entry ./src/app.js app + legacy (webpack X.X.X) compiled successfully +``` diff --git a/examples/html-module-nomodule/build.js b/examples/html-module-nomodule/build.js new file mode 100644 index 00000000000..5768b058787 --- /dev/null +++ b/examples/html-module-nomodule/build.js @@ -0,0 +1,3 @@ +global.NO_TARGET_ARGS = true; +global.NO_PUBLIC_PATH = true; +require("../build-common"); diff --git a/examples/html-module-nomodule/src/app.js b/examples/html-module-nomodule/src/app.js new file mode 100644 index 00000000000..d348da4fe3d --- /dev/null +++ b/examples/html-module-nomodule/src/app.js @@ -0,0 +1,6 @@ +// Modern source. In a real project the legacy build runs this through a +// transpiling loader (babel/swc) with old `targets`; the modern build ships it +// as-is over ` + + diff --git a/examples/html-module-nomodule/template.md b/examples/html-module-nomodule/template.md new file mode 100644 index 00000000000..60c161d92df --- /dev/null +++ b/examples/html-module-nomodule/template.md @@ -0,0 +1,63 @@ +This example shows **module / nomodule differential serving** on top of the +experimental HTML modules support (`experiments.html`), coordinated with the +`output.html` `injectTags` hook. + +Two builds run as a `MultiCompiler` array: + +- **`modern`** — `output.module: true`, so the HTML entry's + ` + <%~ include("footer", { year: it.year }) %> + + +``` + +# src/footer.eta + +An Eta partial pulled in via `include(...)`. Registered as a build dependency +through `addDependency`, not bundled as a module. + +``` +
Built with webpack — <%= it.year %>
+``` + +# src/app.js + +```javascript +// This script is referenced by ` +
Built with webpack — 2025
+ + +``` + +# dist/special.html + +```html + + + + + Special + + +

Special page

+

Unescaped markup injected from the template data.

+ +``` + +# Info + +## Unoptimized + +``` +assets by path *.js 4.57 KiB + asset index.js 2.18 KiB [emitted] (name: index) + asset special.js 1.95 KiB [emitted] (name: special) + asset __html_6d047296_0.js 445 bytes [emitted] (name: __html_6d047296_0) +assets by chunk 15 KiB (auxiliary name: index) + asset 89a353e9c515885abd8e.png 14.6 KiB [emitted] [immutable] [from: src/logo.png] (auxiliary name: index) + asset index.html 407 bytes [emitted] (auxiliary name: index) +asset special.html 218 bytes [emitted] (auxiliary name: special) +chunk (runtime: __html_6d047296_0) __html_6d047296_0.js (__html_6d047296_0) 243 bytes [entry] [rendered] + > ./app.js __html_6d047296_0 + ./src/app.js 243 bytes [built] [code generated] + [used exports unknown] + entry ./app.js __html_6d047296_0 +chunk (runtime: index) index.js (index) 14.6 KiB (asset) 42 bytes (asset-url) 391 bytes (javascript) 381 bytes (html) [entry] [rendered] + > ./src/index.html index + dependent modules 14.6 KiB (asset) 42 bytes (asset-url) [dependent] 1 module + ./src/index.html 391 bytes (javascript) 381 bytes (html) [built] [code generated] + [exports: default] + [used exports unknown] + entry ./src/index.html index +chunk (runtime: special) special.js (special) 228 bytes (javascript) 218 bytes (html) [entry] [rendered] + > ./src/special.html special + ./src/special.html 228 bytes (javascript) 218 bytes (html) [built] [code generated] + [exports: default] + [used exports unknown] + entry ./src/special.html special +webpack X.X.X compiled successfully +``` + +## Production mode + +``` +assets by path *.js 1.11 KiB + asset index.js 658 bytes [emitted] [minimized] (name: index) + asset special.js 431 bytes [emitted] [minimized] (name: special) + asset __html_6d047296_0.js 46 bytes [emitted] [minimized] (name: __html_6d047296_0) +assets by chunk 14.9 KiB (auxiliary name: index) + asset 89a353e9c515885abd8e.png 14.6 KiB [emitted] [immutable] [from: src/logo.png] (auxiliary name: index) + asset index.html 378 bytes [emitted] [minimized] (auxiliary name: index) +asset special.html 199 bytes [emitted] [minimized] (auxiliary name: special) +chunk (runtime: index) index.js (index) 14.6 KiB (asset) 42 bytes (asset-url) 391 bytes (javascript) 381 bytes (html) [entry] [rendered] + > ./src/index.html index + dependent modules 14.6 KiB (asset) 42 bytes (asset-url) [dependent] 1 module + ./src/index.html 391 bytes (javascript) 381 bytes (html) [built] [code generated] + [exports: default] + [no exports used] + entry ./src/index.html index +chunk (runtime: __html_6d047296_0) __html_6d047296_0.js (__html_6d047296_0) 243 bytes [entry] [rendered] + > ./app.js __html_6d047296_0 + ./src/app.js 243 bytes [built] [code generated] + [no exports used] + entry ./app.js __html_6d047296_0 +chunk (runtime: special) special.js (special) 228 bytes (javascript) 218 bytes (html) [entry] [rendered] + > ./src/special.html special + ./src/special.html 228 bytes (javascript) 218 bytes (html) [built] [code generated] + [exports: default] + [no exports used] + entry ./src/special.html special +webpack X.X.X compiled successfully +``` diff --git a/examples/html-template/build.js b/examples/html-template/build.js new file mode 100644 index 00000000000..5768b058787 --- /dev/null +++ b/examples/html-template/build.js @@ -0,0 +1,3 @@ +global.NO_TARGET_ARGS = true; +global.NO_PUBLIC_PATH = true; +require("../build-common"); diff --git a/examples/html-template/src/app.js b/examples/html-template/src/app.js new file mode 100644 index 00000000000..34b3148b7a2 --- /dev/null +++ b/examples/html-template/src/app.js @@ -0,0 +1,4 @@ +// This script is referenced by ` + <%~ include("footer", { year: it.year }) %> + + diff --git a/examples/html-template/src/logo.png b/examples/html-template/src/logo.png new file mode 100644 index 00000000000..fb53b9dedd3 Binary files /dev/null and b/examples/html-template/src/logo.png differ diff --git a/examples/html-template/src/special.html b/examples/html-template/src/special.html new file mode 100644 index 00000000000..6a0337bfde6 --- /dev/null +++ b/examples/html-template/src/special.html @@ -0,0 +1,11 @@ + + + + + {{= it.title }} + + +

{{= it.heading }}

+ {{= it.raw }} + + diff --git a/examples/html-template/template.md b/examples/html-template/template.md new file mode 100644 index 00000000000..6357a5b9b18 --- /dev/null +++ b/examples/html-template/template.md @@ -0,0 +1,83 @@ +This example demonstrates the experimental HTML modules support together with +the `module.parser.html.template` option, using [Eta](https://eta.js.org/) as +the templating engine. `template` compiles each HTML entry to plain HTML +**before** webpack parses it, so the URLs the template emits (the `` +source and the ` + + +

transformTags

+ + +``` + +# src/app.js + +```javascript +document.querySelector("h1").classList.add("ready"); +``` + +# src/styles.css + +```css +h1 { + font-family: sans-serif; + color: #2b3a42; +} + +h1.ready { + color: #8ed6fb; +} +``` + +# dist/index.html + +The emitted page: the debug `` gone, `crossorigin` on the stylesheet, and +the script moved to the end of `` as ` + + +``` + +# Info + +## Unoptimized + +``` +assets by path *.js 2.56 KiB + asset index.js 2.31 KiB [emitted] (name: index) + asset __html_6d047296_1.js 255 bytes [emitted] (name: __html_6d047296_1) +asset index.html 570 bytes [emitted] (auxiliary name: index) +asset __html_6d047296_0.css 183 bytes [emitted] (name: __html_6d047296_0) +chunk (runtime: __html_6d047296_0) __html_6d047296_0.css (__html_6d047296_0) 81 bytes (css) 0 bytes (runtime) [entry] [rendered] + > ./styles.css __html_6d047296_0 + runtime modules 0 bytes 1 module + css ./src/styles.css 81 bytes [built] [code generated] + [no exports] + [used exports unknown] + entry ./styles.css __html_6d047296_0 +chunk (runtime: __html_6d047296_1) __html_6d047296_1.js (__html_6d047296_1) 53 bytes [entry] [rendered] + > ./app.js __html_6d047296_1 + ./src/app.js 53 bytes [built] [code generated] + [used exports unknown] + entry ./app.js __html_6d047296_1 +chunk (runtime: index) index.js (index) 540 bytes (javascript) 530 bytes (html) [entry] [rendered] + > ./src/index.html index + ./src/index.html 540 bytes (javascript) 530 bytes (html) [built] [code generated] + [exports: default] + [used exports unknown] + entry ./src/index.html index +webpack X.X.X compiled successfully +``` + +## Production mode + +``` +assets by path *.js 857 bytes + asset index.js 805 bytes [emitted] [minimized] (name: index) + asset __html_6d047296_1.js 52 bytes [emitted] [minimized] (name: __html_6d047296_1) +asset index.html 289 bytes [emitted] [minimized] (auxiliary name: index) +asset __html_6d047296_0.css 63 bytes [emitted] [minimized] (name: __html_6d047296_0) +chunk (runtime: index) index.js (index) 540 bytes (javascript) 530 bytes (html) [entry] [rendered] + > ./src/index.html index + ./src/index.html 540 bytes (javascript) 530 bytes (html) [built] [code generated] + [exports: default] + [no exports used] + entry ./src/index.html index +chunk (runtime: __html_6d047296_0) __html_6d047296_0.css (__html_6d047296_0) 81 bytes (css) 0 bytes (runtime) [entry] [rendered] + > ./styles.css __html_6d047296_0 + runtime modules 0 bytes 1 module + css ./src/styles.css 81 bytes [built] [code generated] + [no exports] + [no exports used] + entry ./styles.css __html_6d047296_0 +chunk (runtime: __html_6d047296_1) __html_6d047296_1.js (__html_6d047296_1) 53 bytes [entry] [rendered] + > ./app.js __html_6d047296_1 + ./src/app.js 53 bytes [built] [code generated] + [no exports used] + entry ./app.js __html_6d047296_1 +webpack X.X.X compiled successfully +``` diff --git a/examples/html-transform-tags/build.js b/examples/html-transform-tags/build.js new file mode 100644 index 00000000000..5768b058787 --- /dev/null +++ b/examples/html-transform-tags/build.js @@ -0,0 +1,3 @@ +global.NO_TARGET_ARGS = true; +global.NO_PUBLIC_PATH = true; +require("../build-common"); diff --git a/examples/html-transform-tags/src/app.js b/examples/html-transform-tags/src/app.js new file mode 100644 index 00000000000..12e37387703 --- /dev/null +++ b/examples/html-transform-tags/src/app.js @@ -0,0 +1 @@ +document.querySelector("h1").classList.add("ready"); diff --git a/examples/html-transform-tags/src/index.html b/examples/html-transform-tags/src/index.html new file mode 100644 index 00000000000..c3fd9d57aff --- /dev/null +++ b/examples/html-transform-tags/src/index.html @@ -0,0 +1,20 @@ + + + + + transformTags + + + + + + + + + + + +

transformTags

+ + diff --git a/examples/html-transform-tags/src/styles.css b/examples/html-transform-tags/src/styles.css new file mode 100644 index 00000000000..4a940f55f8d --- /dev/null +++ b/examples/html-transform-tags/src/styles.css @@ -0,0 +1,8 @@ +h1 { + font-family: sans-serif; + color: #2b3a42; +} + +h1.ready { + color: #8ed6fb; +} diff --git a/examples/html-transform-tags/template.md b/examples/html-transform-tags/template.md new file mode 100644 index 00000000000..da2cdd17c05 --- /dev/null +++ b/examples/html-transform-tags/template.md @@ -0,0 +1,64 @@ +This example demonstrates the `output.html` **`transformTags`** hook on top of +the experimental HTML modules support (`experiments.html`). + +`transformTags` hands a plugin the page's already-present +` + + + + + +``` + +# src/app.js + +```javascript +// Loaded by the HTML entry via ` + + + + + +``` + +# Info + +## Unoptimized + +``` +assets by path *.png 77.9 KiB + assets by info 34.2 KiB [immutable] + asset eda8c35d5b9a24e8efa6.png 19.6 KiB [emitted] [immutable] [from: src/logo@2x.png] (auxiliary name: page) + asset 89a353e9c515885abd8e.png 14.6 KiB [emitted] [immutable] [from: src/logo.png] (auxiliary name: __html_6d047296_1, page) + asset apple-touch-icon.png 14.6 KiB [emitted] + asset icon-192.png 14.6 KiB [emitted] + asset icon-512.png 14.6 KiB [emitted] +assets by path *.js 11.3 KiB + asset __html_6d047296_1.js 6.77 KiB [emitted] (name: __html_6d047296_1) + asset page.js 3.57 KiB [emitted] (name: page) + asset __html_6d047296_2.js 1.01 KiB [emitted] (name: __html_6d047296_2) +asset favicon.ico 14.6 KiB [emitted] +asset index.html 1.91 KiB [emitted] (auxiliary name: page) +asset manifest.webmanifest 208 bytes [emitted] +asset __html_6d047296_0.css 166 bytes [emitted] (name: __html_6d047296_0) +chunk (runtime: __html_6d047296_0) __html_6d047296_0.css (__html_6d047296_0) 64 bytes (css) 0 bytes (runtime) [entry] [rendered] + > ./styles.css __html_6d047296_0 + runtime modules 0 bytes 1 module + css ./src/styles.css 64 bytes [built] [code generated] + [no exports] + [used exports unknown] + entry ./styles.css __html_6d047296_0 +chunk (runtime: __html_6d047296_1) __html_6d047296_1.js (__html_6d047296_1) 541 bytes (javascript) 14.6 KiB (asset) 42 bytes (asset-url) 2.7 KiB (runtime) [entry] [rendered] + > ./app.js __html_6d047296_1 + runtime modules 2.7 KiB 4 modules + dependent modules 124 bytes (javascript) 14.6 KiB (asset) 42 bytes (asset-url) [dependent] 2 modules + ./src/app.js 417 bytes [built] [code generated] + [no exports] + [used exports unknown] + entry ./app.js __html_6d047296_1 +chunk (runtime: __html_6d047296_1) __html_6d047296_2.js (__html_6d047296_2) 56 bytes [initial] [rendered] + > data:text/javascript;base64,CgkJCWNvbnNvbGUubG9nKCJpbmxpbmUgc2NyaXB0LCBidW5kbGVkIGJ5IHdlYnBhY2siKTsKCQk= __html_6d047296_2 + data:text/javascript;base64,CgkJCWNvbnNvbGUu.. 56 bytes [built] [code generated] + [no exports] + [used exports unknown] + entry data:text/javascript;base64,CgkJCWNvbnNvbGUu.. __html_6d047296_2 +chunk (runtime: page) page.js (page) 34.2 KiB (asset) 84 bytes (asset-url) 65 bytes (css-text) 1.24 KiB (javascript) 1.23 KiB (html) [entry] [rendered] + > ./src/index.html page + dependent modules 34.2 KiB (asset) 84 bytes (asset-url) 65 bytes (css-text) [dependent] 3 modules + ./src/index.html 1.24 KiB (javascript) 1.23 KiB (html) [built] [code generated] + [exports: default] + [used exports unknown] + entry ./src/index.html page +webpack X.X.X compiled successfully +``` + +## Production mode + +``` +assets by path *.png 77.9 KiB + assets by info 34.2 KiB [immutable] + asset eda8c35d5b9a24e8efa6.png 19.6 KiB [emitted] [immutable] [from: src/logo@2x.png] (auxiliary name: page) + asset 89a353e9c515885abd8e.png 14.6 KiB [emitted] [immutable] [from: src/logo.png] (auxiliary name: __html_6d047296_1, page) + asset apple-touch-icon.png 14.6 KiB [emitted] + asset icon-192.png 14.6 KiB [emitted] + asset icon-512.png 14.6 KiB [emitted] +assets by path *.js 2.89 KiB + asset page.js 1.62 KiB [emitted] [minimized] (name: page) + asset __html_6d047296_1.js 1.14 KiB [emitted] [minimized] (name: __html_6d047296_1) + asset __html_6d047296_2.js 143 bytes [emitted] [minimized] (name: __html_6d047296_2) +asset favicon.ico 14.6 KiB [emitted] +asset index.html 749 bytes [emitted] [minimized] (auxiliary name: page) +asset manifest.webmanifest 208 bytes [emitted] +asset __html_6d047296_0.css 40 bytes [emitted] [minimized] (name: __html_6d047296_0) +chunk (runtime: __html_6d047296_0) __html_6d047296_0.css (__html_6d047296_0) 64 bytes (css) 0 bytes (runtime) [entry] [rendered] + > ./styles.css __html_6d047296_0 + runtime modules 0 bytes 1 module + css ./src/styles.css 64 bytes [built] [code generated] + [no exports] + [no exports used] + entry ./styles.css __html_6d047296_0 +chunk (runtime: page) page.js (page) 34.2 KiB (asset) 84 bytes (asset-url) 65 bytes (css-text) 1.24 KiB (javascript) 1.23 KiB (html) [entry] [rendered] + > ./src/index.html page + dependent modules 34.2 KiB (asset) 84 bytes (asset-url) 65 bytes (css-text) [dependent] 3 modules + ./src/index.html 1.24 KiB (javascript) 1.23 KiB (html) [built] [code generated] + [exports: default] + [no exports used] + entry ./src/index.html page +chunk (runtime: __html_6d047296_1) __html_6d047296_2.js (__html_6d047296_2) 56 bytes [initial] [rendered] + > data:text/javascript;base64,CgkJCWNvbnNvbGUubG9nKCJpbmxpbmUgc2NyaXB0LCBidW5kbGVkIGJ5IHdlYnBhY2siKTsKCQk= __html_6d047296_2 + data:text/javascript;base64,CgkJCWNvbnNvbGUu.. 56 bytes [built] [code generated] + [no exports] + [no exports used] + entry data:text/javascript;base64,CgkJCWNvbnNvbGUu.. __html_6d047296_2 +chunk (runtime: __html_6d047296_1) __html_6d047296_1.js (__html_6d047296_1) 541 bytes (javascript) 14.6 KiB (asset) 42 bytes (asset-url) 2.46 KiB (runtime) [entry] [rendered] + > ./app.js __html_6d047296_1 + runtime modules 2.46 KiB 3 modules + dependent modules 14.6 KiB (asset) 42 bytes (asset-url) [dependent] 1 module + ./src/app.js + 1 modules 541 bytes [built] [code generated] + [no exports] + [no exports used] + entry ./app.js __html_6d047296_1 +webpack X.X.X compiled successfully +``` diff --git a/examples/html/build.js b/examples/html/build.js new file mode 100644 index 00000000000..5768b058787 --- /dev/null +++ b/examples/html/build.js @@ -0,0 +1,3 @@ +global.NO_TARGET_ARGS = true; +global.NO_PUBLIC_PATH = true; +require("../build-common"); diff --git a/examples/html/src/app.js b/examples/html/src/app.js new file mode 100644 index 00000000000..46f8894dc43 --- /dev/null +++ b/examples/html/src/app.js @@ -0,0 +1,9 @@ +// Loaded by the HTML entry via ` + + + + + diff --git a/examples/html/src/logo.png b/examples/html/src/logo.png new file mode 100644 index 00000000000..fb53b9dedd3 Binary files /dev/null and b/examples/html/src/logo.png differ diff --git a/examples/html/src/logo@2x.png b/examples/html/src/logo@2x.png new file mode 100644 index 00000000000..d8e410d5a9b Binary files /dev/null and b/examples/html/src/logo@2x.png differ diff --git a/examples/html/src/styles.css b/examples/html/src/styles.css new file mode 100644 index 00000000000..f206858d6b7 --- /dev/null +++ b/examples/html/src/styles.css @@ -0,0 +1,7 @@ +h1 { + color: rebeccapurple; +} + +img { + border: 1px solid #ccc; +} diff --git a/examples/html/template.md b/examples/html/template.md new file mode 100644 index 00000000000..4e46fa0975e --- /dev/null +++ b/examples/html/template.md @@ -0,0 +1,76 @@ +This example demonstrates the experimental HTML modules support +(`experiments.html`) in two ways: + +- **HTML entry point** (`./src/index.html`): emitted as a standalone + `dist/index.html`. Its ``, inline ` + + + +
+ + + + + + + + + + + + + + + +``` + +# src-b/Component.js + +```jsx +import React from "react"; +import { formatRelative, subDays } from "date-fns"; +// date-fns is a shared module, but used as usual +// exposing modules act as async boundary, +// so no additional async boundary need to be added here +// As data-fns is an shared module, it will be placed in a separate file +// It will be loaded in parallel to the code of this module + +const Component = ({ locale }) => ( +
+

I'm a Component exposed from container B!

+

+ Using date-fn in Remote:{" "} + {formatRelative(subDays(new Date(), 2), new Date(), { locale })} +

+
+); +export default Component; +``` + +# dist/aaa/app.js + +```javascript +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ({ + +/***/ 0 +/*!**********************!*\ + !*** ./src/index.js ***! + \**********************/ +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: __webpack_require__.e, __webpack_require__, __webpack_require__.* */ +(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +// Sharing modules requires that all remotes are initialized +// and can provide shared modules to the common scope +// As this is an async operation we need an async boundary (import()) + +// Using modules from remotes is also an async operation +// as chunks need to be loaded for the code of the remote module +// This also requires an async boundary (import()) + +// At this point shared modules initialized and remote modules are loaded +__webpack_require__.e(/*! import() */ "src_bootstrap_js").then(__webpack_require__.bind(__webpack_require__, /*! ./bootstrap */ 2)); + +// It's possible to place more code here to do stuff on page init +// but it can't use any of the shared modules or remote modules. + +/***/ }, + +/***/ 9 +/*!*********************************************!*\ + !*** external "mfeBBB@/dist/bbb/mfeBBB.js" ***! + \*********************************************/ +/*! dynamic exports */ +/*! exports [maybe provided (runtime-defined)] [no usage info] */ +/*! runtime requirements: __webpack_require__.l, module, __webpack_require__.* */ +(module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; +const __webpack_error__ = new Error(); +module.exports = new Promise((resolve, reject) => { + if(typeof mfeBBB !== "undefined") return resolve(); + __webpack_require__.l("/dist/bbb/mfeBBB.js", (event) => { + if(typeof mfeBBB !== "undefined") return resolve(); + const errorType = event && (event.type === 'load' ? 'missing' : event.type); + const realSrc = event && event.target && event.target.src; + __webpack_error__.message = 'Loading script failed.\n(' + errorType + ': ' + realSrc + ')'; + __webpack_error__.name = 'ScriptExternalLoadError'; + __webpack_error__.type = errorType; + __webpack_error__.request = realSrc; + __webpack_error__.event = event; + reject(__webpack_error__); + }, "mfeBBB"); +}).then(() => (mfeBBB)); + +/***/ }, + +/***/ 11 +/*!*********************************************!*\ + !*** external "mfeCCC@/dist/ccc/mfeCCC.js" ***! + \*********************************************/ +/*! dynamic exports */ +/*! exports [maybe provided (runtime-defined)] [no usage info] */ +/*! runtime requirements: __webpack_require__.l, module, __webpack_require__.* */ +(module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; +const __webpack_error__ = new Error(); +module.exports = new Promise((resolve, reject) => { + if(typeof mfeCCC !== "undefined") return resolve(); + __webpack_require__.l("/dist/ccc/mfeCCC.js", (event) => { + if(typeof mfeCCC !== "undefined") return resolve(); + const errorType = event && (event.type === 'load' ? 'missing' : event.type); + const realSrc = event && event.target && event.target.src; + __webpack_error__.message = 'Loading script failed.\n(' + errorType + ': ' + realSrc + ')'; + __webpack_error__.name = 'ScriptExternalLoadError'; + __webpack_error__.type = errorType; + __webpack_error__.request = realSrc; + __webpack_error__.event = event; + reject(__webpack_error__); + }, "mfeCCC"); +}).then(() => (mfeCCC)); + +/***/ } + +/******/ }); +``` + +
/* webpack runtime code */ + +``` js +/************************************************************************/ +/******/ // The module cache +/******/ const __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ const 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; +/******/ } +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = __webpack_modules__; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = __webpack_module_cache__; +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/compat get default export */ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = (module) => { +/******/ const getter = module && module.__esModule ? +/******/ () => (module['default']) : +/******/ () => (module); +/******/ __webpack_require__.d(getter, { a: getter }); +/******/ return getter; +/******/ }; +/******/ +/******/ /* webpack/runtime/create fake namespace object */ +/******/ (() => { +/******/ const getProto = Object.getPrototypeOf ? (obj) => (Object.getPrototypeOf(obj)) : (obj) => (obj.__proto__); +/******/ let leafPrototypes; +/******/ // 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 & 16: return value when it's Promise-like +/******/ // mode & 8|1: behave like require +/******/ __webpack_require__.t = function(value, mode) { +/******/ if(mode & 1) value = this(value); +/******/ if(mode & 8) return value; +/******/ if(typeof value === 'object' && value) { +/******/ if((mode & 4) && value.__esModule) return value; +/******/ if((mode & 16) && typeof value.then === 'function') return value; +/******/ } +/******/ const ns = Object.create(null); +/******/ __webpack_require__.r(ns); +/******/ const def = {}; +/******/ leafPrototypes = leafPrototypes || [null, getProto({}), getProto([]), getProto(getProto)]; +/******/ for(var current = mode & 2 && value; (typeof current == 'object' || typeof current == 'function') && !~leafPrototypes.indexOf(current); current = getProto(current)) { +/******/ Object.getOwnPropertyNames(current).forEach((key) => (def[key] = () => (value[key]))); +/******/ } +/******/ def['default'] = () => (value); +/******/ __webpack_require__.d(ns, def); +/******/ return ns; +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/define property getters */ +/******/ // define getter/value functions for harmony exports +/******/ __webpack_require__.d = (exports, definition) => { +/******/ if(Array.isArray(definition)) { +/******/ var i = 0; +/******/ while(i < definition.length) { +/******/ var key = definition[i++]; +/******/ var binding = definition[i++]; +/******/ if(!__webpack_require__.o(exports, key)) { +/******/ if(binding === 0) { +/******/ Object.defineProperty(exports, key, { enumerable: true, value: definition[i++] }); +/******/ } else { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: binding }); +/******/ } +/******/ } else if(binding === 0) { i++; } +/******/ } +/******/ } else { +/******/ for(var key in definition) { +/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); +/******/ } +/******/ } +/******/ } +/******/ }; +/******/ +/******/ /* webpack/runtime/ensure chunk */ +/******/ __webpack_require__.f = {}; +/******/ // This file contains only the entry chunk. +/******/ // The chunk loading function for additional chunks +/******/ __webpack_require__.e = (chunkId) => { +/******/ return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => { +/******/ __webpack_require__.f[key](chunkId, promises); +/******/ return promises; +/******/ }, [])); +/******/ }; +/******/ +/******/ /* webpack/runtime/get javascript chunk filename */ +/******/ // This function allow to reference async chunks +/******/ __webpack_require__.u = (chunkId) => { +/******/ // return url for filenames based on template +/******/ return "" + chunkId + ".js"; +/******/ }; +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)); +/******/ +/******/ /* webpack/runtime/load script */ +/******/ (() => { +/******/ const inProgress = {}; +/******/ const dataWebpackPrefix = "module-federation-aaa:"; +/******/ // loadScript function to load a script via script tag +/******/ __webpack_require__.l = (url, done, key, chunkId) => { +/******/ if(inProgress[url]) { inProgress[url].push(done); return; } +/******/ let script, needAttach; +/******/ if(key !== undefined) { +/******/ const scripts = document.getElementsByTagName("script"); +/******/ for(var i = 0; i < scripts.length; i++) { +/******/ const s = scripts[i]; +/******/ if(s.getAttribute("src") == url || s.getAttribute("data-webpack") == dataWebpackPrefix + key) { script = s; break; } +/******/ } +/******/ } +/******/ if(!script) { +/******/ needAttach = true; +/******/ script = document.createElement('script'); +/******/ +/******/ script.charset = 'utf-8'; +/******/ if (__webpack_require__.nc) { +/******/ script.setAttribute("nonce", __webpack_require__.nc); +/******/ } +/******/ script.setAttribute("data-webpack", dataWebpackPrefix + key); +/******/ +/******/ script.src = url; +/******/ } +/******/ inProgress[url] = [done]; +/******/ const onScriptComplete = (prev, event) => { +/******/ // avoid mem leaks in IE. +/******/ script.onerror = script.onload = null; +/******/ clearTimeout(timeout); +/******/ const doneFns = inProgress[url]; +/******/ delete inProgress[url]; +/******/ script.parentNode?.removeChild(script); +/******/ doneFns?.forEach((fn) => (fn(event))); +/******/ if(prev) return prev(event); +/******/ } +/******/ const timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000); +/******/ script.onerror = onScriptComplete.bind(null, script.onerror); +/******/ script.onload = onScriptComplete.bind(null, script.onload); +/******/ needAttach && document.head.appendChild(script); +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/make namespace object */ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = (exports) => { +/******/ if(Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/******/ /* webpack/runtime/remotes loading */ +/******/ (() => { +/******/ const chunkMapping = { +/******/ "src_bootstrap_js": [ +/******/ 8, +/******/ 10 +/******/ ], +/******/ "webpack_container_remote_mfe-c_Component2": [ +/******/ 26 +/******/ ] +/******/ }; +/******/ const idToExternalAndNameMapping = { +/******/ "8": [ +/******/ "default", +/******/ "./Component", +/******/ 9 +/******/ ], +/******/ "10": [ +/******/ "default", +/******/ "./Component", +/******/ 11 +/******/ ], +/******/ "26": [ +/******/ "default", +/******/ "./Component2", +/******/ 11 +/******/ ] +/******/ }; +/******/ __webpack_require__.f.remotes = (chunkId, promises) => { +/******/ if(__webpack_require__.o(chunkMapping, chunkId)) { +/******/ chunkMapping[chunkId].forEach((id) => { +/******/ let getScope = __webpack_require__.R; +/******/ if(!getScope) getScope = []; +/******/ const data = idToExternalAndNameMapping[id]; +/******/ if(getScope.indexOf(data) >= 0) return; +/******/ getScope.push(data); +/******/ if(data.p) return promises.push(data.p); +/******/ const onError = (error) => { +/******/ if(!error) error = new Error("Container missing"); +/******/ if(typeof error.message === "string") +/******/ error.message += '\nwhile loading "' + data[1] + '" from ' + data[2]; +/******/ __webpack_require__.m[id] = () => { +/******/ throw error; +/******/ } +/******/ data.p = 0; +/******/ }; +/******/ const handleFunction = (fn, arg1, arg2, d, next, first) => { +/******/ try { +/******/ const promise = fn(arg1, arg2); +/******/ if(promise?.then) { +/******/ const p = promise.then((result) => (next(result, d)), onError); +/******/ if(first) promises.push(data.p = p); else return p; +/******/ } else { +/******/ return next(promise, d, first); +/******/ } +/******/ } catch(error) { +/******/ onError(error); +/******/ } +/******/ } +/******/ const onExternal = (external, _, first) => (external ? handleFunction(__webpack_require__.I, data[0], 0, external, onInitialized, first) : onError()); +/******/ const onInitialized = (_, external, first) => (handleFunction(external.get, data[1], getScope, 0, onFactory, first)); +/******/ const onFactory = (factory) => { +/******/ data.p = 1; +/******/ __webpack_require__.m[id] = (module) => { +/******/ module.exports = factory(); +/******/ } +/******/ }; +/******/ handleFunction(__webpack_require__, data[2], 0, 0, onExternal, 1); +/******/ }); +/******/ } +/******/ } +/******/ })(); +/******/ +/******/ /* webpack/runtime/sharing */ +/******/ (() => { +/******/ __webpack_require__.S = {}; +/******/ const initPromises = {}; +/******/ const initTokens = {}; +/******/ __webpack_require__.I = (name, initScope) => { +/******/ if(!initScope) initScope = []; +/******/ // handling circular init calls +/******/ let initToken = initTokens[name]; +/******/ if(!initToken) initToken = initTokens[name] = {}; +/******/ if(initScope.indexOf(initToken) >= 0) return; +/******/ initScope.push(initToken); +/******/ // only runs once +/******/ if(initPromises[name]) return initPromises[name]; +/******/ // creates a new share scope if needed +/******/ if(!__webpack_require__.o(__webpack_require__.S, name)) __webpack_require__.S[name] = {}; +/******/ // runs all init snippets from all modules reachable +/******/ const scope = __webpack_require__.S[name]; +/******/ const warn = (msg) => { +/******/ if (typeof console !== "undefined" && console.warn) console.warn(msg); +/******/ }; +/******/ const uniqueName = "module-federation-aaa"; +/******/ const register = (name, version, factory, eager) => { +/******/ const versions = scope[name] = scope[name] || {}; +/******/ const activeVersion = versions[version]; +/******/ if(!activeVersion || (!activeVersion.loaded && (!eager != !activeVersion.eager ? eager : uniqueName > activeVersion.from))) versions[version] = { get: factory, from: uniqueName, eager: !!eager }; +/******/ }; +/******/ const initExternal = (id) => { +/******/ const handleError = (err) => (warn("Initialization of sharing external failed: " + err)); +/******/ try { +/******/ const module = __webpack_require__(id); +/******/ if(!module) return; +/******/ const initFn = (module) => (module && module.init && module.init(__webpack_require__.S[name], initScope)) +/******/ if(module.then) return promises.push(module.then(initFn, handleError)); +/******/ const initResult = initFn(module); +/******/ if(initResult?.then) return promises.push(initResult['catch'](handleError)); +/******/ } catch(err) { handleError(err); } +/******/ } +/******/ const promises = []; +/******/ switch(name) { +/******/ case "default": { +/******/ register("react", "19.2.8", () => (__webpack_require__.e("vendors-node_modules_react_index_js").then(() => (() => (__webpack_require__(/*! ../../node_modules/react/index.js */ 24)))))); +/******/ initExternal(9); +/******/ initExternal(11); +/******/ } +/******/ break; +/******/ } +/******/ if(!promises.length) return initPromises[name] = 1; +/******/ return initPromises[name] = Promise.all(promises).then(() => (initPromises[name] = 1)); +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/publicPath */ +/******/ __webpack_require__.p = "dist/aaa/"; +/******/ +/******/ /* webpack/runtime/consumes */ +/******/ (() => { +/******/ var parseVersion = (str) => { +/******/ // see webpack/lib/util/semver.js for original code +/******/ var p=p=>{return p.split(".").map(p=>{return+p==p?+p:p})},n=/^([^-+]+)?(?:-([^+]+))?(?:\+(.+))?$/.exec(str),r=n[1]?p(n[1]):[];return n[2]&&(r.length++,r.push.apply(r,p(n[2]))),n[3]&&(r.push([]),r.push.apply(r,p(n[3]))),r; +/******/ } +/******/ var versionLt = (a, b) => { +/******/ // see webpack/lib/util/semver.js for original code +/******/ a=parseVersion(a),b=parseVersion(b);for(var r=0;;){if(r>=a.length)return r=b.length)return"u"==n;var t=b[r],f=(typeof t)[0];if(n!=f)return"o"==n&&"n"==f||("s"==f||"u"==n);if("o"!=n&&"u"!=n&&e!=t)return e { +/******/ // see webpack/lib/util/semver.js for original code +/******/ var r=range[0],n="";if(1===range.length)return"*";if(r+.5){n+=0==r?">=":-1==r?"<":1==r?"^":2==r?"~":r>0?"=":"!=";for(var e=1,a=1;a0?".":"")+(e=2,t)}return n}var g=[];for(a=1;a { +/******/ // see webpack/lib/util/semver.js for original code +/******/ if(0 in range){version=parseVersion(version);var e=range[0],r=e<0;r&&(e=-e-1);for(var n=0,i=1,a=!0;;i++,n++){var f,s,g=i=version.length||"o"==(s=(typeof(f=version[n]))[0]))return!a||("u"==g?i>e&&!r:""==g!=r);if("u"==s){if(!a||"u"!=g)return!1}else if(a)if(g==s)if(i<=e){if(f!=range[i])return!1}else{if(r?f>range[i]:f { +/******/ return scope && __webpack_require__.o(scope, key); +/******/ } +/******/ const get = (entry) => { +/******/ entry.loaded = 1; +/******/ return entry.get() +/******/ }; +/******/ const eagerOnly = (versions) => { +/******/ return Object.keys(versions).reduce((filtered, version) => { +/******/ if (versions[version].eager) { +/******/ filtered[version] = versions[version]; +/******/ } +/******/ return filtered; +/******/ }, {}); +/******/ }; +/******/ const findLatestVersion = (scope, key, eager) => { +/******/ const versions = eager ? eagerOnly(scope[key]) : scope[key]; +/******/ var key = Object.keys(versions).reduce((a, b) => { +/******/ return !a || versionLt(a, b) ? b : a; +/******/ }, 0); +/******/ return key && versions[key]; +/******/ }; +/******/ const findSatisfyingVersion = (scope, key, requiredVersion, eager) => { +/******/ const versions = eager ? eagerOnly(scope[key]) : scope[key]; +/******/ var key = Object.keys(versions).reduce((a, b) => { +/******/ if (!satisfy(requiredVersion, b)) return a; +/******/ return !a || versionLt(a, b) ? b : a; +/******/ }, 0); +/******/ return key && versions[key] +/******/ }; +/******/ const findSingletonVersionKey = (scope, key, eager) => { +/******/ const versions = eager ? eagerOnly(scope[key]) : scope[key]; +/******/ return Object.keys(versions).reduce((a, b) => { +/******/ return !a || (!versions[a].loaded && versionLt(a, b)) ? b : a; +/******/ }, 0); +/******/ }; +/******/ const getInvalidSingletonVersionMessage = (scope, key, version, requiredVersion) => { +/******/ return "Unsatisfied version " + version + " from " + (version && scope[key][version].from) + " of shared singleton module " + key + " (required " + rangeToString(requiredVersion) + ")" +/******/ }; +/******/ const getInvalidVersionMessage = (scope, scopeName, key, requiredVersion, eager) => { +/******/ const versions = scope[key]; +/******/ return "No satisfying version (" + rangeToString(requiredVersion) + ")" + (eager ? " for eager consumption" : "") + " of shared module " + key + " found in shared scope " + scopeName + ".\n" + +/******/ "Available versions: " + Object.keys(versions).map((key) => { +/******/ return key + " from " + versions[key].from; +/******/ }).join(", "); +/******/ }; +/******/ const fail = (msg) => { +/******/ throw new Error(msg); +/******/ } +/******/ const failAsNotExist = (scopeName, key) => { +/******/ return fail("Shared module " + key + " doesn't exist in shared scope " + scopeName); +/******/ } +/******/ const warn = /*#__PURE__*/ (msg) => { +/******/ if (typeof console !== "undefined" && console.warn) console.warn(msg); +/******/ }; +/******/ const init = (fn) => (function(scopeName, key, eager, c, d) { +/******/ const promise = __webpack_require__.I(scopeName); +/******/ if (promise?.then && !eager) { +/******/ return promise.then(fn.bind(fn, scopeName, __webpack_require__.S[scopeName], key, false, c, d)); +/******/ } +/******/ return fn(scopeName, __webpack_require__.S[scopeName], key, eager, c, d); +/******/ }); +/******/ +/******/ const useFallback = (scopeName, key, fallback) => { +/******/ return fallback ? fallback() : failAsNotExist(scopeName, key); +/******/ } +/******/ const load = /*#__PURE__*/ init((scopeName, scope, key, eager, fallback) => { +/******/ if (!exists(scope, key)) return useFallback(scopeName, key, fallback); +/******/ return get(findLatestVersion(scope, key, eager)); +/******/ }); +/******/ const loadVersion = /*#__PURE__*/ init((scopeName, scope, key, eager, requiredVersion, fallback) => { +/******/ if (!exists(scope, key)) return useFallback(scopeName, key, fallback); +/******/ const satisfyingVersion = findSatisfyingVersion(scope, key, requiredVersion, eager); +/******/ if (satisfyingVersion) return get(satisfyingVersion); +/******/ warn(getInvalidVersionMessage(scope, scopeName, key, requiredVersion, eager)) +/******/ return get(findLatestVersion(scope, key, eager)); +/******/ }); +/******/ const loadStrictVersion = /*#__PURE__*/ init((scopeName, scope, key, eager, requiredVersion, fallback) => { +/******/ if (!exists(scope, key)) return useFallback(scopeName, key, fallback); +/******/ const satisfyingVersion = findSatisfyingVersion(scope, key, requiredVersion, eager); +/******/ if (satisfyingVersion) return get(satisfyingVersion); +/******/ if (fallback) return fallback(); +/******/ fail(getInvalidVersionMessage(scope, scopeName, key, requiredVersion, eager)); +/******/ }); +/******/ const loadSingleton = /*#__PURE__*/ init((scopeName, scope, key, eager, fallback) => { +/******/ if (!exists(scope, key)) return useFallback(scopeName, key, fallback); +/******/ const version = findSingletonVersionKey(scope, key, eager); +/******/ return get(scope[key][version]); +/******/ }); +/******/ const loadSingletonVersion = /*#__PURE__*/ init((scopeName, scope, key, eager, requiredVersion, fallback) => { +/******/ if (!exists(scope, key)) return useFallback(scopeName, key, fallback); +/******/ const version = findSingletonVersionKey(scope, key, eager); +/******/ if (!satisfy(requiredVersion, version)) { +/******/ warn(getInvalidSingletonVersionMessage(scope, key, version, requiredVersion)); +/******/ } +/******/ return get(scope[key][version]); +/******/ }); +/******/ const loadStrictSingletonVersion = /*#__PURE__*/ init((scopeName, scope, key, eager, requiredVersion, fallback) => { +/******/ if (!exists(scope, key)) return useFallback(scopeName, key, fallback); +/******/ const version = findSingletonVersionKey(scope, key, eager); +/******/ if (!satisfy(requiredVersion, version)) { +/******/ fail(getInvalidSingletonVersionMessage(scope, key, version, requiredVersion)); +/******/ } +/******/ return get(scope[key][version]); +/******/ }); +/******/ const installedModules = {}; +/******/ const moduleToHandlerMapping = { +/******/ 5: () => (loadSingletonVersion("default", "react", false, [1,19,2,8], () => (__webpack_require__.e("vendors-node_modules_react_index_js").then(() => (() => (__webpack_require__(/*! react */ 24))))))), +/******/ 6: () => (loadSingletonVersion("default", "react", false, [1,19,2,7], () => (__webpack_require__.e("vendors-node_modules_react_index_js").then(() => (() => (__webpack_require__(/*! react */ 24))))))) +/******/ }; +/******/ // no consumes in initial chunks +/******/ const chunkMapping = { +/******/ "src_bootstrap_js": [ +/******/ 5, +/******/ 6 +/******/ ] +/******/ }; +/******/ const startedInstallModules = {}; +/******/ __webpack_require__.f.consumes = (chunkId, promises) => { +/******/ if(__webpack_require__.o(chunkMapping, chunkId)) { +/******/ chunkMapping[chunkId].forEach((id) => { +/******/ if(__webpack_require__.o(installedModules, id)) return promises.push(installedModules[id]); +/******/ if(!startedInstallModules[id]) { +/******/ const onFactory = (factory) => { +/******/ installedModules[id] = 0; +/******/ __webpack_require__.m[id] = (module) => { +/******/ delete __webpack_require__.c[id]; +/******/ module.exports = factory(); +/******/ } +/******/ }; +/******/ startedInstallModules[id] = true; +/******/ const onError = (error) => { +/******/ delete installedModules[id]; +/******/ __webpack_require__.m[id] = (module) => { +/******/ delete __webpack_require__.c[id]; +/******/ throw error; +/******/ } +/******/ }; +/******/ try { +/******/ const promise = moduleToHandlerMapping[id](); +/******/ if(promise.then) { +/******/ promises.push(installedModules[id] = promise.then(onFactory)['catch'](onError)); +/******/ } else onFactory(promise); +/******/ } catch(e) { onError(e); } +/******/ } +/******/ }); +/******/ } +/******/ } +/******/ })(); +/******/ +/******/ /* webpack/runtime/jsonp chunk loading */ +/******/ (() => { +/******/ // no baseURI +/******/ +/******/ // object to store loaded and loading chunks +/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched +/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded +/******/ const installedChunks = { +/******/ "app": 0 +/******/ }; +/******/ +/******/ __webpack_require__.f.j = (chunkId, promises) => { +/******/ // JSONP chunk loading for javascript +/******/ let installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined; +/******/ if(installedChunkData !== 0) { // 0 means "already installed". +/******/ +/******/ // a Promise means "currently loading". +/******/ if(installedChunkData) { +/******/ promises.push(installedChunkData[2]); +/******/ } else { +/******/ if("webpack_container_remote_mfe-c_Component2" != chunkId) { +/******/ // setup Promise in chunk cache +/******/ const promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject])); +/******/ promises.push(installedChunkData[2] = promise); +/******/ +/******/ // start chunk loading +/******/ const url = __webpack_require__.p + __webpack_require__.u(chunkId); +/******/ // create error before stack unwound to get useful stacktrace later +/******/ const error = new Error(); +/******/ const loadingEnded = (event) => { +/******/ if(__webpack_require__.o(installedChunks, chunkId)) { +/******/ installedChunkData = installedChunks[chunkId]; +/******/ if(installedChunkData !== 0) installedChunks[chunkId] = undefined; +/******/ if(installedChunkData) { +/******/ const errorType = event && (event.type === 'load' ? 'missing' : event.type); +/******/ const realSrc = event && event.target && event.target.src; +/******/ error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'; +/******/ error.name = 'ChunkLoadError'; +/******/ error.type = errorType; +/******/ error.request = realSrc; +/******/ error.event = event; +/******/ installedChunkData[1](error); +/******/ } +/******/ } +/******/ }; +/******/ __webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId); +/******/ } else installedChunks[chunkId] = 0; +/******/ } +/******/ } +/******/ }; +/******/ +/******/ // no prefetching +/******/ +/******/ // no preloaded +/******/ +/******/ // no HMR +/******/ +/******/ // no HMR manifest +/******/ +/******/ // no on chunks loaded +/******/ +/******/ // install a JSONP callback for chunk loading +/******/ const webpackJsonpCallback = (parentChunkLoadingFunction, data) => { +/******/ let [chunkIds, moreModules, runtime] = data; +/******/ // add "moreModules" to the modules object, +/******/ // then flag all "chunkIds" as loaded and fire callback +/******/ var moduleId, chunkId, i = 0; +/******/ if(chunkIds.some((id) => (installedChunks[id] !== 0))) { +/******/ for(moduleId in moreModules) { +/******/ if(__webpack_require__.o(moreModules, moduleId)) { +/******/ __webpack_require__.m[moduleId] = moreModules[moduleId]; +/******/ } +/******/ } +/******/ if(runtime) var result = runtime(__webpack_require__); +/******/ } +/******/ if(parentChunkLoadingFunction) parentChunkLoadingFunction(data); +/******/ for(;i < chunkIds.length; i++) { +/******/ chunkId = chunkIds[i]; +/******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) { +/******/ installedChunks[chunkId][0](); +/******/ } +/******/ installedChunks[chunkId] = 0; +/******/ } +/******/ +/******/ } +/******/ +/******/ const chunkLoadingGlobal = self["webpackChunkmodule_federation_aaa"] = self["webpackChunkmodule_federation_aaa"] || []; +/******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0)); +/******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal)); +/******/ })(); +/******/ +/************************************************************************/ +``` + +
+ +``` js +/******/ +/******/ // module cache are used so entry inlining is disabled +/******/ // startup +/******/ // Load entry module and return exports +/******/ let __webpack_exports__ = __webpack_require__(0); +/******/ +/******/ })() +; +``` + +# dist/bbb/mfeBBB.js + +```javascript +var mfeBBB; +/******/ (() => { // webpackBootstrap +/******/ "use strict"; +/******/ var __webpack_modules__ = ([ +/* 0 */ +/*!***********************!*\ + !*** container entry ***! + \***********************/ +/*! namespace exports */ +/*! export get [provided] [maybe used in mfeBBB (runtime-defined)] [usage and provision prevents renaming] */ +/*! export init [provided] [maybe used in mfeBBB (runtime-defined)] [usage and provision prevents renaming] */ +/*! other exports [not provided] [maybe used in mfeBBB (runtime-defined)] */ +/*! runtime requirements: __webpack_require__.d, __webpack_require__.o, __webpack_exports__, __webpack_require__.e, __webpack_require__, __webpack_require__.* */ +/***/ ((__unused_webpack_module, exports, __webpack_require__) => { + +const moduleMap = { + "./Component": () => { + return __webpack_require__.e("src-b_Component_js").then(() => (() => ((__webpack_require__(/*! ./src-b/Component */ 3))))); + } +}; +const get = (module, getScope) => { + __webpack_require__.R = getScope; + getScope = ( + __webpack_require__.o(moduleMap, module) + ? moduleMap[module]() + : Promise.resolve().then(() => { + throw new Error('Module "' + module + '" does not exist in container.'); + }) + ); + __webpack_require__.R = undefined; + return getScope; +}; +const init = (shareScope, initScope) => { + if (!__webpack_require__.S) return; + const name = "default" + const oldScope = __webpack_require__.S[name]; + if(oldScope && oldScope !== shareScope) throw new Error("Container initialization failed as it has already been initialized with a different share scope"); + __webpack_require__.S[name] = shareScope; + return __webpack_require__.I(name, initScope); +}; + +// This exports getters to disallow modifications +__webpack_require__.d(exports, { + get: () => (get), + init: () => (init) +}); + +/***/ }) +/******/ ]); +``` + +
/* webpack runtime code */ + +``` js +/************************************************************************/ +/******/ // The module cache +/******/ const __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ const 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; +/******/ } +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = __webpack_modules__; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = __webpack_module_cache__; +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/define property getters */ +/******/ // define getter/value functions for harmony exports +/******/ __webpack_require__.d = (exports, definition) => { +/******/ if(Array.isArray(definition)) { +/******/ var i = 0; +/******/ while(i < definition.length) { +/******/ var key = definition[i++]; +/******/ var binding = definition[i++]; +/******/ if(!__webpack_require__.o(exports, key)) { +/******/ if(binding === 0) { +/******/ Object.defineProperty(exports, key, { enumerable: true, value: definition[i++] }); +/******/ } else { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: binding }); +/******/ } +/******/ } else if(binding === 0) { i++; } +/******/ } +/******/ } else { +/******/ for(var key in definition) { +/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); +/******/ } +/******/ } +/******/ } +/******/ }; +/******/ +/******/ /* webpack/runtime/ensure chunk */ +/******/ __webpack_require__.f = {}; +/******/ // This file contains only the entry chunk. +/******/ // The chunk loading function for additional chunks +/******/ __webpack_require__.e = (chunkId) => { +/******/ return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => { +/******/ __webpack_require__.f[key](chunkId, promises); +/******/ return promises; +/******/ }, [])); +/******/ }; +/******/ +/******/ /* webpack/runtime/get javascript chunk filename */ +/******/ // This function allow to reference async chunks +/******/ __webpack_require__.u = (chunkId) => { +/******/ // return url for filenames based on template +/******/ return "" + chunkId + ".js"; +/******/ }; +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)); +/******/ +/******/ /* webpack/runtime/load script */ +/******/ (() => { +/******/ const inProgress = {}; +/******/ const dataWebpackPrefix = "module-federation-bbb:"; +/******/ // loadScript function to load a script via script tag +/******/ __webpack_require__.l = (url, done, key, chunkId) => { +/******/ if(inProgress[url]) { inProgress[url].push(done); return; } +/******/ let script, needAttach; +/******/ if(key !== undefined) { +/******/ const scripts = document.getElementsByTagName("script"); +/******/ for(var i = 0; i < scripts.length; i++) { +/******/ const s = scripts[i]; +/******/ if(s.getAttribute("src") == url || s.getAttribute("data-webpack") == dataWebpackPrefix + key) { script = s; break; } +/******/ } +/******/ } +/******/ if(!script) { +/******/ needAttach = true; +/******/ script = document.createElement('script'); +/******/ +/******/ script.charset = 'utf-8'; +/******/ if (__webpack_require__.nc) { +/******/ script.setAttribute("nonce", __webpack_require__.nc); +/******/ } +/******/ script.setAttribute("data-webpack", dataWebpackPrefix + key); +/******/ +/******/ script.src = url; +/******/ } +/******/ inProgress[url] = [done]; +/******/ const onScriptComplete = (prev, event) => { +/******/ // avoid mem leaks in IE. +/******/ script.onerror = script.onload = null; +/******/ clearTimeout(timeout); +/******/ const doneFns = inProgress[url]; +/******/ delete inProgress[url]; +/******/ script.parentNode?.removeChild(script); +/******/ doneFns?.forEach((fn) => (fn(event))); +/******/ if(prev) return prev(event); +/******/ } +/******/ const timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000); +/******/ script.onerror = onScriptComplete.bind(null, script.onerror); +/******/ script.onload = onScriptComplete.bind(null, script.onload); +/******/ needAttach && document.head.appendChild(script); +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/make namespace object */ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = (exports) => { +/******/ if(Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/******/ /* webpack/runtime/sharing */ +/******/ (() => { +/******/ __webpack_require__.S = {}; +/******/ const initPromises = {}; +/******/ const initTokens = {}; +/******/ __webpack_require__.I = (name, initScope) => { +/******/ if(!initScope) initScope = []; +/******/ // handling circular init calls +/******/ let initToken = initTokens[name]; +/******/ if(!initToken) initToken = initTokens[name] = {}; +/******/ if(initScope.indexOf(initToken) >= 0) return; +/******/ initScope.push(initToken); +/******/ // only runs once +/******/ if(initPromises[name]) return initPromises[name]; +/******/ // creates a new share scope if needed +/******/ if(!__webpack_require__.o(__webpack_require__.S, name)) __webpack_require__.S[name] = {}; +/******/ // runs all init snippets from all modules reachable +/******/ const scope = __webpack_require__.S[name]; +/******/ const warn = (msg) => { +/******/ if (typeof console !== "undefined" && console.warn) console.warn(msg); +/******/ }; +/******/ const uniqueName = "module-federation-bbb"; +/******/ const register = (name, version, factory, eager) => { +/******/ const versions = scope[name] = scope[name] || {}; +/******/ const activeVersion = versions[version]; +/******/ if(!activeVersion || (!activeVersion.loaded && (!eager != !activeVersion.eager ? eager : uniqueName > activeVersion.from))) versions[version] = { get: factory, from: uniqueName, eager: !!eager }; +/******/ }; +/******/ const initExternal = (id) => { +/******/ const handleError = (err) => (warn("Initialization of sharing external failed: " + err)); +/******/ try { +/******/ const module = __webpack_require__(id); +/******/ if(!module) return; +/******/ const initFn = (module) => (module && module.init && module.init(__webpack_require__.S[name], initScope)) +/******/ if(module.then) return promises.push(module.then(initFn, handleError)); +/******/ const initResult = initFn(module); +/******/ if(initResult?.then) return promises.push(initResult['catch'](handleError)); +/******/ } catch(err) { handleError(err); } +/******/ } +/******/ const promises = []; +/******/ switch(name) { +/******/ case "default": { +/******/ register("date-fns", "4.4.0", () => (__webpack_require__.e("vendors-node_modules_date-fns_index_js").then(() => (() => (__webpack_require__(/*! ../../node_modules/date-fns/index.js */ 8)))))); +/******/ register("react", "19.2.8", () => (__webpack_require__.e("vendors-node_modules_react_index_js").then(() => (() => (__webpack_require__(/*! ../../node_modules/react/index.js */ 311)))))); +/******/ } +/******/ break; +/******/ } +/******/ if(!promises.length) return initPromises[name] = 1; +/******/ return initPromises[name] = Promise.all(promises).then(() => (initPromises[name] = 1)); +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/publicPath */ +/******/ __webpack_require__.p = "dist/bbb/"; +/******/ +/******/ /* webpack/runtime/consumes */ +/******/ (() => { +/******/ var parseVersion = (str) => { +/******/ // see webpack/lib/util/semver.js for original code +/******/ var p=p=>{return p.split(".").map(p=>{return+p==p?+p:p})},n=/^([^-+]+)?(?:-([^+]+))?(?:\+(.+))?$/.exec(str),r=n[1]?p(n[1]):[];return n[2]&&(r.length++,r.push.apply(r,p(n[2]))),n[3]&&(r.push([]),r.push.apply(r,p(n[3]))),r; +/******/ } +/******/ var versionLt = (a, b) => { +/******/ // see webpack/lib/util/semver.js for original code +/******/ a=parseVersion(a),b=parseVersion(b);for(var r=0;;){if(r>=a.length)return r=b.length)return"u"==n;var t=b[r],f=(typeof t)[0];if(n!=f)return"o"==n&&"n"==f||("s"==f||"u"==n);if("o"!=n&&"u"!=n&&e!=t)return e { +/******/ // see webpack/lib/util/semver.js for original code +/******/ var r=range[0],n="";if(1===range.length)return"*";if(r+.5){n+=0==r?">=":-1==r?"<":1==r?"^":2==r?"~":r>0?"=":"!=";for(var e=1,a=1;a0?".":"")+(e=2,t)}return n}var g=[];for(a=1;a { +/******/ // see webpack/lib/util/semver.js for original code +/******/ if(0 in range){version=parseVersion(version);var e=range[0],r=e<0;r&&(e=-e-1);for(var n=0,i=1,a=!0;;i++,n++){var f,s,g=i=version.length||"o"==(s=(typeof(f=version[n]))[0]))return!a||("u"==g?i>e&&!r:""==g!=r);if("u"==s){if(!a||"u"!=g)return!1}else if(a)if(g==s)if(i<=e){if(f!=range[i])return!1}else{if(r?f>range[i]:f { +/******/ return scope && __webpack_require__.o(scope, key); +/******/ } +/******/ const get = (entry) => { +/******/ entry.loaded = 1; +/******/ return entry.get() +/******/ }; +/******/ const eagerOnly = (versions) => { +/******/ return Object.keys(versions).reduce((filtered, version) => { +/******/ if (versions[version].eager) { +/******/ filtered[version] = versions[version]; +/******/ } +/******/ return filtered; +/******/ }, {}); +/******/ }; +/******/ const findLatestVersion = (scope, key, eager) => { +/******/ const versions = eager ? eagerOnly(scope[key]) : scope[key]; +/******/ var key = Object.keys(versions).reduce((a, b) => { +/******/ return !a || versionLt(a, b) ? b : a; +/******/ }, 0); +/******/ return key && versions[key]; +/******/ }; +/******/ const findSatisfyingVersion = (scope, key, requiredVersion, eager) => { +/******/ const versions = eager ? eagerOnly(scope[key]) : scope[key]; +/******/ var key = Object.keys(versions).reduce((a, b) => { +/******/ if (!satisfy(requiredVersion, b)) return a; +/******/ return !a || versionLt(a, b) ? b : a; +/******/ }, 0); +/******/ return key && versions[key] +/******/ }; +/******/ const findSingletonVersionKey = (scope, key, eager) => { +/******/ const versions = eager ? eagerOnly(scope[key]) : scope[key]; +/******/ return Object.keys(versions).reduce((a, b) => { +/******/ return !a || (!versions[a].loaded && versionLt(a, b)) ? b : a; +/******/ }, 0); +/******/ }; +/******/ const getInvalidSingletonVersionMessage = (scope, key, version, requiredVersion) => { +/******/ return "Unsatisfied version " + version + " from " + (version && scope[key][version].from) + " of shared singleton module " + key + " (required " + rangeToString(requiredVersion) + ")" +/******/ }; +/******/ const getInvalidVersionMessage = (scope, scopeName, key, requiredVersion, eager) => { +/******/ const versions = scope[key]; +/******/ return "No satisfying version (" + rangeToString(requiredVersion) + ")" + (eager ? " for eager consumption" : "") + " of shared module " + key + " found in shared scope " + scopeName + ".\n" + +/******/ "Available versions: " + Object.keys(versions).map((key) => { +/******/ return key + " from " + versions[key].from; +/******/ }).join(", "); +/******/ }; +/******/ const fail = (msg) => { +/******/ throw new Error(msg); +/******/ } +/******/ const failAsNotExist = (scopeName, key) => { +/******/ return fail("Shared module " + key + " doesn't exist in shared scope " + scopeName); +/******/ } +/******/ const warn = /*#__PURE__*/ (msg) => { +/******/ if (typeof console !== "undefined" && console.warn) console.warn(msg); +/******/ }; +/******/ const init = (fn) => (function(scopeName, key, eager, c, d) { +/******/ const promise = __webpack_require__.I(scopeName); +/******/ if (promise?.then && !eager) { +/******/ return promise.then(fn.bind(fn, scopeName, __webpack_require__.S[scopeName], key, false, c, d)); +/******/ } +/******/ return fn(scopeName, __webpack_require__.S[scopeName], key, eager, c, d); +/******/ }); +/******/ +/******/ const useFallback = (scopeName, key, fallback) => { +/******/ return fallback ? fallback() : failAsNotExist(scopeName, key); +/******/ } +/******/ const load = /*#__PURE__*/ init((scopeName, scope, key, eager, fallback) => { +/******/ if (!exists(scope, key)) return useFallback(scopeName, key, fallback); +/******/ return get(findLatestVersion(scope, key, eager)); +/******/ }); +/******/ const loadVersion = /*#__PURE__*/ init((scopeName, scope, key, eager, requiredVersion, fallback) => { +/******/ if (!exists(scope, key)) return useFallback(scopeName, key, fallback); +/******/ const satisfyingVersion = findSatisfyingVersion(scope, key, requiredVersion, eager); +/******/ if (satisfyingVersion) return get(satisfyingVersion); +/******/ warn(getInvalidVersionMessage(scope, scopeName, key, requiredVersion, eager)) +/******/ return get(findLatestVersion(scope, key, eager)); +/******/ }); +/******/ const loadStrictVersion = /*#__PURE__*/ init((scopeName, scope, key, eager, requiredVersion, fallback) => { +/******/ if (!exists(scope, key)) return useFallback(scopeName, key, fallback); +/******/ const satisfyingVersion = findSatisfyingVersion(scope, key, requiredVersion, eager); +/******/ if (satisfyingVersion) return get(satisfyingVersion); +/******/ if (fallback) return fallback(); +/******/ fail(getInvalidVersionMessage(scope, scopeName, key, requiredVersion, eager)); +/******/ }); +/******/ const loadSingleton = /*#__PURE__*/ init((scopeName, scope, key, eager, fallback) => { +/******/ if (!exists(scope, key)) return useFallback(scopeName, key, fallback); +/******/ const version = findSingletonVersionKey(scope, key, eager); +/******/ return get(scope[key][version]); +/******/ }); +/******/ const loadSingletonVersion = /*#__PURE__*/ init((scopeName, scope, key, eager, requiredVersion, fallback) => { +/******/ if (!exists(scope, key)) return useFallback(scopeName, key, fallback); +/******/ const version = findSingletonVersionKey(scope, key, eager); +/******/ if (!satisfy(requiredVersion, version)) { +/******/ warn(getInvalidSingletonVersionMessage(scope, key, version, requiredVersion)); +/******/ } +/******/ return get(scope[key][version]); +/******/ }); +/******/ const loadStrictSingletonVersion = /*#__PURE__*/ init((scopeName, scope, key, eager, requiredVersion, fallback) => { +/******/ if (!exists(scope, key)) return useFallback(scopeName, key, fallback); +/******/ const version = findSingletonVersionKey(scope, key, eager); +/******/ if (!satisfy(requiredVersion, version)) { +/******/ fail(getInvalidSingletonVersionMessage(scope, key, version, requiredVersion)); +/******/ } +/******/ return get(scope[key][version]); +/******/ }); +/******/ const installedModules = {}; +/******/ const moduleToHandlerMapping = { +/******/ 4: () => (loadSingletonVersion("default", "react", false, [1,19,2,7], () => (__webpack_require__.e("vendors-node_modules_react_index_js").then(() => (() => (__webpack_require__(/*! react */ 311))))))), +/******/ 5: () => (loadStrictVersion("default", "date-fns", false, [1,4,4,0], () => (__webpack_require__.e("vendors-node_modules_date-fns_index_js").then(() => (() => (__webpack_require__(/*! date-fns */ 8))))))) +/******/ }; +/******/ // no consumes in initial chunks +/******/ const chunkMapping = { +/******/ "src-b_Component_js": [ +/******/ 4, +/******/ 5 +/******/ ] +/******/ }; +/******/ const startedInstallModules = {}; +/******/ __webpack_require__.f.consumes = (chunkId, promises) => { +/******/ if(__webpack_require__.o(chunkMapping, chunkId)) { +/******/ chunkMapping[chunkId].forEach((id) => { +/******/ if(__webpack_require__.o(installedModules, id)) return promises.push(installedModules[id]); +/******/ if(!startedInstallModules[id]) { +/******/ const onFactory = (factory) => { +/******/ installedModules[id] = 0; +/******/ __webpack_require__.m[id] = (module) => { +/******/ delete __webpack_require__.c[id]; +/******/ module.exports = factory(); +/******/ } +/******/ }; +/******/ startedInstallModules[id] = true; +/******/ const onError = (error) => { +/******/ delete installedModules[id]; +/******/ __webpack_require__.m[id] = (module) => { +/******/ delete __webpack_require__.c[id]; +/******/ throw error; +/******/ } +/******/ }; +/******/ try { +/******/ const promise = moduleToHandlerMapping[id](); +/******/ if(promise.then) { +/******/ promises.push(installedModules[id] = promise.then(onFactory)['catch'](onError)); +/******/ } else onFactory(promise); +/******/ } catch(e) { onError(e); } +/******/ } +/******/ }); +/******/ } +/******/ } +/******/ })(); +/******/ +/******/ /* webpack/runtime/jsonp chunk loading */ +/******/ (() => { +/******/ // no baseURI +/******/ +/******/ // object to store loaded and loading chunks +/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched +/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded +/******/ const installedChunks = { +/******/ "mfeBBB": 0 +/******/ }; +/******/ +/******/ __webpack_require__.f.j = (chunkId, promises) => { +/******/ // JSONP chunk loading for javascript +/******/ let installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined; +/******/ if(installedChunkData !== 0) { // 0 means "already installed". +/******/ +/******/ // a Promise means "currently loading". +/******/ if(installedChunkData) { +/******/ promises.push(installedChunkData[2]); +/******/ } else { +/******/ if(true) { // all chunks have JS +/******/ // setup Promise in chunk cache +/******/ const promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject])); +/******/ promises.push(installedChunkData[2] = promise); +/******/ +/******/ // start chunk loading +/******/ const url = __webpack_require__.p + __webpack_require__.u(chunkId); +/******/ // create error before stack unwound to get useful stacktrace later +/******/ const error = new Error(); +/******/ const loadingEnded = (event) => { +/******/ if(__webpack_require__.o(installedChunks, chunkId)) { +/******/ installedChunkData = installedChunks[chunkId]; +/******/ if(installedChunkData !== 0) installedChunks[chunkId] = undefined; +/******/ if(installedChunkData) { +/******/ const errorType = event && (event.type === 'load' ? 'missing' : event.type); +/******/ const realSrc = event && event.target && event.target.src; +/******/ error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'; +/******/ error.name = 'ChunkLoadError'; +/******/ error.type = errorType; +/******/ error.request = realSrc; +/******/ error.event = event; +/******/ installedChunkData[1](error); +/******/ } +/******/ } +/******/ }; +/******/ __webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId); +/******/ } +/******/ } +/******/ } +/******/ }; +/******/ +/******/ // no prefetching +/******/ +/******/ // no preloaded +/******/ +/******/ // no HMR +/******/ +/******/ // no HMR manifest +/******/ +/******/ // no on chunks loaded +/******/ +/******/ // install a JSONP callback for chunk loading +/******/ const webpackJsonpCallback = (parentChunkLoadingFunction, data) => { +/******/ let [chunkIds, moreModules, runtime] = data; +/******/ // add "moreModules" to the modules object, +/******/ // then flag all "chunkIds" as loaded and fire callback +/******/ var moduleId, chunkId, i = 0; +/******/ if(chunkIds.some((id) => (installedChunks[id] !== 0))) { +/******/ for(moduleId in moreModules) { +/******/ if(__webpack_require__.o(moreModules, moduleId)) { +/******/ __webpack_require__.m[moduleId] = moreModules[moduleId]; +/******/ } +/******/ } +/******/ if(runtime) var result = runtime(__webpack_require__); +/******/ } +/******/ if(parentChunkLoadingFunction) parentChunkLoadingFunction(data); +/******/ for(;i < chunkIds.length; i++) { +/******/ chunkId = chunkIds[i]; +/******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) { +/******/ installedChunks[chunkId][0](); +/******/ } +/******/ installedChunks[chunkId] = 0; +/******/ } +/******/ +/******/ } +/******/ +/******/ const chunkLoadingGlobal = self["webpackChunkmodule_federation_bbb"] = self["webpackChunkmodule_federation_bbb"] || []; +/******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0)); +/******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal)); +/******/ })(); +/******/ +/************************************************************************/ +``` + +
+ +``` js +/******/ +/******/ // module cache are used so entry inlining is disabled +/******/ // startup +/******/ // Load entry module and return exports +/******/ let __webpack_exports__ = __webpack_require__(0); +/******/ mfeBBB = __webpack_exports__; +/******/ +/******/ })() +; +``` + +# dist/ccc/mfeCCC.js + +```javascript +var mfeCCC; +/******/ (() => { // webpackBootstrap +/******/ "use strict"; +/******/ var __webpack_modules__ = ([ +/* 0 */ +/*!***********************!*\ + !*** container entry ***! + \***********************/ +/*! namespace exports */ +/*! export get [provided] [maybe used in mfeCCC (runtime-defined)] [usage and provision prevents renaming] */ +/*! export init [provided] [maybe used in mfeCCC (runtime-defined)] [usage and provision prevents renaming] */ +/*! other exports [not provided] [maybe used in mfeCCC (runtime-defined)] */ +/*! runtime requirements: __webpack_require__.d, __webpack_require__.o, __webpack_exports__, __webpack_require__.e, __webpack_require__, __webpack_require__.* */ +/***/ ((__unused_webpack_module, exports, __webpack_require__) => { + +const moduleMap = { + "./Component": () => { + return Promise.all([__webpack_require__.e("webpack_sharing_consume_default_react"), __webpack_require__.e("src-c_Component_js")]).then(() => (() => ((__webpack_require__(/*! ./src-c/Component */ 3))))); + }, + "./Component2": () => { + return Promise.all([__webpack_require__.e("webpack_sharing_consume_default_react"), __webpack_require__.e("src-c_LazyComponent_js")]).then(() => (() => ((__webpack_require__(/*! ./src-c/LazyComponent */ 8))))); + } +}; +const get = (module, getScope) => { + __webpack_require__.R = getScope; + getScope = ( + __webpack_require__.o(moduleMap, module) + ? moduleMap[module]() + : Promise.resolve().then(() => { + throw new Error('Module "' + module + '" does not exist in container.'); + }) + ); + __webpack_require__.R = undefined; + return getScope; +}; +const init = (shareScope, initScope) => { + if (!__webpack_require__.S) return; + const name = "default" + const oldScope = __webpack_require__.S[name]; + if(oldScope && oldScope !== shareScope) throw new Error("Container initialization failed as it has already been initialized with a different share scope"); + __webpack_require__.S[name] = shareScope; + return __webpack_require__.I(name, initScope); +}; + +// This exports getters to disallow modifications +__webpack_require__.d(exports, { + get: () => (get), + init: () => (init) +}); + +/***/ }) +/******/ ]); +``` + +
/* webpack runtime code */ + +``` js +/************************************************************************/ +/******/ // The module cache +/******/ const __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ const 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; +/******/ } +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = __webpack_modules__; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = __webpack_module_cache__; +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/compat get default export */ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = (module) => { +/******/ const getter = module && module.__esModule ? +/******/ () => (module['default']) : +/******/ () => (module); +/******/ __webpack_require__.d(getter, { a: getter }); +/******/ return getter; +/******/ }; +/******/ +/******/ /* webpack/runtime/define property getters */ +/******/ // define getter/value functions for harmony exports +/******/ __webpack_require__.d = (exports, definition) => { +/******/ if(Array.isArray(definition)) { +/******/ var i = 0; +/******/ while(i < definition.length) { +/******/ var key = definition[i++]; +/******/ var binding = definition[i++]; +/******/ if(!__webpack_require__.o(exports, key)) { +/******/ if(binding === 0) { +/******/ Object.defineProperty(exports, key, { enumerable: true, value: definition[i++] }); +/******/ } else { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: binding }); +/******/ } +/******/ } else if(binding === 0) { i++; } +/******/ } +/******/ } else { +/******/ for(var key in definition) { +/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); +/******/ } +/******/ } +/******/ } +/******/ }; +/******/ +/******/ /* webpack/runtime/ensure chunk */ +/******/ __webpack_require__.f = {}; +/******/ // This file contains only the entry chunk. +/******/ // The chunk loading function for additional chunks +/******/ __webpack_require__.e = (chunkId) => { +/******/ return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => { +/******/ __webpack_require__.f[key](chunkId, promises); +/******/ return promises; +/******/ }, [])); +/******/ }; +/******/ +/******/ /* webpack/runtime/get javascript chunk filename */ +/******/ // This function allow to reference async chunks +/******/ __webpack_require__.u = (chunkId) => { +/******/ // return url for filenames based on template +/******/ return "" + chunkId + ".js"; +/******/ }; +/******/ +/******/ /* webpack/runtime/global */ +/******/ __webpack_require__.g = (function() { +/******/ if (typeof globalThis === 'object') return globalThis; +/******/ try { +/******/ return this || new Function('return this')(); +/******/ } catch (e) { +/******/ if (typeof window === 'object') return window; +/******/ } +/******/ })(); +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)); +/******/ +/******/ /* webpack/runtime/load script */ +/******/ (() => { +/******/ const inProgress = {}; +/******/ const dataWebpackPrefix = "module-federation-ccc:"; +/******/ // loadScript function to load a script via script tag +/******/ __webpack_require__.l = (url, done, key, chunkId) => { +/******/ if(inProgress[url]) { inProgress[url].push(done); return; } +/******/ let script, needAttach; +/******/ if(key !== undefined) { +/******/ const scripts = document.getElementsByTagName("script"); +/******/ for(var i = 0; i < scripts.length; i++) { +/******/ const s = scripts[i]; +/******/ if(s.getAttribute("src") == url || s.getAttribute("data-webpack") == dataWebpackPrefix + key) { script = s; break; } +/******/ } +/******/ } +/******/ if(!script) { +/******/ needAttach = true; +/******/ script = document.createElement('script'); +/******/ +/******/ script.charset = 'utf-8'; +/******/ if (__webpack_require__.nc) { +/******/ script.setAttribute("nonce", __webpack_require__.nc); +/******/ } +/******/ script.setAttribute("data-webpack", dataWebpackPrefix + key); +/******/ +/******/ script.src = url; +/******/ } +/******/ inProgress[url] = [done]; +/******/ const onScriptComplete = (prev, event) => { +/******/ // avoid mem leaks in IE. +/******/ script.onerror = script.onload = null; +/******/ clearTimeout(timeout); +/******/ const doneFns = inProgress[url]; +/******/ delete inProgress[url]; +/******/ script.parentNode?.removeChild(script); +/******/ doneFns?.forEach((fn) => (fn(event))); +/******/ if(prev) return prev(event); +/******/ } +/******/ const timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000); +/******/ script.onerror = onScriptComplete.bind(null, script.onerror); +/******/ script.onload = onScriptComplete.bind(null, script.onload); +/******/ needAttach && document.head.appendChild(script); +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/make namespace object */ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = (exports) => { +/******/ if(Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/******/ /* webpack/runtime/sharing */ +/******/ (() => { +/******/ __webpack_require__.S = {}; +/******/ const initPromises = {}; +/******/ const initTokens = {}; +/******/ __webpack_require__.I = (name, initScope) => { +/******/ if(!initScope) initScope = []; +/******/ // handling circular init calls +/******/ let initToken = initTokens[name]; +/******/ if(!initToken) initToken = initTokens[name] = {}; +/******/ if(initScope.indexOf(initToken) >= 0) return; +/******/ initScope.push(initToken); +/******/ // only runs once +/******/ if(initPromises[name]) return initPromises[name]; +/******/ // creates a new share scope if needed +/******/ if(!__webpack_require__.o(__webpack_require__.S, name)) __webpack_require__.S[name] = {}; +/******/ // runs all init snippets from all modules reachable +/******/ const scope = __webpack_require__.S[name]; +/******/ const warn = (msg) => { +/******/ if (typeof console !== "undefined" && console.warn) console.warn(msg); +/******/ }; +/******/ const uniqueName = "module-federation-ccc"; +/******/ const register = (name, version, factory, eager) => { +/******/ const versions = scope[name] = scope[name] || {}; +/******/ const activeVersion = versions[version]; +/******/ if(!activeVersion || (!activeVersion.loaded && (!eager != !activeVersion.eager ? eager : uniqueName > activeVersion.from))) versions[version] = { get: factory, from: uniqueName, eager: !!eager }; +/******/ }; +/******/ const initExternal = (id) => { +/******/ const handleError = (err) => (warn("Initialization of sharing external failed: " + err)); +/******/ try { +/******/ const module = __webpack_require__(id); +/******/ if(!module) return; +/******/ const initFn = (module) => (module && module.init && module.init(__webpack_require__.S[name], initScope)) +/******/ if(module.then) return promises.push(module.then(initFn, handleError)); +/******/ const initResult = initFn(module); +/******/ if(initResult?.then) return promises.push(initResult['catch'](handleError)); +/******/ } catch(err) { handleError(err); } +/******/ } +/******/ const promises = []; +/******/ switch(name) { +/******/ case "default": { +/******/ register("date-fns", "4.4.0", () => (__webpack_require__.e("vendors-node_modules_date-fns_index_js").then(() => (() => (__webpack_require__(/*! ../../node_modules/date-fns/index.js */ 10)))))); +/******/ register("lodash/random", "4.18.1", () => (__webpack_require__.e("vendors-node_modules_lodash_random_js").then(() => (() => (__webpack_require__(/*! ../../node_modules/lodash/random.js */ 313)))))); +/******/ } +/******/ break; +/******/ } +/******/ if(!promises.length) return initPromises[name] = 1; +/******/ return initPromises[name] = Promise.all(promises).then(() => (initPromises[name] = 1)); +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/publicPath */ +/******/ __webpack_require__.p = "dist/ccc/"; +/******/ +/******/ /* webpack/runtime/consumes */ +/******/ (() => { +/******/ var parseVersion = (str) => { +/******/ // see webpack/lib/util/semver.js for original code +/******/ var p=p=>{return p.split(".").map(p=>{return+p==p?+p:p})},n=/^([^-+]+)?(?:-([^+]+))?(?:\+(.+))?$/.exec(str),r=n[1]?p(n[1]):[];return n[2]&&(r.length++,r.push.apply(r,p(n[2]))),n[3]&&(r.push([]),r.push.apply(r,p(n[3]))),r; +/******/ } +/******/ var versionLt = (a, b) => { +/******/ // see webpack/lib/util/semver.js for original code +/******/ a=parseVersion(a),b=parseVersion(b);for(var r=0;;){if(r>=a.length)return r=b.length)return"u"==n;var t=b[r],f=(typeof t)[0];if(n!=f)return"o"==n&&"n"==f||("s"==f||"u"==n);if("o"!=n&&"u"!=n&&e!=t)return e { +/******/ // see webpack/lib/util/semver.js for original code +/******/ var r=range[0],n="";if(1===range.length)return"*";if(r+.5){n+=0==r?">=":-1==r?"<":1==r?"^":2==r?"~":r>0?"=":"!=";for(var e=1,a=1;a0?".":"")+(e=2,t)}return n}var g=[];for(a=1;a { +/******/ // see webpack/lib/util/semver.js for original code +/******/ if(0 in range){version=parseVersion(version);var e=range[0],r=e<0;r&&(e=-e-1);for(var n=0,i=1,a=!0;;i++,n++){var f,s,g=i=version.length||"o"==(s=(typeof(f=version[n]))[0]))return!a||("u"==g?i>e&&!r:""==g!=r);if("u"==s){if(!a||"u"!=g)return!1}else if(a)if(g==s)if(i<=e){if(f!=range[i])return!1}else{if(r?f>range[i]:f { +/******/ return scope && __webpack_require__.o(scope, key); +/******/ } +/******/ const get = (entry) => { +/******/ entry.loaded = 1; +/******/ return entry.get() +/******/ }; +/******/ const eagerOnly = (versions) => { +/******/ return Object.keys(versions).reduce((filtered, version) => { +/******/ if (versions[version].eager) { +/******/ filtered[version] = versions[version]; +/******/ } +/******/ return filtered; +/******/ }, {}); +/******/ }; +/******/ const findLatestVersion = (scope, key, eager) => { +/******/ const versions = eager ? eagerOnly(scope[key]) : scope[key]; +/******/ var key = Object.keys(versions).reduce((a, b) => { +/******/ return !a || versionLt(a, b) ? b : a; +/******/ }, 0); +/******/ return key && versions[key]; +/******/ }; +/******/ const findSatisfyingVersion = (scope, key, requiredVersion, eager) => { +/******/ const versions = eager ? eagerOnly(scope[key]) : scope[key]; +/******/ var key = Object.keys(versions).reduce((a, b) => { +/******/ if (!satisfy(requiredVersion, b)) return a; +/******/ return !a || versionLt(a, b) ? b : a; +/******/ }, 0); +/******/ return key && versions[key] +/******/ }; +/******/ const findSingletonVersionKey = (scope, key, eager) => { +/******/ const versions = eager ? eagerOnly(scope[key]) : scope[key]; +/******/ return Object.keys(versions).reduce((a, b) => { +/******/ return !a || (!versions[a].loaded && versionLt(a, b)) ? b : a; +/******/ }, 0); +/******/ }; +/******/ const getInvalidSingletonVersionMessage = (scope, key, version, requiredVersion) => { +/******/ return "Unsatisfied version " + version + " from " + (version && scope[key][version].from) + " of shared singleton module " + key + " (required " + rangeToString(requiredVersion) + ")" +/******/ }; +/******/ const getInvalidVersionMessage = (scope, scopeName, key, requiredVersion, eager) => { +/******/ const versions = scope[key]; +/******/ return "No satisfying version (" + rangeToString(requiredVersion) + ")" + (eager ? " for eager consumption" : "") + " of shared module " + key + " found in shared scope " + scopeName + ".\n" + +/******/ "Available versions: " + Object.keys(versions).map((key) => { +/******/ return key + " from " + versions[key].from; +/******/ }).join(", "); +/******/ }; +/******/ const fail = (msg) => { +/******/ throw new Error(msg); +/******/ } +/******/ const failAsNotExist = (scopeName, key) => { +/******/ return fail("Shared module " + key + " doesn't exist in shared scope " + scopeName); +/******/ } +/******/ const warn = /*#__PURE__*/ (msg) => { +/******/ if (typeof console !== "undefined" && console.warn) console.warn(msg); +/******/ }; +/******/ const init = (fn) => (function(scopeName, key, eager, c, d) { +/******/ const promise = __webpack_require__.I(scopeName); +/******/ if (promise?.then && !eager) { +/******/ return promise.then(fn.bind(fn, scopeName, __webpack_require__.S[scopeName], key, false, c, d)); +/******/ } +/******/ return fn(scopeName, __webpack_require__.S[scopeName], key, eager, c, d); +/******/ }); +/******/ +/******/ const useFallback = (scopeName, key, fallback) => { +/******/ return fallback ? fallback() : failAsNotExist(scopeName, key); +/******/ } +/******/ const load = /*#__PURE__*/ init((scopeName, scope, key, eager, fallback) => { +/******/ if (!exists(scope, key)) return useFallback(scopeName, key, fallback); +/******/ return get(findLatestVersion(scope, key, eager)); +/******/ }); +/******/ const loadVersion = /*#__PURE__*/ init((scopeName, scope, key, eager, requiredVersion, fallback) => { +/******/ if (!exists(scope, key)) return useFallback(scopeName, key, fallback); +/******/ const satisfyingVersion = findSatisfyingVersion(scope, key, requiredVersion, eager); +/******/ if (satisfyingVersion) return get(satisfyingVersion); +/******/ warn(getInvalidVersionMessage(scope, scopeName, key, requiredVersion, eager)) +/******/ return get(findLatestVersion(scope, key, eager)); +/******/ }); +/******/ const loadStrictVersion = /*#__PURE__*/ init((scopeName, scope, key, eager, requiredVersion, fallback) => { +/******/ if (!exists(scope, key)) return useFallback(scopeName, key, fallback); +/******/ const satisfyingVersion = findSatisfyingVersion(scope, key, requiredVersion, eager); +/******/ if (satisfyingVersion) return get(satisfyingVersion); +/******/ if (fallback) return fallback(); +/******/ fail(getInvalidVersionMessage(scope, scopeName, key, requiredVersion, eager)); +/******/ }); +/******/ const loadSingleton = /*#__PURE__*/ init((scopeName, scope, key, eager, fallback) => { +/******/ if (!exists(scope, key)) return useFallback(scopeName, key, fallback); +/******/ const version = findSingletonVersionKey(scope, key, eager); +/******/ return get(scope[key][version]); +/******/ }); +/******/ const loadSingletonVersion = /*#__PURE__*/ init((scopeName, scope, key, eager, requiredVersion, fallback) => { +/******/ if (!exists(scope, key)) return useFallback(scopeName, key, fallback); +/******/ const version = findSingletonVersionKey(scope, key, eager); +/******/ if (!satisfy(requiredVersion, version)) { +/******/ warn(getInvalidSingletonVersionMessage(scope, key, version, requiredVersion)); +/******/ } +/******/ return get(scope[key][version]); +/******/ }); +/******/ const loadStrictSingletonVersion = /*#__PURE__*/ init((scopeName, scope, key, eager, requiredVersion, fallback) => { +/******/ if (!exists(scope, key)) return useFallback(scopeName, key, fallback); +/******/ const version = findSingletonVersionKey(scope, key, eager); +/******/ if (!satisfy(requiredVersion, version)) { +/******/ fail(getInvalidSingletonVersionMessage(scope, key, version, requiredVersion)); +/******/ } +/******/ return get(scope[key][version]); +/******/ }); +/******/ const installedModules = {}; +/******/ const moduleToHandlerMapping = { +/******/ 4: () => (loadSingletonVersion("default", "react", false, [1,19,2,7])), +/******/ 5: () => (loadStrictVersion("default", "date-fns", false, [1,4,4,0], () => (__webpack_require__.e("vendors-node_modules_date-fns_index_js").then(() => (() => (__webpack_require__(/*! date-fns */ 10))))))), +/******/ 9: () => (loadStrictVersion("default", "lodash/random", false, [1,4,17,19], () => (__webpack_require__.e("vendors-node_modules_lodash_random_js").then(() => (() => (__webpack_require__(/*! lodash/random */ 313))))))) +/******/ }; +/******/ // no consumes in initial chunks +/******/ const chunkMapping = { +/******/ "webpack_sharing_consume_default_react": [ +/******/ 4 +/******/ ], +/******/ "src-c_Component_js": [ +/******/ 5 +/******/ ], +/******/ "src-c_LazyComponent_js": [ +/******/ 9 +/******/ ] +/******/ }; +/******/ const startedInstallModules = {}; +/******/ __webpack_require__.f.consumes = (chunkId, promises) => { +/******/ if(__webpack_require__.o(chunkMapping, chunkId)) { +/******/ chunkMapping[chunkId].forEach((id) => { +/******/ if(__webpack_require__.o(installedModules, id)) return promises.push(installedModules[id]); +/******/ if(!startedInstallModules[id]) { +/******/ const onFactory = (factory) => { +/******/ installedModules[id] = 0; +/******/ __webpack_require__.m[id] = (module) => { +/******/ delete __webpack_require__.c[id]; +/******/ module.exports = factory(); +/******/ } +/******/ }; +/******/ startedInstallModules[id] = true; +/******/ const onError = (error) => { +/******/ delete installedModules[id]; +/******/ __webpack_require__.m[id] = (module) => { +/******/ delete __webpack_require__.c[id]; +/******/ throw error; +/******/ } +/******/ }; +/******/ try { +/******/ const promise = moduleToHandlerMapping[id](); +/******/ if(promise.then) { +/******/ promises.push(installedModules[id] = promise.then(onFactory)['catch'](onError)); +/******/ } else onFactory(promise); +/******/ } catch(e) { onError(e); } +/******/ } +/******/ }); +/******/ } +/******/ } +/******/ })(); +/******/ +/******/ /* webpack/runtime/jsonp chunk loading */ +/******/ (() => { +/******/ // no baseURI +/******/ +/******/ // object to store loaded and loading chunks +/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched +/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded +/******/ const installedChunks = { +/******/ "mfeCCC": 0 +/******/ }; +/******/ +/******/ __webpack_require__.f.j = (chunkId, promises) => { +/******/ // JSONP chunk loading for javascript +/******/ let installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined; +/******/ if(installedChunkData !== 0) { // 0 means "already installed". +/******/ +/******/ // a Promise means "currently loading". +/******/ if(installedChunkData) { +/******/ promises.push(installedChunkData[2]); +/******/ } else { +/******/ if("webpack_sharing_consume_default_react" != chunkId) { +/******/ // setup Promise in chunk cache +/******/ const promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject])); +/******/ promises.push(installedChunkData[2] = promise); +/******/ +/******/ // start chunk loading +/******/ const url = __webpack_require__.p + __webpack_require__.u(chunkId); +/******/ // create error before stack unwound to get useful stacktrace later +/******/ const error = new Error(); +/******/ const loadingEnded = (event) => { +/******/ if(__webpack_require__.o(installedChunks, chunkId)) { +/******/ installedChunkData = installedChunks[chunkId]; +/******/ if(installedChunkData !== 0) installedChunks[chunkId] = undefined; +/******/ if(installedChunkData) { +/******/ const errorType = event && (event.type === 'load' ? 'missing' : event.type); +/******/ const realSrc = event && event.target && event.target.src; +/******/ error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'; +/******/ error.name = 'ChunkLoadError'; +/******/ error.type = errorType; +/******/ error.request = realSrc; +/******/ error.event = event; +/******/ installedChunkData[1](error); +/******/ } +/******/ } +/******/ }; +/******/ __webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId); +/******/ } else installedChunks[chunkId] = 0; +/******/ } +/******/ } +/******/ }; +/******/ +/******/ // no prefetching +/******/ +/******/ // no preloaded +/******/ +/******/ // no HMR +/******/ +/******/ // no HMR manifest +/******/ +/******/ // no on chunks loaded +/******/ +/******/ // install a JSONP callback for chunk loading +/******/ const webpackJsonpCallback = (parentChunkLoadingFunction, data) => { +/******/ let [chunkIds, moreModules, runtime] = data; +/******/ // add "moreModules" to the modules object, +/******/ // then flag all "chunkIds" as loaded and fire callback +/******/ var moduleId, chunkId, i = 0; +/******/ if(chunkIds.some((id) => (installedChunks[id] !== 0))) { +/******/ for(moduleId in moreModules) { +/******/ if(__webpack_require__.o(moreModules, moduleId)) { +/******/ __webpack_require__.m[moduleId] = moreModules[moduleId]; +/******/ } +/******/ } +/******/ if(runtime) var result = runtime(__webpack_require__); +/******/ } +/******/ if(parentChunkLoadingFunction) parentChunkLoadingFunction(data); +/******/ for(;i < chunkIds.length; i++) { +/******/ chunkId = chunkIds[i]; +/******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) { +/******/ installedChunks[chunkId][0](); +/******/ } +/******/ installedChunks[chunkId] = 0; +/******/ } +/******/ +/******/ } +/******/ +/******/ const chunkLoadingGlobal = self["webpackChunkmodule_federation_ccc"] = self["webpackChunkmodule_federation_ccc"] || []; +/******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0)); +/******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal)); +/******/ })(); +/******/ +/************************************************************************/ +``` + +
+ +``` js +/******/ +/******/ // module cache are used so entry inlining is disabled +/******/ // startup +/******/ // Load entry module and return exports +/******/ let __webpack_exports__ = __webpack_require__(0); +/******/ mfeCCC = __webpack_exports__; +/******/ +/******/ })() +; +``` + +# Info + +## Unoptimized + +``` +app: + asset src_bootstrap_js.js 46 KiB [emitted] + asset app.js 30.6 KiB [emitted] (name: app) + asset vendors-node_modules_react_index_js.js 28.4 KiB [emitted] (id hint: vendors) + chunk (runtime: app) app.js (app) 672 bytes (javascript) 42 bytes (share-init) 19.5 KiB (runtime) [entry] [rendered] + > ./src/index.js app + runtime modules 19.5 KiB 13 modules + built modules 672 bytes (javascript) 42 bytes (share-init) [built] + ./src/index.js 588 bytes [built] [code generated] + external "mfeBBB@/dist/bbb/mfeBBB.js" 42 bytes [built] [code generated] + external "mfeCCC@/dist/ccc/mfeCCC.js" 42 bytes [built] [code generated] + provide shared module (default) react@19.2.8 = ../../node_modules/react/index.js 42 bytes [built] [code generated] + chunk (runtime: app) src_bootstrap_js.js 27.1 KiB (javascript) 84 bytes (consume-shared) 12 bytes (remote) 12 bytes (share-init) [rendered] + > ./bootstrap ./src/index.js 10:0-21 + dependent modules 26.7 KiB (javascript) 84 bytes (consume-shared) 12 bytes (remote) 12 bytes (share-init) [dependent] 19 modules + ./src/bootstrap.js 440 bytes [built] [code generated] + chunk (runtime: app) vendors-node_modules_react_index_js.js (id hint: vendors) 17 KiB [rendered] reused as split chunk (cache group: defaultVendors) + > provide shared module (default) react@19.2.8 = ../../node_modules/react/index.js + > consume shared module (default) react@^19.2.8 (singleton) (fallback: ../../node_modules/react/index.js) + > consume shared module (default) react@^19.2.7 (singleton) (fallback: ../../node_modules/react/index.js) + dependent modules 16.8 KiB [dependent] 1 module + ../../node_modules/react/index.js 186 bytes [built] [code generated] + chunk (runtime: app) 6 bytes (remote) 6 bytes (share-init) + > mfe-c/Component2 ./src/App.js 8:49-75 + remote mfe-c/Component2 6 bytes (remote) 6 bytes (share-init) [built] [code generated] + app (webpack X.X.X) compiled successfully + +mfe-b: + assets by chunk 1 MiB (id hint: vendors) + asset vendors-node_modules_date-fns_index_js.js 997 KiB [emitted] (id hint: vendors) + asset vendors-node_modules_react_index_js.js 28.4 KiB [emitted] (id hint: vendors) + asset mfeBBB.js 24.6 KiB [emitted] (name: mfeBBB) + asset src-b_Component_js.js 4.02 KiB [emitted] + chunk (runtime: mfeBBB) mfeBBB.js (mfeBBB) 42 bytes (javascript) 84 bytes (share-init) 16.4 KiB (runtime) [entry] [rendered] + > mfeBBB + runtime modules 16.4 KiB 10 modules + built modules 42 bytes (javascript) 84 bytes (share-init) [built] + container entry 42 bytes [built] [code generated] + provide shared module (default) date-fns@4.4.0 = ../../node_modules/dat...(truncated) 42 bytes [built] [code generated] + provide shared module (default) react@19.2.8 = ../../node_modules/react/index.js 42 bytes [built] [code generated] + chunk (runtime: mfeBBB) src-b_Component_js.js 1.44 KiB (javascript) 84 bytes (consume-shared) [rendered] + > ./src-b/Component container entry ./Component + dependent modules 605 bytes (javascript) 84 bytes (consume-shared) [dependent] 4 modules + ./src-b/Component.js 869 bytes [built] [code generated] + chunk (runtime: mfeBBB) vendors-node_modules_date-fns_index_js.js (id hint: vendors) 523 KiB [rendered] reused as split chunk (cache group: defaultVendors) + > provide shared module (default) date-fns@4.4.0 = ../../node_modules/date-fns/index.js + > consume shared module (default) date-fns@^4.4.0 (strict) (fallback: ../../node_modules/date-fns/index.js) + dependent modules 515 KiB [dependent] 302 modules + ../../node_modules/date-fns/index.js 8.68 KiB [built] [code generated] + chunk (runtime: mfeBBB) vendors-node_modules_react_index_js.js (id hint: vendors) 17 KiB [rendered] reused as split chunk (cache group: defaultVendors) + > provide shared module (default) react@19.2.8 = ../../node_modules/react/index.js + > consume shared module (default) react@^19.2.7 (singleton) (fallback: ../../node_modules/react/index.js) + dependent modules 16.8 KiB [dependent] 1 module + ../../node_modules/react/index.js 186 bytes [built] [code generated] + mfe-b (webpack X.X.X) compiled successfully + +mfe-c: + assets by chunk 1020 KiB (id hint: vendors) + asset vendors-node_modules_date-fns_index_js.js 997 KiB [emitted] (id hint: vendors) + asset vendors-node_modules_lodash_random_js.js 24.8 KiB [emitted] (id hint: vendors) + asset mfeCCC.js 26 KiB [emitted] (name: mfeCCC) + asset src-c_LazyComponent_js.js 4.09 KiB [emitted] + asset src-c_Component_js.js 3.9 KiB [emitted] + chunk (runtime: mfeCCC) mfeCCC.js (mfeCCC) 42 bytes (javascript) 84 bytes (share-init) 17.1 KiB (runtime) [entry] [rendered] + > mfeCCC + runtime modules 17.1 KiB 12 modules + built modules 42 bytes (javascript) 84 bytes (share-init) [built] + container entry 42 bytes [built] [code generated] + provide shared module (default) date-fns@4.4.0 = ../../node_modules/dat...(truncated) 42 bytes [built] [code generated] + provide shared module (default) lodash/random@4.18.1 = ../../node_modules/lo...(truncated) 42 bytes [built] [code generated] + chunk (runtime: mfeCCC) src-c_Component_js.js 1.16 KiB (javascript) 42 bytes (consume-shared) [rendered] + > ./src-c/Component container entry ./Component + dependent modules 605 bytes (javascript) 42 bytes (consume-shared) [dependent] 3 modules + ./src-c/Component.js 585 bytes [built] [code generated] + chunk (runtime: mfeCCC) src-c_LazyComponent_js.js 1.21 KiB (javascript) 42 bytes (consume-shared) [rendered] + > ./src-c/LazyComponent container entry ./Component2 + dependent modules 605 bytes (javascript) 42 bytes (consume-shared) [dependent] 3 modules + ./src-c/LazyComponent.js 631 bytes [built] [code generated] + chunk (runtime: mfeCCC) vendors-node_modules_date-fns_index_js.js (id hint: vendors) 523 KiB [rendered] reused as split chunk (cache group: defaultVendors) + > provide shared module (default) date-fns@4.4.0 = ../../node_modules/date-fns/index.js + > consume shared module (default) date-fns@^4.4.0 (strict) (fallback: ../../node_modules/date-fns/index.js) + dependent modules 515 KiB [dependent] 302 modules + ../../node_modules/date-fns/index.js 8.68 KiB [built] [code generated] + chunk (runtime: mfeCCC) vendors-node_modules_lodash_random_js.js (id hint: vendors) 16.3 KiB [rendered] reused as split chunk (cache group: defaultVendors) + > provide shared module (default) lodash/random@4.18.1 = ../../node_modules/lodash/random.js + > consume shared module (default) lodash/random@^4.17.19 (strict) (fallback: ../../node_modules/lodash/random.js) + dependent modules 13.7 KiB [dependent] 20 modules + ../../node_modules/lodash/random.js 2.56 KiB [built] [code generated] + chunk (runtime: mfeCCC) 42 bytes split chunk (cache group: default) + > ./src-c/Component container entry ./Component + > ./src-c/LazyComponent container entry ./Component2 + consume shared module (default) react@^19.2.7 (singleton) 42 bytes [built] [code generated] + mfe-c (webpack X.X.X) compiled successfully +``` + +## Production mode + +``` +app: + asset src_bootstrap_js.js 12.8 KiB [emitted] [minimized] 1 related asset + asset app.js 8.05 KiB [emitted] [minimized] (name: app) + asset node_modules_react_index_js.js 7.45 KiB [emitted] [minimized] 1 related asset + chunk (runtime: app) app.js (app) 672 bytes (javascript) 42 bytes (share-init) 19.9 KiB (runtime) [entry] [rendered] + > ./src/index.js app + runtime modules 19.9 KiB 14 modules + built modules 672 bytes (javascript) 42 bytes (share-init) [built] + ./src/index.js 588 bytes [built] [code generated] + external "mfeBBB@/dist/bbb/mfeBBB.js" 42 bytes [built] [code generated] + external "mfeCCC@/dist/ccc/mfeCCC.js" 42 bytes [built] [code generated] + provide shared module (default) react@19.2.8 = ../../node_modules/react/index.js 42 bytes [built] [code generated] + chunk (runtime: app) node_modules_react_index_js.js 17 KiB [rendered] + > provide shared module (default) react@19.2.8 = ../../node_modules/react/index.js + > consume shared module (default) react@^19.2.7 (singleton) (fallback: ../../node_modules/react/index.js) + > consume shared module (default) react@^19.2.8 (singleton) (fallback: ../../node_modules/react/index.js) + dependent modules 16.8 KiB [dependent] 1 module + ../../node_modules/react/index.js 186 bytes [built] [code generated] + chunk (runtime: app) src_bootstrap_js.js 27.1 KiB (javascript) 84 bytes (consume-shared) 12 bytes (remote) 12 bytes (share-init) [rendered] + > ./bootstrap ./src/index.js 10:0-21 + dependent modules 84 bytes (consume-shared) 12 bytes (remote) 12 bytes (share-init) [dependent] 4 modules + ./src/bootstrap.js + 15 modules 27.1 KiB [built] [code generated] + chunk (runtime: app) 6 bytes (remote) 6 bytes (share-init) + > mfe-c/Component2 ./src/App.js 8:49-75 + remote mfe-c/Component2 6 bytes (remote) 6 bytes (share-init) [built] [code generated] + app (webpack X.X.X) compiled successfully + +mfe-b: + asset vendors-node_modules_date-fns_index_js.js 70.3 KiB [emitted] [minimized] (id hint: vendors) + asset node_modules_react_index_js.js 7.45 KiB [emitted] [minimized] 1 related asset + asset mfeBBB.js 6.56 KiB [emitted] [minimized] (name: mfeBBB) + asset src-b_Component_js.js 632 bytes [emitted] [minimized] + chunk (runtime: mfeBBB) mfeBBB.js (mfeBBB) 42 bytes (javascript) 84 bytes (share-init) 16.7 KiB (runtime) [entry] [rendered] + > mfeBBB + runtime modules 16.7 KiB 11 modules + built modules 42 bytes (javascript) 84 bytes (share-init) [built] + container entry 42 bytes [built] [code generated] + provide shared module (default) date-fns@4.4.0 = ../../node_modules/dat...(truncated) 42 bytes [built] [code generated] + provide shared module (default) react@19.2.8 = ../../node_modules/react/index.js 42 bytes [built] [code generated] + chunk (runtime: mfeBBB) node_modules_react_index_js.js 17 KiB [rendered] + > provide shared module (default) react@19.2.8 = ../../node_modules/react/index.js + > consume shared module (default) react@^19.2.7 (singleton) (fallback: ../../node_modules/react/index.js) + dependent modules 16.8 KiB [dependent] 1 module + ../../node_modules/react/index.js 186 bytes [built] [code generated] + chunk (runtime: mfeBBB) src-b_Component_js.js 1.44 KiB (javascript) 84 bytes (consume-shared) [rendered] + > ./src-b/Component container entry ./Component + dependent modules 84 bytes [dependent] 2 modules + ./src-b/Component.js + 2 modules 1.44 KiB [built] [code generated] + chunk (runtime: mfeBBB) vendors-node_modules_date-fns_index_js.js (id hint: vendors) 523 KiB [rendered] reused as split chunk (cache group: defaultVendors) + > consume shared module (default) date-fns@^4.4.0 (strict) (fallback: ../../node_modules/date-fns/index.js) + > provide shared module (default) date-fns@4.4.0 = ../../node_modules/date-fns/index.js + ../../node_modules/date-fns/index.js + 302 modules 523 KiB [built] [code generated] + mfe-b (webpack X.X.X) compiled successfully + +mfe-c: + asset vendors-node_modules_date-fns_index_js.js 70.3 KiB [emitted] [minimized] (id hint: vendors) + asset mfeCCC.js 7.06 KiB [emitted] [minimized] (name: mfeCCC) + asset node_modules_lodash_random_js.js 3.08 KiB [emitted] [minimized] + asset src-c_LazyComponent_js.js 651 bytes [emitted] [minimized] + asset src-c_Component_js.js 595 bytes [emitted] [minimized] + chunk (runtime: mfeCCC) mfeCCC.js (mfeCCC) 42 bytes (javascript) 84 bytes (share-init) 17 KiB (runtime) [entry] [rendered] + > mfeCCC + runtime modules 17 KiB 12 modules + built modules 42 bytes (javascript) 84 bytes (share-init) [built] + container entry 42 bytes [built] [code generated] + provide shared module (default) date-fns@4.4.0 = ../../node_modules/dat...(truncated) 42 bytes [built] [code generated] + provide shared module (default) lodash/random@4.18.1 = ../../node_modules/lo...(truncated) 42 bytes [built] [code generated] + chunk (runtime: mfeCCC) node_modules_lodash_random_js.js 16.3 KiB [rendered] + > provide shared module (default) lodash/random@4.18.1 = ../../node_modules/lodash/random.js + > consume shared module (default) lodash/random@^4.17.19 (strict) (fallback: ../../node_modules/lodash/random.js) + dependent modules 13.7 KiB [dependent] 20 modules + ../../node_modules/lodash/random.js 2.56 KiB [built] [code generated] + chunk (runtime: mfeCCC) src-c_Component_js.js 1.16 KiB (javascript) 42 bytes (consume-shared) [rendered] + > ./src-c/Component container entry ./Component + dependent modules 605 bytes (javascript) 42 bytes (consume-shared) [dependent] 3 modules + ./src-c/Component.js 585 bytes [built] [code generated] + chunk (runtime: mfeCCC) src-c_LazyComponent_js.js 1.21 KiB (javascript) 42 bytes (consume-shared) [rendered] + > ./src-c/LazyComponent container entry ./Component2 + dependent modules 605 bytes (javascript) 42 bytes (consume-shared) [dependent] 3 modules + ./src-c/LazyComponent.js 631 bytes [built] [code generated] + chunk (runtime: mfeCCC) vendors-node_modules_date-fns_index_js.js (id hint: vendors) 523 KiB [rendered] reused as split chunk (cache group: defaultVendors) + > consume shared module (default) date-fns@^4.4.0 (strict) (fallback: ../../node_modules/date-fns/index.js) + > provide shared module (default) date-fns@4.4.0 = ../../node_modules/date-fns/index.js + ../../node_modules/date-fns/index.js + 302 modules 523 KiB [built] [code generated] + chunk (runtime: mfeCCC) 42 bytes split chunk (cache group: default) + > ./src-c/Component container entry ./Component + > ./src-c/LazyComponent container entry ./Component2 + consume shared module (default) react@^19.2.7 (singleton) 42 bytes [built] [code generated] + mfe-c (webpack X.X.X) compiled successfully +``` diff --git a/examples/module-federation/build.js b/examples/module-federation/build.js new file mode 100644 index 00000000000..2eab80c3400 --- /dev/null +++ b/examples/module-federation/build.js @@ -0,0 +1,5 @@ +global.NO_TARGET_ARGS = true; +global.NO_REASONS = true; +global.NO_STATS_OPTIONS = true; +global.NO_PUBLIC_PATH = true; +require("../build-common"); diff --git a/examples/module-federation/index.html b/examples/module-federation/index.html new file mode 100644 index 00000000000..f5a0a1ec244 --- /dev/null +++ b/examples/module-federation/index.html @@ -0,0 +1,83 @@ + + + + + + +
+ + + + + + + + + + + + + + + diff --git a/examples/module-federation/src-b/Component.js b/examples/module-federation/src-b/Component.js new file mode 100644 index 00000000000..4225568f1e2 --- /dev/null +++ b/examples/module-federation/src-b/Component.js @@ -0,0 +1,18 @@ +import React from "react"; +import { formatRelative, subDays } from "date-fns"; +// date-fns is a shared module, but used as usual +// exposing modules act as async boundary, +// so no additional async boundary need to be added here +// As data-fns is an shared module, it will be placed in a separate file +// It will be loaded in parallel to the code of this module + +const Component = ({ locale }) => ( +
+

I'm a Component exposed from container B!

+

+ Using date-fn in Remote:{" "} + {formatRelative(subDays(new Date(), 2), new Date(), { locale })} +

+
+); +export default Component; diff --git a/examples/module-federation/src-c/Component.js b/examples/module-federation/src-c/Component.js new file mode 100644 index 00000000000..dba8151efb8 --- /dev/null +++ b/examples/module-federation/src-c/Component.js @@ -0,0 +1,13 @@ +import React from "react"; +import { formatRelative, subDays } from "date-fns"; + +const Component = ({ locale }) => ( +
+

I'm a Component exposed from container C!

+

+ Using date-fn in Remote:{" "} + {formatRelative(subDays(new Date(), 3), new Date(), { locale })} +

+
+); +export default Component; diff --git a/examples/module-federation/src-c/LazyComponent.js b/examples/module-federation/src-c/LazyComponent.js new file mode 100644 index 00000000000..22dea24a471 --- /dev/null +++ b/examples/module-federation/src-c/LazyComponent.js @@ -0,0 +1,11 @@ +import React from "react"; +import random from "lodash/random"; + +const Component = () => ( +
+

I'm a lazy Component exposed from container C!

+

I'm lazy loaded by the app and lazy load another component myself.

+

Using lodash in Remote: {random(0, 6)}

+
+); +export default Component; diff --git a/examples/module-federation/src/App.js b/examples/module-federation/src/App.js new file mode 100644 index 00000000000..b58a5c19650 --- /dev/null +++ b/examples/module-federation/src/App.js @@ -0,0 +1,26 @@ +import React from "react"; +import ComponentB from "mfe-b/Component"; // <- these are remote modules, +import ComponentC from "mfe-c/Component"; // <- but they are used as usual packages +import { de } from "date-fns/locale"; + +// remote modules can also be used with import() which lazy loads them as usual +const ComponentD = React.lazy(() => import("mfe-c/Component2")); + +const App = () => ( +
+
+

Hello World

+
+

This component is from a remote container:

+ +

And this component is from another remote container:

+ + Lazy loading component...

}> +

+ And this component is from this remote container too, but lazy loaded: +

+ +
+
+); +export default App; diff --git a/examples/module-federation/src/bootstrap.js b/examples/module-federation/src/bootstrap.js new file mode 100644 index 00000000000..afb68467aba --- /dev/null +++ b/examples/module-federation/src/bootstrap.js @@ -0,0 +1,11 @@ +import ReactDom from "react-dom"; +import React from "react"; // <- this is a shared module, but used as usual +import App from "./App"; + +// load app +const el = document.createElement("main"); +ReactDom.render(, el); +document.body.appendChild(el); + +// remove spinner +document.body.removeChild(document.getElementsByClassName("spinner")[0]); diff --git a/examples/module-federation/src/index.js b/examples/module-federation/src/index.js new file mode 100644 index 00000000000..5e42922531e --- /dev/null +++ b/examples/module-federation/src/index.js @@ -0,0 +1,13 @@ +// Sharing modules requires that all remotes are initialized +// and can provide shared modules to the common scope +// As this is an async operation we need an async boundary (import()) + +// Using modules from remotes is also an async operation +// as chunks need to be loaded for the code of the remote module +// This also requires an async boundary (import()) + +// At this point shared modules initialized and remote modules are loaded +import("./bootstrap"); + +// It's possible to place more code here to do stuff on page init +// but it can't use any of the shared modules or remote modules. diff --git a/examples/module-federation/template.md b/examples/module-federation/template.md new file mode 100644 index 00000000000..abb4767fccb --- /dev/null +++ b/examples/module-federation/template.md @@ -0,0 +1,67 @@ +# webpack.config.js + +```javascript +_{{webpack.config.js}}_ +``` + +# src/index.js + +```javascript +_{{src/index.js}}_ +``` + +# src/bootstrap.js + +```jsx +_{{src/bootstrap.js}}_ +``` + +# src/App.js + +```jsx +_{{src/App.js}}_ +``` + +# index.html + +```html +_{{index.html}}_ +``` + +# src-b/Component.js + +```jsx +_{{src-b/Component.js}}_ +``` + +# dist/aaa/app.js + +```javascript +_{{dist/aaa/app.js}}_ +``` + +# dist/bbb/mfeBBB.js + +```javascript +_{{dist/bbb/mfeBBB.js}}_ +``` + +# dist/ccc/mfeCCC.js + +```javascript +_{{dist/ccc/mfeCCC.js}}_ +``` + +# Info + +## Unoptimized + +``` +_{{stdout}}_ +``` + +## Production mode + +``` +_{{production:stdout}}_ +``` diff --git a/examples/module-federation/test.filter.js b/examples/module-federation/test.filter.js new file mode 100644 index 00000000000..45a8113efb3 --- /dev/null +++ b/examples/module-federation/test.filter.js @@ -0,0 +1,9 @@ +"use strict"; + +// These cases load ESM-only @babel/core@8, which needs Node ^22.18 || >=24.11 +// (subpath imports) and can't load under Jest's vm below Node 24.9 or on Deno/Bun. +module.exports = () => { + if ("deno" in process.versions || "bun" in process.versions) return false; + const [major, minor] = process.versions.node.split(".").map(Number); + return major > 24 || (major === 24 && minor >= 9); +}; diff --git a/examples/module-federation/webpack.config.js b/examples/module-federation/webpack.config.js new file mode 100644 index 00000000000..3fc1189a542 --- /dev/null +++ b/examples/module-federation/webpack.config.js @@ -0,0 +1,159 @@ +"use strict"; + +const path = require("path"); +const { ModuleFederationPlugin } = require("../../").container; + +const rules = [ + { + test: /\.js$/, + include: path.resolve(__dirname, "src"), + use: { + loader: "babel-loader", + options: { + presets: ["@babel/react"] + } + } + } +]; + +/** @type {import("webpack").Configuration["optimization"]} */ +const optimization = { + chunkIds: "named", // for this example only: readable filenames in production too + nodeEnv: "production" // for this example only: always production version of react +}; +const stats = { + chunks: true, + modules: false, + chunkModules: true, + chunkOrigins: true +}; + +/** @type {(env: "development" | "production") => import("webpack").Configuration[]} */ +const config = (env = "development") => [ + // For this example we have 3 configs in a single file + // In practice you probably would have separate config + // maybe even separate repos for each build. + // For Module Federation there is not compile-time dependency + // between the builds. + // Each one can have different config options. + { + name: "app", + mode: env, + entry: { + app: "./src/index.js" + }, + output: { + filename: "[name].js", + path: path.resolve(__dirname, "dist/aaa"), + publicPath: "dist/aaa/", + + // Each build needs a unique name + // to avoid runtime collisions + // The default uses "name" from package.json + uniqueName: "module-federation-aaa" + }, + module: { rules }, + optimization, + plugins: [ + new ModuleFederationPlugin({ + // List of remotes with URLs + remotes: { + "mfe-b": "mfeBBB@/dist/bbb/mfeBBB.js", + "mfe-c": "mfeCCC@/dist/ccc/mfeCCC.js" + }, + + // list of shared modules with optional options + shared: { + // specifying a module request as shared module + // will provide all used modules matching this name (version from package.json) + // and consume shared modules in the version specified in dependencies from package.json + // (or in dev/peer/optionalDependencies) + // So it use the highest available version of this package matching the version requirement + // from package.json, while providing it's own version to others. + react: { + singleton: true // make sure only a single react module is used + } + } + }) + ], + stats + }, + { + name: "mfe-b", + mode: env, + entry: {}, + output: { + filename: "[name].js", + path: path.resolve(__dirname, "dist/bbb"), + publicPath: "dist/bbb/", + uniqueName: "module-federation-bbb" + }, + module: { rules }, + optimization, + plugins: [ + new ModuleFederationPlugin({ + // A unique name + name: "mfeBBB", + + // List of exposed modules + exposes: { + "./Component": "./src-b/Component" + }, + + // list of shared modules + shared: [ + // date-fns is shared with the other remote, app doesn't know about that + "date-fns", + { + react: { + singleton: true // must be specified in each config + } + } + ] + }) + ], + stats + }, + { + name: "mfe-c", + mode: env, + entry: {}, + output: { + filename: "[name].js", + path: path.resolve(__dirname, "dist/ccc"), + publicPath: "dist/ccc/", + uniqueName: "module-federation-ccc" + }, + module: { rules }, + optimization, + plugins: [ + new ModuleFederationPlugin({ + name: "mfeCCC", + + exposes: { + "./Component": "./src-c/Component", + "./Component2": "./src-c/LazyComponent" + }, + + shared: [ + // All (used) requests within lodash are shared. + "lodash/", + "date-fns", + { + react: { + // Do not load our own version. + // There must be a valid shared module available at runtime. + // This improves build time as this module doesn't need to be compiled, + // but it opts-out of possible fallbacks and runtime version upgrade. + import: false, + singleton: true + } + } + ] + }) + ], + stats + } +]; + +module.exports = config; diff --git a/examples/module-library/README.md b/examples/module-library/README.md new file mode 100644 index 00000000000..dead320fa91 --- /dev/null +++ b/examples/module-library/README.md @@ -0,0 +1,161 @@ +# example.js + +```javascript +export * from "./counter"; +export * from "./methods"; +``` + +# methods.js + +```javascript +export { reset as resetCounter } from "./counter"; + +export const print = value => console.log(value); +``` + +# counter.js + +```javascript +export let value = 0; +export function increment() { + value++; +} +export function decrement() { + value--; +} +export function reset() { + value = 0; +} +``` + +# dist/output.js + +```javascript +/******/ // The require scope +/******/ const __webpack_require__ = {}; +/******/ +``` + +
/* webpack runtime code */ + +``` js +/************************************************************************/ +/******/ /* webpack/runtime/define property getters */ +/******/ // define getter/value functions for harmony exports +/******/ __webpack_require__.d = (exports, definition) => { +/******/ if(Array.isArray(definition)) { +/******/ var i = 0; +/******/ while(i < definition.length) { +/******/ var key = definition[i++]; +/******/ var binding = definition[i++]; +/******/ if(!__webpack_require__.o(exports, key)) { +/******/ if(binding === 0) { +/******/ Object.defineProperty(exports, key, { enumerable: true, value: definition[i++] }); +/******/ } else { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: binding }); +/******/ } +/******/ } else if(binding === 0) { i++; } +/******/ } +/******/ } else { +/******/ for(var key in definition) { +/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); +/******/ } +/******/ } +/******/ } +/******/ }; +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)); +/******/ +/******/ /* webpack/runtime/make namespace object */ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = (exports) => { +/******/ if(Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/************************************************************************/ +``` + +
+ +``` js +let __webpack_exports__ = {}; +/*!********************************!*\ + !*** ./example.js + 2 modules ***! + \********************************/ +/*! namespace exports */ +/*! export decrement [provided] [used in main] [missing usage info prevents renaming] -> ./counter.js .decrement */ +/*! export increment [provided] [used in main] [missing usage info prevents renaming] -> ./counter.js .increment */ +/*! export print [provided] [used in main] [missing usage info prevents renaming] -> ./methods.js .print */ +/*! export reset [provided] [used in main] [missing usage info prevents renaming] -> ./counter.js .reset */ +/*! export resetCounter [provided] [used in main] [missing usage info prevents renaming] -> ./counter.js .reset */ +/*! export value [provided] [used in main] [missing usage info prevents renaming] -> ./counter.js .value */ +/*! other exports [not provided] [no usage info] */ +/*! runtime requirements: __webpack_exports__, __webpack_require__.d, __webpack_require__.r, __webpack_require__.* */ +// ESM COMPAT FLAG +__webpack_require__.r(__webpack_exports__); + +;// ./counter.js +let value = 0; +function increment() { + value++; +} +function decrement() { + value--; +} +function counter_reset() { + value = 0; +} + +;// ./methods.js + + +const print = value => console.log(value); + +;// ./example.js + + + +export { decrement, increment, print, counter_reset as reset, counter_reset as resetCounter, value }; +``` + +# dist/output.js (production) + +```javascript +let n=0;function o(){n++}function t(){n--}function e(){n=0}const s=n=>console.log(n);export{t as decrement,o as increment,s as print,e as reset,e as resetCounter,n as value}; +``` + +# Info + +## Unoptimized + +``` +asset output.js 3.13 KiB [emitted] [javascript module] (name: main) +chunk (runtime: main) output.js (main) 302 bytes (javascript) 1.08 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 1.08 KiB 3 modules + ./example.js + 2 modules 302 bytes [built] [code generated] + [exports: decrement, increment, print, reset, resetCounter, value] + [used exports unknown] + entry ./example.js main + used as library export +webpack X.X.X compiled successfully +``` + +## Production mode + +``` +asset output.js 174 bytes [emitted] [javascript module] [minimized] (name: main) +chunk (runtime: main) output.js (main) 302 bytes [entry] [rendered] + > ./example.js main + ./example.js + 2 modules 302 bytes [built] [code generated] + [exports: decrement, increment, print, reset, resetCounter, value] + [all exports used] + entry ./example.js main + used as library export +webpack X.X.X compiled successfully +``` diff --git a/examples/module-library/build.js b/examples/module-library/build.js new file mode 100644 index 00000000000..41c29c9d169 --- /dev/null +++ b/examples/module-library/build.js @@ -0,0 +1 @@ +require("../build-common"); \ No newline at end of file diff --git a/examples/module-library/counter.js b/examples/module-library/counter.js new file mode 100644 index 00000000000..7009896e282 --- /dev/null +++ b/examples/module-library/counter.js @@ -0,0 +1,10 @@ +export let value = 0; +export function increment() { + value++; +} +export function decrement() { + value--; +} +export function reset() { + value = 0; +} diff --git a/examples/module-library/example.js b/examples/module-library/example.js new file mode 100644 index 00000000000..ef58a21ffa1 --- /dev/null +++ b/examples/module-library/example.js @@ -0,0 +1,2 @@ +export * from "./counter"; +export * from "./methods"; diff --git a/examples/module-library/methods.js b/examples/module-library/methods.js new file mode 100644 index 00000000000..4be8f10f704 --- /dev/null +++ b/examples/module-library/methods.js @@ -0,0 +1,3 @@ +export { reset as resetCounter } from "./counter"; + +export const print = value => console.log(value); diff --git a/examples/module-library/template.md b/examples/module-library/template.md new file mode 100644 index 00000000000..98d06e62ec9 --- /dev/null +++ b/examples/module-library/template.md @@ -0,0 +1,43 @@ +# example.js + +```javascript +_{{example.js}}_ +``` + +# methods.js + +```javascript +_{{methods.js}}_ +``` + +# counter.js + +```javascript +_{{counter.js}}_ +``` + +# dist/output.js + +```javascript +_{{dist/output.js}}_ +``` + +# dist/output.js (production) + +```javascript +_{{production:dist/output.js}}_ +``` + +# Info + +## Unoptimized + +``` +_{{stdout}}_ +``` + +## Production mode + +``` +_{{production:stdout}}_ +``` diff --git a/examples/module-library/webpack.config.js b/examples/module-library/webpack.config.js new file mode 100644 index 00000000000..f73e384bb6a --- /dev/null +++ b/examples/module-library/webpack.config.js @@ -0,0 +1,19 @@ +"use strict"; + +/** @type {import("webpack").Configuration} */ +const config = { + output: { + module: true, + library: { + type: "module" + } + }, + optimization: { + concatenateModules: true + }, + experiments: { + outputModule: true + } +}; + +module.exports = config; diff --git a/examples/module-worker/README.md b/examples/module-worker/README.md new file mode 100644 index 00000000000..5f857284a0c --- /dev/null +++ b/examples/module-worker/README.md @@ -0,0 +1,873 @@ +# example.js + +```javascript +document.body.innerHTML = ` +

+	
+ + +
+

Computing fibonacci without worker:

+ +

+	

Computing fibonacci with worker:

+ +

+`;
+
+const history = document.getElementById("history");
+const message = document.getElementById("message");
+const send = document.getElementById("send");
+const fib1 = document.getElementById("fib1");
+const output1 = document.getElementById("output1");
+const fib2 = document.getElementById("fib2");
+const output2 = document.getElementById("output2");
+
+/// CHAT with shared worker ///
+
+const chatWorker = new SharedWorker(
+	new URL("./chat-worker.js", import.meta.url),
+	{
+		name: "chat",
+		type: "module"
+	}
+);
+
+let historyTimeout;
+const scheduleUpdateHistory = () => {
+	clearTimeout(historyTimeout);
+	historyTimeout = setTimeout(() => {
+		chatWorker.port.postMessage({ type: "history" });
+	}, 1000);
+};
+scheduleUpdateHistory();
+
+const from = `User ${Math.floor(Math.random() * 10000)}`;
+
+send.addEventListener("click", e => {
+	chatWorker.port.postMessage({
+		type: "message",
+		content: message.value,
+		from
+	});
+	message.value = "";
+	message.focus();
+	e.preventDefault();
+});
+
+chatWorker.port.onmessage = event => {
+	const msg = event.data;
+	switch (msg.type) {
+		case "history":
+			history.innerText = msg.history.join("\n");
+			scheduleUpdateHistory();
+			break;
+	}
+};
+
+/// FIBONACCI without worker ///
+
+fib1.addEventListener("change", async () => {
+	try {
+		const value = parseInt(fib1.value, 10);
+		const { fibonacci } = await import("./fibonacci");
+		const result = fibonacci(value);
+		output1.innerText = `fib(${value}) = ${result}`;
+	} catch (e) {
+		output1.innerText = e.message;
+	}
+});
+
+/// FIBONACCI with worker ///
+
+const fibWorker = new Worker(new URL("./fib-worker.js", import.meta.url), {
+	name: "fibonacci",
+	type: "module"
+	/* webpackEntryOptions: { filename: "workers/[name].js" } */
+});
+
+fib2.addEventListener("change", () => {
+	try {
+		const value = parseInt(fib2.value, 10);
+		fibWorker.postMessage(`${value}`);
+	} catch (e) {
+		output2.innerText = e.message;
+	}
+});
+
+fibWorker.onmessage = event => {
+	output2.innerText = event.data;
+};
+```
+
+# fib-worker.js
+
+```javascript
+onmessage = async event => {
+	const { fibonacci } = await import("./fibonacci");
+	const value = JSON.parse(event.data);
+	postMessage(`fib(${value}) = ${fibonacci(value)}`);
+};
+```
+
+# fibonacci.js
+
+```javascript
+export function fibonacci(n) {
+	return n < 1 ? 0 : n <= 2 ? 1 : fibonacci(n - 1) + fibonacci(n - 2);
+}
+```
+
+# chat-worker.js
+
+```javascript
+onconnect = function (e) {
+	for (const port of e.ports) {
+		port.onmessage = async event => {
+			const msg = event.data;
+			switch (msg.type) {
+				case "message":
+					const { add } = await import("./chat-module");
+					add(msg.content, msg.from);
+				// fallthrough
+				case "history":
+					const { history } = await import("./chat-module");
+					port.postMessage({
+						type: "history",
+						history
+					});
+					break;
+			}
+		};
+	}
+};
+```
+
+# chat-module.js
+
+```javascript
+export const history = [];
+
+export const add = (content, from) => {
+	if (history.length > 10) history.shift();
+	history.push(`${from}: ${content}`);
+};
+```
+
+# dist/main.js
+
+```javascript
+/******/ var __webpack_modules__ = ({});
+```
+
+
/* webpack runtime code */ + +``` js +/************************************************************************/ +/******/ // The module cache +/******/ const __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ const 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; +/******/ } +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = __webpack_modules__; +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/define property getters */ +/******/ // define getter/value functions for harmony exports +/******/ __webpack_require__.d = (exports, definition) => { +/******/ if(Array.isArray(definition)) { +/******/ var i = 0; +/******/ while(i < definition.length) { +/******/ var key = definition[i++]; +/******/ var binding = definition[i++]; +/******/ if(!__webpack_require__.o(exports, key)) { +/******/ if(binding === 0) { +/******/ Object.defineProperty(exports, key, { enumerable: true, value: definition[i++] }); +/******/ } else { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: binding }); +/******/ } +/******/ } else if(binding === 0) { i++; } +/******/ } +/******/ } else { +/******/ for(var key in definition) { +/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); +/******/ } +/******/ } +/******/ } +/******/ }; +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ __webpack_require__.o = (obj, prop) => (Object.hasOwn(obj, prop)); +/******/ +/******/ /* webpack/runtime/make namespace object */ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = (exports) => { +/******/ if(Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/******/ /* webpack/runtime/import chunk loading */ +/******/ (() => { +/******/ // no baseURI +/******/ +/******/ // object to store loaded and loading chunks +/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched +/******/ // [resolve, Promise] = chunk loading, 0 = chunk loaded +/******/ const installedChunks = { +/******/ 792: 0 +/******/ }; +/******/ +/******/ const installChunk = (data) => { +/******/ let {__webpack_esm_ids__, __webpack_esm_modules__, __webpack_esm_runtime__} = data; +/******/ // add "modules" to the modules object, +/******/ // then flag all "ids" as loaded and fire callback +/******/ var moduleId, chunkId, i = 0; +/******/ for(moduleId in __webpack_esm_modules__) { +/******/ if(__webpack_require__.o(__webpack_esm_modules__, moduleId)) { +/******/ __webpack_require__.m[moduleId] = __webpack_esm_modules__[moduleId]; +/******/ } +/******/ } +/******/ if(__webpack_esm_runtime__) __webpack_esm_runtime__(__webpack_require__); +/******/ for(;i < __webpack_esm_ids__.length; i++) { +/******/ chunkId = __webpack_esm_ids__[i]; +/******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) { +/******/ installedChunks[chunkId][0](); +/******/ } +/******/ installedChunks[__webpack_esm_ids__[i]] = 0; +/******/ } +/******/ +/******/ } +/******/ +/******/ // no chunk on demand loading +/******/ +/******/ // no prefetching +/******/ +/******/ // no preloaded +/******/ +/******/ // no external install chunk +/******/ +/******/ __webpack_require__.ei = (chunkId, importFn) => { +/******/ let promises = []; +/******/ let installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined; +/******/ if(installedChunkData !== 0) { // 0 means "already installed". +/******/ // a Promise means "currently loading". +/******/ if(installedChunkData) { +/******/ promises.push(installedChunkData[1]); +/******/ } else { +/******/ let promise = importFn().then(installChunk, (e) => { +/******/ if(installedChunks[chunkId] !== 0) installedChunks[chunkId] = undefined; +/******/ throw e; +/******/ }); +/******/ promise = Promise.race([promise, new Promise((resolve) => (installedChunkData = installedChunks[chunkId] = [resolve]))]); +/******/ promises.push((installedChunkData[1] = promise)); +/******/ } +/******/ } +/******/ // no other chunk loading handlers +/******/ return Promise.all(promises); +/******/ }; +/******/ +/******/ // no on chunks loaded +/******/ // no HMR +/******/ +/******/ // no HMR manifest +/******/ })(); +/******/ +/************************************************************************/ +``` + +
+ +``` js +let __webpack_exports__ = {}; +/*!********************!*\ + !*** ./example.js ***! + \********************/ +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: __webpack_require__.ei, __webpack_require__ */ +document.body.innerHTML = ` +

+	
+ + +
+

Computing fibonacci without worker:

+ +

+	

Computing fibonacci with worker:

+ +

+`;
+
+const history = document.getElementById("history");
+const message = document.getElementById("message");
+const send = document.getElementById("send");
+const fib1 = document.getElementById("fib1");
+const output1 = document.getElementById("output1");
+const fib2 = document.getElementById("fib2");
+const output2 = document.getElementById("output2");
+
+/// CHAT with shared worker ///
+
+const chatWorker = new SharedWorker(
+	new URL(/* worker import */ "/dist/chat.js", import.meta.url),
+	{
+		name: "chat",
+		type: "module"
+	}
+);
+
+let historyTimeout;
+const scheduleUpdateHistory = () => {
+	clearTimeout(historyTimeout);
+	historyTimeout = setTimeout(() => {
+		chatWorker.port.postMessage({ type: "history" });
+	}, 1000);
+};
+scheduleUpdateHistory();
+
+const from = `User ${Math.floor(Math.random() * 10000)}`;
+
+send.addEventListener("click", e => {
+	chatWorker.port.postMessage({
+		type: "message",
+		content: message.value,
+		from
+	});
+	message.value = "";
+	message.focus();
+	e.preventDefault();
+});
+
+chatWorker.port.onmessage = event => {
+	const msg = event.data;
+	switch (msg.type) {
+		case "history":
+			history.innerText = msg.history.join("\n");
+			scheduleUpdateHistory();
+			break;
+	}
+};
+
+/// FIBONACCI without worker ///
+
+fib1.addEventListener("change", async () => {
+	try {
+		const value = parseInt(fib1.value, 10);
+		const { fibonacci } = await __webpack_require__.ei(129, () => import(/*! import() */ "/dist/129.js")).then(__webpack_require__.bind(__webpack_require__, /*! ./fibonacci */ 3));
+		const result = fibonacci(value);
+		output1.innerText = `fib(${value}) = ${result}`;
+	} catch (e) {
+		output1.innerText = e.message;
+	}
+});
+
+/// FIBONACCI with worker ///
+
+const fibWorker = new Worker(new URL(/* worker import */ "/dist/workers/fibonacci.js", import.meta.url), {
+	name: "fibonacci",
+	type: "module"
+	/* webpackEntryOptions: { filename: "workers/[name].js" } */
+});
+
+fib2.addEventListener("change", () => {
+	try {
+		const value = parseInt(fib2.value, 10);
+		fibWorker.postMessage(`${value}`);
+	} catch (e) {
+		output2.innerText = e.message;
+	}
+});
+
+fibWorker.onmessage = event => {
+	output2.innerText = event.data;
+};
+```
+
+# dist/chat.js
+
+```javascript
+/******/ var __webpack_modules__ = ({});
+```
+
+
/* webpack runtime code */ + +``` js +/************************************************************************/ +/******/ // The module cache +/******/ const __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ const 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; +/******/ } +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = __webpack_modules__; +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/define property getters */ +/******/ // define getter/value functions for harmony exports +/******/ __webpack_require__.d = (exports, definition) => { +/******/ if(Array.isArray(definition)) { +/******/ var i = 0; +/******/ while(i < definition.length) { +/******/ var key = definition[i++]; +/******/ var binding = definition[i++]; +/******/ if(!__webpack_require__.o(exports, key)) { +/******/ if(binding === 0) { +/******/ Object.defineProperty(exports, key, { enumerable: true, value: definition[i++] }); +/******/ } else { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: binding }); +/******/ } +/******/ } else if(binding === 0) { i++; } +/******/ } +/******/ } else { +/******/ for(var key in definition) { +/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); +/******/ } +/******/ } +/******/ } +/******/ }; +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ __webpack_require__.o = (obj, prop) => (Object.hasOwn(obj, prop)); +/******/ +/******/ /* webpack/runtime/make namespace object */ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = (exports) => { +/******/ if(Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/******/ /* webpack/runtime/import chunk loading */ +/******/ (() => { +/******/ // no baseURI +/******/ +/******/ // object to store loaded and loading chunks +/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched +/******/ // [resolve, Promise] = chunk loading, 0 = chunk loaded +/******/ const installedChunks = { +/******/ 377: 0 +/******/ }; +/******/ +/******/ const installChunk = (data) => { +/******/ let {__webpack_esm_ids__, __webpack_esm_modules__, __webpack_esm_runtime__} = data; +/******/ // add "modules" to the modules object, +/******/ // then flag all "ids" as loaded and fire callback +/******/ var moduleId, chunkId, i = 0; +/******/ for(moduleId in __webpack_esm_modules__) { +/******/ if(__webpack_require__.o(__webpack_esm_modules__, moduleId)) { +/******/ __webpack_require__.m[moduleId] = __webpack_esm_modules__[moduleId]; +/******/ } +/******/ } +/******/ if(__webpack_esm_runtime__) __webpack_esm_runtime__(__webpack_require__); +/******/ for(;i < __webpack_esm_ids__.length; i++) { +/******/ chunkId = __webpack_esm_ids__[i]; +/******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) { +/******/ installedChunks[chunkId][0](); +/******/ } +/******/ installedChunks[__webpack_esm_ids__[i]] = 0; +/******/ } +/******/ +/******/ } +/******/ +/******/ // no chunk on demand loading +/******/ +/******/ // no prefetching +/******/ +/******/ // no preloaded +/******/ +/******/ // no external install chunk +/******/ +/******/ __webpack_require__.ei = (chunkId, importFn) => { +/******/ let promises = []; +/******/ let installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined; +/******/ if(installedChunkData !== 0) { // 0 means "already installed". +/******/ // a Promise means "currently loading". +/******/ if(installedChunkData) { +/******/ promises.push(installedChunkData[1]); +/******/ } else { +/******/ let promise = importFn().then(installChunk, (e) => { +/******/ if(installedChunks[chunkId] !== 0) installedChunks[chunkId] = undefined; +/******/ throw e; +/******/ }); +/******/ promise = Promise.race([promise, new Promise((resolve) => (installedChunkData = installedChunks[chunkId] = [resolve]))]); +/******/ promises.push((installedChunkData[1] = promise)); +/******/ } +/******/ } +/******/ // no other chunk loading handlers +/******/ return Promise.all(promises); +/******/ }; +/******/ +/******/ // no on chunks loaded +/******/ // no HMR +/******/ +/******/ // no HMR manifest +/******/ })(); +/******/ +/************************************************************************/ +``` + +
+ +``` js +let __webpack_exports__ = {}; +/*!************************!*\ + !*** ./chat-worker.js ***! + \************************/ +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: __webpack_require__.ei, __webpack_require__ */ +onconnect = function (e) { + for (const port of e.ports) { + port.onmessage = async event => { + const msg = event.data; + switch (msg.type) { + case "message": + const { add } = await __webpack_require__.ei(936, () => import(/*! import() */ "/dist/936.js")).then(__webpack_require__.bind(__webpack_require__, /*! ./chat-module */ 4)); + add(msg.content, msg.from); + // fallthrough + case "history": + const { history } = await __webpack_require__.ei(936, () => import(/*! import() */ "/dist/936.js")).then(__webpack_require__.bind(__webpack_require__, /*! ./chat-module */ 4)); + port.postMessage({ + type: "history", + history + }); + break; + } + }; + } +}; +``` + +```javascript +var e={};const t={};function o(r){const s=t[r];if(void 0!==s)return s.exports;const n=t[r]={exports:{}};return e[r](n,n.exports,o),n.exports}o.m=e,o.d=(e,t)=>{if(Array.isArray(t))for(var r=0;rObject.hasOwn(e,t),(()=>{const e={377:0},t=t=>{let{__webpack_esm_ids__:r,__webpack_esm_modules__:s,__webpack_esm_runtime__:n}=t;var i,a,c=0;for(i in s)o.o(s,i)&&(o.m[i]=s[i]);for(n&&n(o);c{let n=[],i=o.o(e,r)?e[r]:void 0;if(0!==i)if(i)n.push(i[1]);else{let o=s().then(t,t=>{throw 0!==e[r]&&(e[r]=void 0),t});o=Promise.race([o,new Promise(t=>i=e[r]=[t])]),n.push(i[1]=o)}return Promise.all(n)}})(),onconnect=function(e){for(const t of e.ports)t.onmessage=async e=>{const r=e.data;switch(r.type){case"message":const{add:e}=await o.ei(936,()=>import("/dist/936.js")).then(o.bind(o,936));e(r.content,r.from);case"history":const{history:s}=await o.ei(936,()=>import("/dist/936.js")).then(o.bind(o,936));t.postMessage({type:"history",history:s})}}}; +``` + +# dist/workers/fibonacci.js + +```javascript +/******/ var __webpack_modules__ = ({}); +``` + +
/* webpack runtime code */ + +``` js +/************************************************************************/ +/******/ // The module cache +/******/ const __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ const 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; +/******/ } +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = __webpack_modules__; +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/define property getters */ +/******/ // define getter/value functions for harmony exports +/******/ __webpack_require__.d = (exports, definition) => { +/******/ if(Array.isArray(definition)) { +/******/ var i = 0; +/******/ while(i < definition.length) { +/******/ var key = definition[i++]; +/******/ var binding = definition[i++]; +/******/ if(!__webpack_require__.o(exports, key)) { +/******/ if(binding === 0) { +/******/ Object.defineProperty(exports, key, { enumerable: true, value: definition[i++] }); +/******/ } else { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: binding }); +/******/ } +/******/ } else if(binding === 0) { i++; } +/******/ } +/******/ } else { +/******/ for(var key in definition) { +/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); +/******/ } +/******/ } +/******/ } +/******/ }; +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ __webpack_require__.o = (obj, prop) => (Object.hasOwn(obj, prop)); +/******/ +/******/ /* webpack/runtime/make namespace object */ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = (exports) => { +/******/ if(Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/******/ /* webpack/runtime/import chunk loading */ +/******/ (() => { +/******/ // no baseURI +/******/ +/******/ // object to store loaded and loading chunks +/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched +/******/ // [resolve, Promise] = chunk loading, 0 = chunk loaded +/******/ const installedChunks = { +/******/ 721: 0 +/******/ }; +/******/ +/******/ const installChunk = (data) => { +/******/ let {__webpack_esm_ids__, __webpack_esm_modules__, __webpack_esm_runtime__} = data; +/******/ // add "modules" to the modules object, +/******/ // then flag all "ids" as loaded and fire callback +/******/ var moduleId, chunkId, i = 0; +/******/ for(moduleId in __webpack_esm_modules__) { +/******/ if(__webpack_require__.o(__webpack_esm_modules__, moduleId)) { +/******/ __webpack_require__.m[moduleId] = __webpack_esm_modules__[moduleId]; +/******/ } +/******/ } +/******/ if(__webpack_esm_runtime__) __webpack_esm_runtime__(__webpack_require__); +/******/ for(;i < __webpack_esm_ids__.length; i++) { +/******/ chunkId = __webpack_esm_ids__[i]; +/******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) { +/******/ installedChunks[chunkId][0](); +/******/ } +/******/ installedChunks[__webpack_esm_ids__[i]] = 0; +/******/ } +/******/ +/******/ } +/******/ +/******/ // no chunk on demand loading +/******/ +/******/ // no prefetching +/******/ +/******/ // no preloaded +/******/ +/******/ // no external install chunk +/******/ +/******/ __webpack_require__.ei = (chunkId, importFn) => { +/******/ let promises = []; +/******/ let installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined; +/******/ if(installedChunkData !== 0) { // 0 means "already installed". +/******/ // a Promise means "currently loading". +/******/ if(installedChunkData) { +/******/ promises.push(installedChunkData[1]); +/******/ } else { +/******/ let promise = importFn().then(installChunk, (e) => { +/******/ if(installedChunks[chunkId] !== 0) installedChunks[chunkId] = undefined; +/******/ throw e; +/******/ }); +/******/ promise = Promise.race([promise, new Promise((resolve) => (installedChunkData = installedChunks[chunkId] = [resolve]))]); +/******/ promises.push((installedChunkData[1] = promise)); +/******/ } +/******/ } +/******/ // no other chunk loading handlers +/******/ return Promise.all(promises); +/******/ }; +/******/ +/******/ // no on chunks loaded +/******/ // no HMR +/******/ +/******/ // no HMR manifest +/******/ })(); +/******/ +/************************************************************************/ +``` + +
+ +``` js +let __webpack_exports__ = {}; +/*!***********************!*\ + !*** ./fib-worker.js ***! + \***********************/ +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: __webpack_require__.ei, __webpack_require__ */ +onmessage = async event => { + const { fibonacci } = await __webpack_require__.ei(129, () => import(/*! import() */ "/dist/129.js")).then(__webpack_require__.bind(__webpack_require__, /*! ./fibonacci */ 3)); + const value = JSON.parse(event.data); + postMessage(`fib(${value}) = ${fibonacci(value)}`); +}; +``` + +```javascript +var e={};const r={};function o(t){const s=r[t];if(void 0!==s)return s.exports;const n=r[t]={exports:{}};return e[t](n,n.exports,o),n.exports}o.m=e,o.d=(e,r)=>{if(Array.isArray(r))for(var t=0;tObject.hasOwn(e,r),(()=>{const e={721:0},r=r=>{let{__webpack_esm_ids__:t,__webpack_esm_modules__:s,__webpack_esm_runtime__:n}=r;var i,a,_=0;for(i in s)o.o(s,i)&&(o.m[i]=s[i]);for(n&&n(o);_{let n=[],i=o.o(e,t)?e[t]:void 0;if(0!==i)if(i)n.push(i[1]);else{let o=s().then(r,r=>{throw 0!==e[t]&&(e[t]=void 0),r});o=Promise.race([o,new Promise(r=>i=e[t]=[r])]),n.push(i[1]=o)}return Promise.all(n)}})(),onmessage=async e=>{const{fibonacci:r}=await o.ei(129,()=>import("/dist/129.js")).then(o.bind(o,129)),t=JSON.parse(e.data);postMessage(`fib(${t}) = ${r(t)}`)}; +``` + +# dist/129.js + +```javascript +export const __webpack_esm_id__ = 129; +export const __webpack_esm_ids__ = [129]; +export const __webpack_esm_modules__ = { + +/***/ 3 +/*!**********************!*\ + !*** ./fibonacci.js ***! + \**********************/ +/*! namespace exports */ +/*! export fibonacci [provided] [no usage info] [missing usage info prevents renaming] */ +/*! other exports [not provided] [no usage info] */ +/*! runtime requirements: __webpack_require__.r, __webpack_exports__, __webpack_require__.d, __webpack_require__.* */ +(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ fibonacci: () => (/* binding */ fibonacci) +/* harmony export */ }); +function fibonacci(n) { + return n < 1 ? 0 : n <= 2 ? 1 : fibonacci(n - 1) + fibonacci(n - 2); +} + + +/***/ } + +}; +``` + +# Info + +## Unoptimized + +``` +asset main.js 7.94 KiB [emitted] [javascript module] (name: main) +asset chat.js 6.2 KiB [emitted] [javascript module] (name: chat) +asset workers/fibonacci.js 5.82 KiB [emitted] [javascript module] (name: fibonacci) +asset 936.js 1.04 KiB [emitted] [javascript module] +asset 129.js 881 bytes [emitted] [javascript module] +chunk (runtime: 9a81d90cfd0dfd13d748, main) 129.js 103 bytes [rendered] + > ./fibonacci ./example.js 70:30-51 + > ./fibonacci ./fib-worker.js 2:29-50 + ./fibonacci.js 103 bytes [built] [code generated] + [exports: fibonacci] + [used exports unknown] + import() ./fibonacci ./example.js 70:30-51 + import() ./fibonacci ./fib-worker.js 2:29-50 +chunk (runtime: 1fad8bf8de78b0a77bfd) chat.js (chat) 442 bytes (javascript) 2.92 KiB (runtime) [entry] [rendered] + > ./example.js 25:19-31:1 + runtime modules 2.92 KiB 4 modules + ./chat-worker.js 442 bytes [built] [code generated] + [used exports unknown] + new Worker() ./chat-worker.js ./example.js 25:19-31:1 +chunk (runtime: 9a81d90cfd0dfd13d748) workers/fibonacci.js (fibonacci) 176 bytes (javascript) 2.92 KiB (runtime) [entry] [rendered] + > ./example.js 80:18-84:2 + runtime modules 2.92 KiB 4 modules + ./fib-worker.js 176 bytes [built] [code generated] + [used exports unknown] + new Worker() ./fib-worker.js ./example.js 80:18-84:2 +chunk (runtime: main) main.js (main) 2.25 KiB (javascript) 2.92 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 2.92 KiB 4 modules + ./example.js 2.25 KiB [built] [code generated] + [used exports unknown] + entry ./example.js main +chunk (runtime: 1fad8bf8de78b0a77bfd) 936.js 152 bytes [rendered] + > ./chat-module ./chat-worker.js 11:31-54 + > ./chat-module ./chat-worker.js 7:27-50 + ./chat-module.js 152 bytes [built] [code generated] + [exports: add, history] + [used exports unknown] + import() ./chat-module ./chat-worker.js 7:27-50 + import() ./chat-module ./chat-worker.js 11:31-54 +webpack X.X.X compiled successfully +``` + +## Production mode + +``` +asset main.js 2.45 KiB [emitted] [javascript module] [minimized] (name: main) +asset chat.js 1.25 KiB [emitted] [javascript module] [minimized] (name: chat) +asset workers/fibonacci.js 1.06 KiB [emitted] [javascript module] [minimized] (name: fibonacci) +asset 936.js 221 bytes [emitted] [javascript module] [minimized] +asset 129.js 199 bytes [emitted] [javascript module] [minimized] +chunk (runtime: 9a81d90cfd0dfd13d748, main) 129.js 103 bytes [rendered] + > ./fibonacci ./fib-worker.js 2:29-50 + > ./fibonacci ./example.js 70:30-51 + ./fibonacci.js 103 bytes [built] [code generated] + [exports: fibonacci] + [all exports used] + import() ./fibonacci ./example.js 70:30-51 + import() ./fibonacci ./fib-worker.js 2:29-50 +chunk (runtime: 1fad8bf8de78b0a77bfd) chat.js (chat) 442 bytes (javascript) 2.68 KiB (runtime) [entry] [rendered] + > ./example.js 25:19-31:1 + runtime modules 2.68 KiB 3 modules + ./chat-worker.js 442 bytes [built] [code generated] + [no exports used] + new Worker() ./chat-worker.js ./example.js 25:19-31:1 +chunk (runtime: 9a81d90cfd0dfd13d748) workers/fibonacci.js (fibonacci) 176 bytes (javascript) 2.68 KiB (runtime) [entry] [rendered] + > ./example.js 80:18-84:2 + runtime modules 2.68 KiB 3 modules + ./fib-worker.js 176 bytes [built] [code generated] + [no exports used] + new Worker() ./fib-worker.js ./example.js 80:18-84:2 +chunk (runtime: main) main.js (main) 2.25 KiB (javascript) 2.68 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 2.68 KiB 3 modules + ./example.js 2.25 KiB [built] [code generated] + [no exports used] + entry ./example.js main +chunk (runtime: 1fad8bf8de78b0a77bfd) 936.js 152 bytes [rendered] + > ./chat-module ./chat-worker.js 11:31-54 + > ./chat-module ./chat-worker.js 7:27-50 + ./chat-module.js 152 bytes [built] [code generated] + [exports: add, history] + [all exports used] + import() ./chat-module ./chat-worker.js 7:27-50 + import() ./chat-module ./chat-worker.js 11:31-54 +webpack X.X.X compiled successfully +``` diff --git a/examples/module-worker/build.js b/examples/module-worker/build.js new file mode 100644 index 00000000000..5768b058787 --- /dev/null +++ b/examples/module-worker/build.js @@ -0,0 +1,3 @@ +global.NO_TARGET_ARGS = true; +global.NO_PUBLIC_PATH = true; +require("../build-common"); diff --git a/examples/module-worker/chat-module.js b/examples/module-worker/chat-module.js new file mode 100644 index 00000000000..716a104a9dc --- /dev/null +++ b/examples/module-worker/chat-module.js @@ -0,0 +1,6 @@ +export const history = []; + +export const add = (content, from) => { + if (history.length > 10) history.shift(); + history.push(`${from}: ${content}`); +}; diff --git a/examples/module-worker/chat-worker.js b/examples/module-worker/chat-worker.js new file mode 100644 index 00000000000..1a8bcb81ea0 --- /dev/null +++ b/examples/module-worker/chat-worker.js @@ -0,0 +1,20 @@ +onconnect = function (e) { + for (const port of e.ports) { + port.onmessage = async event => { + const msg = event.data; + switch (msg.type) { + case "message": + const { add } = await import("./chat-module"); + add(msg.content, msg.from); + // fallthrough + case "history": + const { history } = await import("./chat-module"); + port.postMessage({ + type: "history", + history + }); + break; + } + }; + } +}; diff --git a/examples/module-worker/example.js b/examples/module-worker/example.js new file mode 100644 index 00000000000..fcbe23f092f --- /dev/null +++ b/examples/module-worker/example.js @@ -0,0 +1,97 @@ +document.body.innerHTML = ` +

+	
+ + +
+

Computing fibonacci without worker:

+ +

+	

Computing fibonacci with worker:

+ +

+`;
+
+const history = document.getElementById("history");
+const message = document.getElementById("message");
+const send = document.getElementById("send");
+const fib1 = document.getElementById("fib1");
+const output1 = document.getElementById("output1");
+const fib2 = document.getElementById("fib2");
+const output2 = document.getElementById("output2");
+
+/// CHAT with shared worker ///
+
+const chatWorker = new SharedWorker(
+	new URL("./chat-worker.js", import.meta.url),
+	{
+		name: "chat",
+		type: "module"
+	}
+);
+
+let historyTimeout;
+const scheduleUpdateHistory = () => {
+	clearTimeout(historyTimeout);
+	historyTimeout = setTimeout(() => {
+		chatWorker.port.postMessage({ type: "history" });
+	}, 1000);
+};
+scheduleUpdateHistory();
+
+const from = `User ${Math.floor(Math.random() * 10000)}`;
+
+send.addEventListener("click", e => {
+	chatWorker.port.postMessage({
+		type: "message",
+		content: message.value,
+		from
+	});
+	message.value = "";
+	message.focus();
+	e.preventDefault();
+});
+
+chatWorker.port.onmessage = event => {
+	const msg = event.data;
+	switch (msg.type) {
+		case "history":
+			history.innerText = msg.history.join("\n");
+			scheduleUpdateHistory();
+			break;
+	}
+};
+
+/// FIBONACCI without worker ///
+
+fib1.addEventListener("change", async () => {
+	try {
+		const value = parseInt(fib1.value, 10);
+		const { fibonacci } = await import("./fibonacci");
+		const result = fibonacci(value);
+		output1.innerText = `fib(${value}) = ${result}`;
+	} catch (e) {
+		output1.innerText = e.message;
+	}
+});
+
+/// FIBONACCI with worker ///
+
+const fibWorker = new Worker(new URL("./fib-worker.js", import.meta.url), {
+	name: "fibonacci",
+	type: "module"
+	/* webpackEntryOptions: { filename: "workers/[name].js" } */
+});
+
+fib2.addEventListener("change", () => {
+	try {
+		const value = parseInt(fib2.value, 10);
+		fibWorker.postMessage(`${value}`);
+	} catch (e) {
+		output2.innerText = e.message;
+	}
+});
+
+fibWorker.onmessage = event => {
+	output2.innerText = event.data;
+};
diff --git a/examples/module-worker/fib-worker.js b/examples/module-worker/fib-worker.js
new file mode 100644
index 00000000000..42efa83cf4e
--- /dev/null
+++ b/examples/module-worker/fib-worker.js
@@ -0,0 +1,5 @@
+onmessage = async event => {
+	const { fibonacci } = await import("./fibonacci");
+	const value = JSON.parse(event.data);
+	postMessage(`fib(${value}) = ${fibonacci(value)}`);
+};
diff --git a/examples/module-worker/fibonacci.js b/examples/module-worker/fibonacci.js
new file mode 100644
index 00000000000..282fcec2fca
--- /dev/null
+++ b/examples/module-worker/fibonacci.js
@@ -0,0 +1,3 @@
+export function fibonacci(n) {
+	return n < 1 ? 0 : n <= 2 ? 1 : fibonacci(n - 1) + fibonacci(n - 2);
+}
diff --git a/examples/module-worker/index.html b/examples/module-worker/index.html
new file mode 100644
index 00000000000..e3b460bdf15
--- /dev/null
+++ b/examples/module-worker/index.html
@@ -0,0 +1,10 @@
+
+
+	
+		
+		Worker example
+	
+	
+		
+	
+
diff --git a/examples/module-worker/template.md b/examples/module-worker/template.md
new file mode 100644
index 00000000000..6a93ddfd9b5
--- /dev/null
+++ b/examples/module-worker/template.md
@@ -0,0 +1,75 @@
+# example.js
+
+```javascript
+_{{example.js}}_
+```
+
+# fib-worker.js
+
+```javascript
+_{{fib-worker.js}}_
+```
+
+# fibonacci.js
+
+```javascript
+_{{fibonacci.js}}_
+```
+
+# chat-worker.js
+
+```javascript
+_{{chat-worker.js}}_
+```
+
+# chat-module.js
+
+```javascript
+_{{chat-module.js}}_
+```
+
+# dist/main.js
+
+```javascript
+_{{dist/main.js}}_
+```
+
+# dist/chat.js
+
+```javascript
+_{{dist/chat.js}}_
+```
+
+```javascript
+_{{production:dist/chat.js}}_
+```
+
+# dist/workers/fibonacci.js
+
+```javascript
+_{{dist/workers/fibonacci.js}}_
+```
+
+```javascript
+_{{production:dist/workers/fibonacci.js}}_
+```
+
+# dist/129.js
+
+```javascript
+_{{dist/129.js}}_
+```
+
+# Info
+
+## Unoptimized
+
+```
+_{{stdout}}_
+```
+
+## Production mode
+
+```
+_{{production:stdout}}_
+```
diff --git a/examples/module-worker/webpack.config.js b/examples/module-worker/webpack.config.js
new file mode 100644
index 00000000000..98d1091ca0c
--- /dev/null
+++ b/examples/module-worker/webpack.config.js
@@ -0,0 +1,23 @@
+"use strict";
+
+const path = require("path");
+
+/** @type {import("webpack").Configuration} */
+const config = {
+	entry: "./example.js",
+	output: {
+		path: path.join(__dirname, "dist"),
+		filename: "[name].js",
+		chunkFilename: "[name].js",
+		publicPath: "/dist/"
+	},
+	optimization: {
+		chunkIds: "deterministic" // To keep filename consistent between different modes (for example building only)
+	},
+	target: "browserslist: last 2 Chrome versions",
+	experiments: {
+		outputModule: true
+	}
+};
+
+module.exports = config;
diff --git a/examples/module/README.md b/examples/module/README.md
new file mode 100644
index 00000000000..924f4d6fcb8
--- /dev/null
+++ b/examples/module/README.md
@@ -0,0 +1,118 @@
+# example.js
+
+```javascript
+import { increment as inc, value } from "./counter";
+import { resetCounter, print } from "./methods";
+print(value);
+inc();
+inc();
+inc();
+print(value);
+resetCounter();
+print(value);
+
+export { inc, print };
+```
+
+# methods.js
+
+```javascript
+export { reset as resetCounter } from "./counter";
+
+export const print = value => console.log(value);
+```
+
+# counter.js
+
+```javascript
+export let value = 0;
+export function increment() {
+	value++;
+}
+export function decrement() {
+	value--;
+}
+export function reset() {
+	value = 0;
+}
+```
+
+# dist/output.js
+
+```javascript
+/*!********************************!*\
+  !*** ./example.js + 2 modules ***!
+  \********************************/
+/*! namespace exports */
+/*! export inc [provided] [used in main] [could be renamed] -> ./counter.js .increment */
+/*! export print [provided] [used in main] [could be renamed] -> ./methods.js .print */
+/*! runtime requirements: __webpack_exports__, __webpack_require__.d, __webpack_require__.* */
+
+;// ./counter.js
+let value = 0;
+function increment() {
+	value++;
+}
+function decrement() {
+	value--;
+}
+function counter_reset() {
+	value = 0;
+}
+
+;// ./methods.js
+
+
+const print = value => console.log(value);
+
+;// ./example.js
+
+
+print(value);
+increment();
+increment();
+increment();
+print(value);
+counter_reset();
+print(value);
+
+
+
+export { increment as inc, print };
+```
+
+# dist/output.js (production)
+
+```javascript
+let o=0;function n(){o++}const c=o=>console.log(o);c(o),n(),n(),n(),c(o),o=0,c(o);export{n as inc,c as print};
+```
+
+# Info
+
+## Unoptimized
+
+```
+asset output.js 775 bytes [emitted] [javascript module] (name: main)
+chunk (runtime: main) output.js (main) 453 bytes [entry] [rendered]
+  > ./example.js main
+  ./example.js + 2 modules 453 bytes [built] [code generated]
+    [exports: inc, print]
+    [all exports used]
+    entry ./example.js main
+    used as library export
+webpack X.X.X compiled successfully
+```
+
+## Production mode
+
+```
+asset output.js 110 bytes [emitted] [javascript module] [minimized] (name: main)
+chunk (runtime: main) output.js (main) 453 bytes [entry] [rendered]
+  > ./example.js main
+  ./example.js + 2 modules 453 bytes [built] [code generated]
+    [exports: inc, print]
+    [all exports used]
+    entry ./example.js main
+    used as library export
+webpack X.X.X compiled successfully
+```
diff --git a/examples/module/build.js b/examples/module/build.js
new file mode 100644
index 00000000000..41c29c9d169
--- /dev/null
+++ b/examples/module/build.js
@@ -0,0 +1 @@
+require("../build-common");
\ No newline at end of file
diff --git a/examples/module/counter.js b/examples/module/counter.js
new file mode 100644
index 00000000000..7009896e282
--- /dev/null
+++ b/examples/module/counter.js
@@ -0,0 +1,10 @@
+export let value = 0;
+export function increment() {
+	value++;
+}
+export function decrement() {
+	value--;
+}
+export function reset() {
+	value = 0;
+}
diff --git a/examples/module/example.js b/examples/module/example.js
new file mode 100644
index 00000000000..29e215a8009
--- /dev/null
+++ b/examples/module/example.js
@@ -0,0 +1,11 @@
+import { increment as inc, value } from "./counter";
+import { resetCounter, print } from "./methods";
+print(value);
+inc();
+inc();
+inc();
+print(value);
+resetCounter();
+print(value);
+
+export { inc, print };
diff --git a/examples/module/methods.js b/examples/module/methods.js
new file mode 100644
index 00000000000..4be8f10f704
--- /dev/null
+++ b/examples/module/methods.js
@@ -0,0 +1,3 @@
+export { reset as resetCounter } from "./counter";
+
+export const print = value => console.log(value);
diff --git a/examples/module/template.md b/examples/module/template.md
new file mode 100644
index 00000000000..98d06e62ec9
--- /dev/null
+++ b/examples/module/template.md
@@ -0,0 +1,43 @@
+# example.js
+
+```javascript
+_{{example.js}}_
+```
+
+# methods.js
+
+```javascript
+_{{methods.js}}_
+```
+
+# counter.js
+
+```javascript
+_{{counter.js}}_
+```
+
+# dist/output.js
+
+```javascript
+_{{dist/output.js}}_
+```
+
+# dist/output.js (production)
+
+```javascript
+_{{production:dist/output.js}}_
+```
+
+# Info
+
+## Unoptimized
+
+```
+_{{stdout}}_
+```
+
+## Production mode
+
+```
+_{{production:stdout}}_
+```
diff --git a/examples/module/webpack.config.js b/examples/module/webpack.config.js
new file mode 100644
index 00000000000..38de9e9ad7e
--- /dev/null
+++ b/examples/module/webpack.config.js
@@ -0,0 +1,20 @@
+"use strict";
+
+/** @type {import("webpack").Configuration} */
+const config = {
+	output: {
+		module: true,
+		library: {
+			type: "module"
+		}
+	},
+	optimization: {
+		usedExports: true,
+		concatenateModules: true
+	},
+	experiments: {
+		outputModule: true
+	}
+};
+
+module.exports = config;
diff --git a/examples/multi-compiler/README.md b/examples/multi-compiler/README.md
index 1a84eb892fd..2158221aaff 100644
--- a/examples/multi-compiler/README.md
+++ b/examples/multi-compiler/README.md
@@ -1,7 +1,6 @@
-
 # example.js
 
-``` javascript
+```javascript
 if(ENV === "mobile") {
 	require("./mobile-stuff");
 }
@@ -10,13 +9,17 @@ console.log("Running " + ENV + " build");
 
 # webpack.config.js
 
-``` javascript
-var path = require("path");
-var webpack = require("../../");
-module.exports = [
+```javascript
+"use strict";
+
+const path = require("path");
+const webpack = require("../../");
+
+/** @type {import("webpack").Configuration[]} */
+const config = [
 	{
 		name: "mobile",
-		// mode: "development || "production",
+		// mode: "development" || "production",
 		entry: "./example",
 		output: {
 			path: path.join(__dirname, "dist"),
@@ -31,7 +34,7 @@ module.exports = [
 
 	{
 		name: "desktop",
-		// mode: "development || "production",
+		// mode: "development" || "production",
 		entry: "./example",
 		output: {
 			path: path.join(__dirname, "dist"),
@@ -44,203 +47,95 @@ module.exports = [
 		]
 	}
 ];
-```
-
-# dist/desktop.js
-
-
/******/ (function(modules) { /* webpackBootstrap */ }) -``` javascript -/******/ (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, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); -/******/ } -/******/ }; -/******/ -/******/ // define __esModule on exports -/******/ __webpack_require__.r = function(exports) { -/******/ Object.defineProperty(exports, '__esModule', { value: true }); -/******/ }; -/******/ -/******/ // 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 = "dist/"; -/******/ -/******/ -/******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 0); -/******/ }) -/************************************************************************/ +module.exports = config; ``` -
+# dist/desktop.js -``` javascript -/******/ ([ -/* 0 */ +```javascript +/******/ (() => { // webpackBootstrap /*!********************!*\ !*** ./example.js ***! \********************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -if(false) {} +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: */ +if(false) // removed by dead control flow +{} console.log("Running " + "desktop" + " build"); +/******/ })() +; +``` + +# dist/mobile.js + +```javascript +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ([ +/* 0 */, +/* 1 */ +/*!*************************!*\ + !*** ./mobile-stuff.js ***! + \*************************/ +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: */ +/***/ (() => { + +// mobile only stuff /***/ }) -/******/ ]); +/******/ ]); ``` -# dist/mobile.js +
/* webpack runtime code */ -``` javascript -/******/ (function(modules) { // webpackBootstrap +``` js +/************************************************************************/ /******/ // The module cache -/******/ var installedModules = {}; -/******/ +/******/ const __webpack_module_cache__ = {}; +/******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { -/******/ /******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) { -/******/ return installedModules[moduleId].exports; +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, +/******/ const module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed /******/ exports: {} /******/ }; -/******/ +/******/ /******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ /******/ // 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, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); -/******/ } -/******/ }; -/******/ -/******/ // define __esModule on exports -/******/ __webpack_require__.r = function(exports) { -/******/ Object.defineProperty(exports, '__esModule', { value: true }); -/******/ }; -/******/ -/******/ // 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 = "dist/"; -/******/ -/******/ -/******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 0); -/******/ }) +/******/ /************************************************************************/ -/******/ ([ -/* 0 */ +``` + +
+ +``` js +// This entry needs to be wrapped in an IIFE because it needs to be isolated against other modules in the chunk. +(() => { /*!********************!*\ !*** ./example.js ***! \********************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: __webpack_require__ */ if(true) { __webpack_require__(/*! ./mobile-stuff */ 1); } console.log("Running " + "mobile" + " build"); +})(); -/***/ }), -/* 1 */ -/*!*************************!*\ - !*** ./mobile-stuff.js ***! - \*************************/ -/*! no static exports found */ -/***/ (function(module, exports) { - -// mobile only stuff - -/***/ }) -/******/ ]); +/******/ })() +; ``` # Info @@ -248,53 +143,44 @@ console.log("Running " + "mobile" + " build"); ## Unoptimized ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 -Child mobile: - Hash: 0a1b2c3d4e5f6a7b8c9d - Asset Size Chunks Chunk Names - mobile.js 3.04 KiB 0 [emitted] main - Entrypoint main = mobile.js - chunk {0} mobile.js (main) 117 bytes [entry] [rendered] - > ./example main - [0] ./example.js 97 bytes {0} [built] - single entry ./example main - [1] ./mobile-stuff.js 20 bytes {0} [built] - cjs require ./mobile-stuff [0] ./example.js 2:1-26 -Child desktop: - Hash: 0a1b2c3d4e5f6a7b8c9d - Asset Size Chunks Chunk Names - desktop.js 2.8 KiB 0 [emitted] main - Entrypoint main = desktop.js - chunk {0} desktop.js (main) 97 bytes [entry] [rendered] - > ./example main - [0] ./example.js 97 bytes {0} [built] - single entry ./example main +mobile: + asset mobile.js 1.72 KiB [emitted] (name: main) + chunk (runtime: main) mobile.js (main) 114 bytes [entry] [rendered] + > ./example main + dependent modules 20 bytes [dependent] 1 module + ./example.js 94 bytes [built] [code generated] + [used exports unknown] + entry ./example main + mobile (webpack X.X.X) compiled successfully + +desktop: + asset desktop.js 294 bytes [emitted] (name: main) + chunk (runtime: main) desktop.js (main) 94 bytes [entry] [rendered] + > ./example main + ./example.js 94 bytes [built] [code generated] + [used exports unknown] + entry ./example main + desktop (webpack X.X.X) compiled successfully ``` ## Production mode ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 -Child mobile: - Hash: 0a1b2c3d4e5f6a7b8c9d - Asset Size Chunks Chunk Names - mobile.js 608 bytes 0 [emitted] main - Entrypoint main = mobile.js - chunk {0} mobile.js (main) 117 bytes [entry] [rendered] - > ./example main - [0] ./mobile-stuff.js 20 bytes {0} [built] - cjs require ./mobile-stuff [1] ./example.js 2:1-26 - [1] ./example.js 97 bytes {0} [built] - single entry ./example main -Child desktop: - Hash: 0a1b2c3d4e5f6a7b8c9d - Asset Size Chunks Chunk Names - desktop.js 588 bytes 0 [emitted] main - Entrypoint main = desktop.js - chunk {0} desktop.js (main) 97 bytes [entry] [rendered] - > ./example main - [0] ./example.js 97 bytes {0} [built] - single entry ./example main -``` \ No newline at end of file +mobile: + asset mobile.js 36 bytes [emitted] [minimized] (name: main) + chunk (runtime: main) mobile.js (main) 94 bytes [entry] [rendered] + > ./example main + ./example.js 94 bytes [built] [code generated] + [no exports used] + entry ./example main + mobile (webpack X.X.X) compiled successfully + +desktop: + asset desktop.js 37 bytes [emitted] [minimized] (name: main) + chunk (runtime: main) desktop.js (main) 94 bytes [entry] [rendered] + > ./example main + ./example.js 94 bytes [built] [code generated] + [no exports used] + entry ./example main + desktop (webpack X.X.X) compiled successfully +``` diff --git a/examples/multi-compiler/template.md b/examples/multi-compiler/template.md index f1d65349f43..bcd632cf46e 100644 --- a/examples/multi-compiler/template.md +++ b/examples/multi-compiler/template.md @@ -1,26 +1,25 @@ - # example.js -``` javascript -{{example.js}} +```javascript +_{{example.js}}_ ``` # webpack.config.js -``` javascript -{{webpack.config.js}} +```javascript +_{{webpack.config.js}}_ ``` # dist/desktop.js -``` javascript -{{dist/desktop.js}} +```javascript +_{{dist/desktop.js}}_ ``` # dist/mobile.js -``` javascript -{{dist/mobile.js}} +```javascript +_{{dist/mobile.js}}_ ``` # Info @@ -28,11 +27,11 @@ ## Unoptimized ``` -{{stdout}} +_{{stdout}}_ ``` ## Production mode ``` -{{production:stdout}} -``` \ No newline at end of file +_{{production:stdout}}_ +``` diff --git a/examples/multi-compiler/webpack.config.js b/examples/multi-compiler/webpack.config.js index 4fc3088639a..87fe4e0c2e4 100644 --- a/examples/multi-compiler/webpack.config.js +++ b/examples/multi-compiler/webpack.config.js @@ -1,9 +1,13 @@ -var path = require("path"); -var webpack = require("../../"); -module.exports = [ +"use strict"; + +const path = require("path"); +const webpack = require("../../"); + +/** @type {import("webpack").Configuration[]} */ +const config = [ { name: "mobile", - // mode: "development || "production", + // mode: "development" || "production", entry: "./example", output: { path: path.join(__dirname, "dist"), @@ -18,7 +22,7 @@ module.exports = [ { name: "desktop", - // mode: "development || "production", + // mode: "development" || "production", entry: "./example", output: { path: path.join(__dirname, "dist"), @@ -31,3 +35,5 @@ module.exports = [ ] } ]; + +module.exports = config; diff --git a/examples/multi-part-library/README.md b/examples/multi-part-library/README.md index 9570e6dfb24..ed7bfa9b330 100644 --- a/examples/multi-part-library/README.md +++ b/examples/multi-part-library/README.md @@ -1,23 +1,27 @@ -This example demonstrates how to build a complex library with webpack. The library consist of multiple parts that are usable on its own and together. +This example demonstrates how to build a complex library with webpack. The library consists of multiple parts that are usable on its own and together. -When using this library with script tags it exports itself to the namespace `MyLibrary` and each part to a property in this namespace (`MyLibrary.alpha` and `MyLibrary.beta`). When consuming the library with CommonsJs or AMD it just export each part. +When using this library with script tags it exports itself to the namespace `MyLibrary` and each part to a property in this namespace (`MyLibrary.alpha` and `MyLibrary.beta`). When consuming the library with CommonsJS or AMD it just exports each part. -We are using multiple entry points (`entry` option) to build every part of the library as separate output file. The `output.filename` option contains `[name]` to give each output file a different name. +We are using multiple entry points (`entry` option) to build every part of the library as a separate output file. The `output.filename` option contains `[name]` to give each output file a different name. -We are using the `libraryTarget` option to generate a UMD ([Universal Module Definition](https://github.com/umdjs/umd)) module that is consumable in CommonsJs, AMD and with script tags. The `library` option defines the namespace. We are using `[name]` in the `library` option to give every entry a different namespace. +We are using the `libraryTarget` option to generate a UMD ([Universal Module Definition](https://github.com/umdjs/umd)) module that is consumable in CommonsJS, AMD and with script tags. The `library` option defines the namespace. We are using `[name]` in the `library` option to give every entry a different namespace. You can see that webpack automatically wraps your module so that it is consumable in every environment. All you need is this simple config. Note: You can also use the `library` and `libraryTarget` options without multiple entry points. Then you don't need `[name]`. -Note: When your library has dependencies that should not be included in the compiled version, you can use the `externals` option. See [externals example](https://github.com/webpack/webpack/tree/master/examples/externals). +Note: When your library has dependencies that should not be included in the compiled version, you can use the `externals` option. See [externals example](https://github.com/webpack/webpack/tree/main/examples/externals). # webpack.config.js -``` javascript -var path = require("path"); -module.exports = { - // mode: "development || "production", +```javascript +"use strict"; + +const path = require("path"); + +/** @type {import("webpack").Configuration} */ +const config = { + // mode: "development" || "production", entry: { alpha: "./alpha", beta: "./beta" @@ -29,213 +33,160 @@ module.exports = { libraryTarget: "umd" } }; + +module.exports = config; ``` # dist/MyLibrary.alpha.js -``` javascript +```javascript (function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if(typeof define === 'function' && define.amd) define([], factory); else if(typeof exports === 'object') - exports["alpha"] = factory(); + exports["MyLibrary"] = factory(); else root["MyLibrary"] = root["MyLibrary"] || {}, root["MyLibrary"]["alpha"] = factory(); -})(window, function() { +})(self, () => { +return /******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ([ +/* 0 */ +/*!******************!*\ + !*** ./alpha.js ***! + \******************/ +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: module */ +/*! CommonJS bailout: module.exports is used directly at 1:0-14 */ +/***/ ((module) => { + +module.exports = "alpha"; + +/***/ }) +/******/ ]); ``` -
return /******/ (function(modules) { /* webpackBootstrap */ }) + +
/* webpack runtime code */ ``` js -return /******/ (function(modules) { // webpackBootstrap +/************************************************************************/ /******/ // The module cache -/******/ var installedModules = {}; -/******/ +/******/ const __webpack_module_cache__ = {}; +/******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { -/******/ /******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) { -/******/ return installedModules[moduleId].exports; +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, +/******/ const module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed /******/ exports: {} /******/ }; -/******/ +/******/ /******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ /******/ // 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, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); -/******/ } -/******/ }; -/******/ -/******/ // define __esModule on exports -/******/ __webpack_require__.r = function(exports) { -/******/ Object.defineProperty(exports, '__esModule', { value: true }); -/******/ }; -/******/ -/******/ // 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 = "dist/"; -/******/ -/******/ -/******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 0); -/******/ }) +/******/ /************************************************************************/ ```
``` js -/******/ ([ -/* 0 */ -/*!******************!*\ - !*** ./alpha.js ***! - \******************/ -/*! no static exports found */ -/***/ (function(module, exports) { - -module.exports = "alpha"; - -/***/ }) -/******/ ]); +/******/ +/******/ // startup +/******/ // Load entry module and return exports +/******/ // This entry module is referenced by other modules so it can't be inlined +/******/ let __webpack_exports__ = __webpack_require__(0); +/******/ +/******/ return __webpack_exports__; +/******/ })() +; }); ``` # dist/MyLibrary.beta.js -``` javascript +```javascript (function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if(typeof define === 'function' && define.amd) define([], factory); else if(typeof exports === 'object') - exports["beta"] = factory(); + exports["MyLibrary"] = factory(); else root["MyLibrary"] = root["MyLibrary"] || {}, root["MyLibrary"]["beta"] = factory(); -})(window, function() { -return /******/ (function(modules) { // webpackBootstrap +})(self, () => { +return /******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ([ +/* 0 */, +/* 1 */ +/*!*****************!*\ + !*** ./beta.js ***! + \*****************/ +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: module */ +/*! CommonJS bailout: module.exports is used directly at 1:0-14 */ +/***/ ((module) => { + +module.exports = "beta"; + +/***/ }) +/******/ ]); +``` + +
/* webpack runtime code */ + +``` js +/************************************************************************/ /******/ // The module cache -/******/ var installedModules = {}; -/******/ +/******/ const __webpack_module_cache__ = {}; +/******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { -/******/ /******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) { -/******/ return installedModules[moduleId].exports; +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, +/******/ const module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed /******/ exports: {} /******/ }; -/******/ +/******/ /******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ /******/ // 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, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); -/******/ } -/******/ }; -/******/ -/******/ // define __esModule on exports -/******/ __webpack_require__.r = function(exports) { -/******/ Object.defineProperty(exports, '__esModule', { value: true }); -/******/ }; -/******/ -/******/ // 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 = "dist/"; -/******/ -/******/ -/******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 1); -/******/ }) +/******/ /************************************************************************/ -/******/ ([ -/* 0 */, -/* 1 */ -/*!*****************!*\ - !*** ./beta.js ***! - \*****************/ -/*! no static exports found */ -/***/ (function(module, exports) { +``` -module.exports = "beta"; +
-/***/ }) -/******/ ]); +``` js +/******/ +/******/ // startup +/******/ // Load entry module and return exports +/******/ // This entry module is referenced by other modules so it can't be inlined +/******/ let __webpack_exports__ = __webpack_require__(1); +/******/ +/******/ return __webpack_exports__; +/******/ })() +; }); ``` @@ -244,39 +195,43 @@ module.exports = "beta"; ## Unoptimized ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 - Asset Size Chunks Chunk Names -MyLibrary.alpha.js 3.16 KiB 0 [emitted] alpha - MyLibrary.beta.js 3.16 KiB 1 [emitted] beta -Entrypoint alpha = MyLibrary.alpha.js -Entrypoint beta = MyLibrary.beta.js -chunk {0} MyLibrary.alpha.js (alpha) 25 bytes [entry] [rendered] - > ./alpha alpha - [0] ./alpha.js 25 bytes {0} [built] - single entry ./alpha alpha -chunk {1} MyLibrary.beta.js (beta) 24 bytes [entry] [rendered] - > ./beta beta - [1] ./beta.js 24 bytes {1} [built] - single entry ./beta beta +asset MyLibrary.beta.js 2.07 KiB [emitted] (name: beta) +asset MyLibrary.alpha.js 2.06 KiB [emitted] (name: alpha) +chunk (runtime: alpha) MyLibrary.alpha.js (alpha) 25 bytes [entry] [rendered] + > ./alpha alpha + ./alpha.js 25 bytes [built] [code generated] + [used exports unknown] + cjs self exports reference ./alpha.js 1:0-14 + entry ./alpha alpha + used as library export +chunk (runtime: beta) MyLibrary.beta.js (beta) 24 bytes [entry] [rendered] + > ./beta beta + ./beta.js 24 bytes [built] [code generated] + [used exports unknown] + cjs self exports reference ./beta.js 1:0-14 + entry ./beta beta + used as library export +webpack X.X.X compiled successfully ``` ## Production mode ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 - Asset Size Chunks Chunk Names - MyLibrary.beta.js 828 bytes 0 [emitted] beta -MyLibrary.alpha.js 832 bytes 1 [emitted] alpha -Entrypoint alpha = MyLibrary.alpha.js -Entrypoint beta = MyLibrary.beta.js -chunk {0} MyLibrary.beta.js (beta) 24 bytes [entry] [rendered] - > ./beta beta - [0] ./beta.js 24 bytes {0} [built] - single entry ./beta beta -chunk {1} MyLibrary.alpha.js (alpha) 25 bytes [entry] [rendered] - > ./alpha alpha - [1] ./alpha.js 25 bytes {1} [built] - single entry ./alpha alpha +asset MyLibrary.alpha.js 434 bytes [emitted] [minimized] (name: alpha) +asset MyLibrary.beta.js 432 bytes [emitted] [minimized] (name: beta) +chunk (runtime: beta) MyLibrary.beta.js (beta) 24 bytes [entry] [rendered] + > ./beta beta + ./beta.js 24 bytes [built] [code generated] + [used exports unknown] + cjs self exports reference ./beta.js 1:0-14 + entry ./beta beta + used as library export +chunk (runtime: alpha) MyLibrary.alpha.js (alpha) 25 bytes [entry] [rendered] + > ./alpha alpha + ./alpha.js 25 bytes [built] [code generated] + [used exports unknown] + cjs self exports reference ./alpha.js 1:0-14 + entry ./alpha alpha + used as library export +webpack X.X.X compiled successfully ``` diff --git a/examples/multi-part-library/template.md b/examples/multi-part-library/template.md index 900b16dabf6..6237b338ecb 100644 --- a/examples/multi-part-library/template.md +++ b/examples/multi-part-library/template.md @@ -1,33 +1,33 @@ -This example demonstrates how to build a complex library with webpack. The library consist of multiple parts that are usable on its own and together. +This example demonstrates how to build a complex library with webpack. The library consists of multiple parts that are usable on its own and together. -When using this library with script tags it exports itself to the namespace `MyLibrary` and each part to a property in this namespace (`MyLibrary.alpha` and `MyLibrary.beta`). When consuming the library with CommonsJs or AMD it just export each part. +When using this library with script tags it exports itself to the namespace `MyLibrary` and each part to a property in this namespace (`MyLibrary.alpha` and `MyLibrary.beta`). When consuming the library with CommonsJS or AMD it just exports each part. -We are using multiple entry points (`entry` option) to build every part of the library as separate output file. The `output.filename` option contains `[name]` to give each output file a different name. +We are using multiple entry points (`entry` option) to build every part of the library as a separate output file. The `output.filename` option contains `[name]` to give each output file a different name. -We are using the `libraryTarget` option to generate a UMD ([Universal Module Definition](https://github.com/umdjs/umd)) module that is consumable in CommonsJs, AMD and with script tags. The `library` option defines the namespace. We are using `[name]` in the `library` option to give every entry a different namespace. +We are using the `libraryTarget` option to generate a UMD ([Universal Module Definition](https://github.com/umdjs/umd)) module that is consumable in CommonsJS, AMD and with script tags. The `library` option defines the namespace. We are using `[name]` in the `library` option to give every entry a different namespace. You can see that webpack automatically wraps your module so that it is consumable in every environment. All you need is this simple config. Note: You can also use the `library` and `libraryTarget` options without multiple entry points. Then you don't need `[name]`. -Note: When your library has dependencies that should not be included in the compiled version, you can use the `externals` option. See [externals example](https://github.com/webpack/webpack/tree/master/examples/externals). +Note: When your library has dependencies that should not be included in the compiled version, you can use the `externals` option. See [externals example](https://github.com/webpack/webpack/tree/main/examples/externals). # webpack.config.js -``` javascript -{{webpack.config.js}} +```javascript +_{{webpack.config.js}}_ ``` # dist/MyLibrary.alpha.js -``` javascript -{{dist/MyLibrary.alpha.js}} +```javascript +_{{dist/MyLibrary.alpha.js}}_ ``` # dist/MyLibrary.beta.js -``` javascript -{{dist/MyLibrary.beta.js}} +```javascript +_{{dist/MyLibrary.beta.js}}_ ``` # Info @@ -35,11 +35,11 @@ Note: When your library has dependencies that should not be included in the comp ## Unoptimized ``` -{{stdout}} +_{{stdout}}_ ``` ## Production mode ``` -{{production:stdout}} +_{{production:stdout}}_ ``` diff --git a/examples/multi-part-library/webpack.config.js b/examples/multi-part-library/webpack.config.js index f79be11fe71..75888588ac3 100644 --- a/examples/multi-part-library/webpack.config.js +++ b/examples/multi-part-library/webpack.config.js @@ -1,6 +1,10 @@ -var path = require("path"); -module.exports = { - // mode: "development || "production", +"use strict"; + +const path = require("path"); + +/** @type {import("webpack").Configuration} */ +const config = { + // mode: "development" || "production", entry: { alpha: "./alpha", beta: "./beta" @@ -12,3 +16,5 @@ module.exports = { libraryTarget: "umd" } }; + +module.exports = config; diff --git a/examples/multiple-entry-points/README.md b/examples/multiple-entry-points/README.md index a3c2f0b930d..3341f6bbbc2 100644 --- a/examples/multiple-entry-points/README.md +++ b/examples/multiple-entry-points/README.md @@ -1,6 +1,6 @@ This example shows how to use multiple entry points with a commons chunk. -In this example you have two (HTML) pages `pageA` and `pageB`. You want to create individual bundles for each page. In addition to this you want to create a shared bundle that contains all modules used in both pages (assuming there are many/big modules in common). The pages also use Code Splitting to load a less used part of the features on demand. +In this example, you have two (HTML) pages `pageA` and `pageB`. You want to create individual bundles for each page. In addition to this, you want to create a shared bundle that contains all the modules used in both pages (assuming there are many/big modules in common). The pages also use Code Splitting to load a less used part of the features on demand. You can see how to define multiple entry points via the `entry` option. @@ -8,29 +8,29 @@ You can use You can see the output files: -* `commons.js` contains: - * module `common.js` which is used in both pages -* `pageA.js` contains: (`pageB.js` is similar) - * the module system - * chunk loading logic - * the entry point `pageA.js` - * it would contain any other module that is only used by `pageA` -* `0.chunk.js` is an additional chunk which is used by both pages. It contains: - * module `shared.js` +- `commons.js` contains: + - module `common.js` which is used in both pages +- `pageA.js` contains: (`pageB.js` is similar) + - the module system + - chunk loading logic + - the entry point `pageA.js` + - it would contain any other module that is only used by `pageA` +- `406.js` is an additional chunk which is used by both pages. It contains: + - module `shared.js` You can also see the info that is printed to console. It shows among others: -* the generated files -* the chunks with file, name and id - * see lines starting with `chunk` -* the modules that are in the chunks -* the reasons why the modules are included -* the reasons why a chunk is created - * see lines starting with `>` +- the generated files +- the chunks with file, name, and id + - see lines starting with `chunk` +- the modules that are in the chunks +- the reasons why the modules are included +- the reasons why a chunk is created + - see lines starting with `>` # pageA.js -``` javascript +```javascript var common = require("./common"); require(["./shared"], function(shared) { shared("This is page A"); @@ -39,7 +39,7 @@ require(["./shared"], function(shared) { # pageB.js -``` javascript +```javascript var common = require("./common"); require.ensure(["./shared"], function(require) { var shared = require("./shared"); @@ -49,9 +49,12 @@ require.ensure(["./shared"], function(require) { # webpack.config.js -``` javascript -module.exports = { - // mode: "development || "production", +```javascript +"use strict"; + +/** @type {import("webpack").Configuration} */ +const config = { + // mode: "development" || "production", entry: { pageA: "./pageA", pageB: "./pageB" @@ -67,14 +70,16 @@ module.exports = { } } }, - occurrenceOrder: true // To keep filename consistent between different modes (for example building only) + chunkIds: "named" // To keep filename consistent between different modes (for example building only) } }; + +module.exports = config; ``` # pageA.html -``` html +```html @@ -86,15 +91,17 @@ module.exports = { # dist/commons.js -``` javascript -(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[1],[ +```javascript +(self["webpackChunk"] = self["webpackChunk"] || []).push([["commons"],[ /* 0 */, /* 1 */ /*!*******************!*\ !*** ./common.js ***! \*******************/ -/*! no static exports found */ -/***/ (function(module, exports) { +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: module */ +/*! CommonJS bailout: module.exports is used directly at 1:0-14 */ +/***/ ((module) => { module.exports = "Common"; @@ -104,480 +111,555 @@ module.exports = "Common"; # dist/pageA.js -
/******/ (function(modules) { /* webpackBootstrap */ }) - -``` javascript -/******/ (function(modules) { // webpackBootstrap -/******/ // install a JSONP callback for chunk loading -/******/ function webpackJsonpCallback(data) { -/******/ var chunkIds = data[0]; -/******/ var moreModules = data[1]; -/******/ var executeModules = data[2]; -/******/ // add "moreModules" to the modules object, -/******/ // then flag all "chunkIds" as loaded and fire callback -/******/ var moduleId, chunkId, i = 0, resolves = []; -/******/ for(;i < chunkIds.length; i++) { -/******/ chunkId = chunkIds[i]; -/******/ if(installedChunks[chunkId]) { -/******/ resolves.push(installedChunks[chunkId][0]); -/******/ } -/******/ installedChunks[chunkId] = 0; -/******/ } -/******/ for(moduleId in moreModules) { -/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { -/******/ modules[moduleId] = moreModules[moduleId]; -/******/ } -/******/ } -/******/ if(parentJsonpFunction) parentJsonpFunction(data); -/******/ while(resolves.length) { -/******/ resolves.shift()(); -/******/ } -/******/ -/******/ // add entry modules from loaded chunk to deferred list -/******/ deferredModules.push.apply(deferredModules, executeModules || []); -/******/ -/******/ // run deferred modules when all chunks ready -/******/ return checkDeferredModules(); -/******/ }; -/******/ function checkDeferredModules() { -/******/ var result; -/******/ for(var i = 0; i < deferredModules.length; i++) { -/******/ var deferredModule = deferredModules[i]; -/******/ var fulfilled = true; -/******/ for(var j = 1; j < deferredModule.length; j++) { -/******/ var depId = deferredModule[j]; -/******/ if(installedChunks[depId] !== 0) fulfilled = false; -/******/ } -/******/ if(fulfilled) { -/******/ deferredModules.splice(i--, 1); -/******/ result = __webpack_require__(__webpack_require__.s = deferredModule[0]); -/******/ } -/******/ } -/******/ return result; -/******/ } -/******/ +```javascript +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ([ +/* 0 */ +/*!******************!*\ + !*** ./pageA.js ***! + \******************/ +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: __webpack_require__, __webpack_require__.e, __webpack_require__.oe, __webpack_require__.* */ +/***/ ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => { + +var common = __webpack_require__(/*! ./common */ 1); +__webpack_require__.e(/*! AMD require */ "shared_js").then(function() { var __WEBPACK_AMD_REQUIRE_ARRAY__ = [__webpack_require__(/*! ./shared */ 3)]; (function(shared) { + shared("This is page A"); +}).apply(null, __WEBPACK_AMD_REQUIRE_ARRAY__);})['catch'](__webpack_require__.oe); + +/***/ }) +/******/ ]); +``` + +
/* webpack runtime code */ + +``` js +/************************************************************************/ /******/ // The module cache -/******/ var installedModules = {}; -/******/ -/******/ // object to store loaded and loading chunks -/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched -/******/ // Promise = chunk loading, 0 = chunk loaded -/******/ var installedChunks = { -/******/ 3: 0 -/******/ }; -/******/ -/******/ var deferredModules = []; -/******/ -/******/ // script path function -/******/ function jsonpScriptSrc(chunkId) { -/******/ return __webpack_require__.p + "" + ({}[chunkId]||chunkId) + ".js" -/******/ } -/******/ +/******/ const __webpack_module_cache__ = {}; +/******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { -/******/ /******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) { -/******/ return installedModules[moduleId].exports; +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, +/******/ const module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed /******/ exports: {} /******/ }; -/******/ +/******/ /******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } -/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = __webpack_modules__; +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/chunk loaded */ +/******/ (() => { +/******/ const deferred = []; +/******/ __webpack_require__.O = (result, chunkIds, fn, priority) => { +/******/ if(chunkIds) { +/******/ priority = priority || 0; +/******/ for(var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1]; +/******/ deferred[i] = [chunkIds, fn, priority]; +/******/ return; +/******/ } +/******/ let notFulfilled = Infinity; +/******/ for (var i = 0; i < deferred.length; i++) { +/******/ let [chunkIds, fn, priority] = deferred[i]; +/******/ let fulfilled = true; +/******/ for (var j = 0; j < chunkIds.length; j++) { +/******/ if ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every((key) => (__webpack_require__.O[key](chunkIds[j])))) { +/******/ chunkIds.splice(j--, 1); +/******/ } else { +/******/ fulfilled = false; +/******/ if(priority < notFulfilled) notFulfilled = priority; +/******/ } +/******/ } +/******/ if(fulfilled) { +/******/ deferred.splice(i--, 1) +/******/ const r = fn(); +/******/ if (r !== undefined) result = r; +/******/ } +/******/ } +/******/ return result; +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/ensure chunk */ +/******/ __webpack_require__.f = {}; /******/ // This file contains only the entry chunk. /******/ // The chunk loading function for additional chunks -/******/ __webpack_require__.e = function requireEnsure(chunkId) { -/******/ var promises = []; -/******/ -/******/ -/******/ // JSONP chunk loading for javascript -/******/ -/******/ var installedChunkData = installedChunks[chunkId]; -/******/ if(installedChunkData !== 0) { // 0 means "already installed". -/******/ -/******/ // a Promise means "currently loading". -/******/ if(installedChunkData) { -/******/ promises.push(installedChunkData[2]); -/******/ } else { -/******/ // setup Promise in chunk cache -/******/ var promise = new Promise(function(resolve, reject) { -/******/ installedChunkData = installedChunks[chunkId] = [resolve, reject]; -/******/ }); -/******/ promises.push(installedChunkData[2] = promise); -/******/ -/******/ // start chunk loading -/******/ var head = document.getElementsByTagName('head')[0]; -/******/ var script = document.createElement('script'); -/******/ +/******/ __webpack_require__.e = (chunkId) => { +/******/ return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => { +/******/ __webpack_require__.f[key](chunkId, promises); +/******/ return promises; +/******/ }, [])); +/******/ }; +/******/ +/******/ /* webpack/runtime/get javascript chunk filename */ +/******/ // This function allow to reference async chunks +/******/ __webpack_require__.u = (chunkId) => { +/******/ // return url for filenames based on template +/******/ return "" + chunkId + ".js"; +/******/ }; +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)); +/******/ +/******/ /* webpack/runtime/load script */ +/******/ (() => { +/******/ const inProgress = {}; +/******/ // data-webpack is not used as build has no uniqueName +/******/ // loadScript function to load a script via script tag +/******/ __webpack_require__.l = (url, done, key, chunkId) => { +/******/ if(inProgress[url]) { inProgress[url].push(done); return; } +/******/ let script, needAttach; +/******/ if(key !== undefined) { +/******/ const scripts = document.getElementsByTagName("script"); +/******/ for(var i = 0; i < scripts.length; i++) { +/******/ const s = scripts[i]; +/******/ if(s.getAttribute("src") == url) { script = s; break; } +/******/ } +/******/ } +/******/ if(!script) { +/******/ needAttach = true; +/******/ script = document.createElement('script'); +/******/ /******/ script.charset = 'utf-8'; -/******/ script.timeout = 120; -/******/ /******/ if (__webpack_require__.nc) { /******/ script.setAttribute("nonce", __webpack_require__.nc); /******/ } -/******/ script.src = jsonpScriptSrc(chunkId); -/******/ var timeout = setTimeout(function(){ -/******/ onScriptComplete({ type: 'timeout', target: script }); -/******/ }, 120000); -/******/ script.onerror = script.onload = onScriptComplete; -/******/ function onScriptComplete(event) { -/******/ // avoid mem leaks in IE. -/******/ script.onerror = script.onload = null; -/******/ clearTimeout(timeout); -/******/ var chunk = installedChunks[chunkId]; -/******/ if(chunk !== 0) { -/******/ if(chunk) { -/******/ var errorType = event && (event.type === 'load' ? 'missing' : event.type); -/******/ var realSrc = event && event.target && event.target.src; -/******/ var error = new Error('Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'); -/******/ error.type = errorType; -/******/ error.request = realSrc; -/******/ chunk[1](error); +/******/ +/******/ +/******/ script.src = url; +/******/ } +/******/ inProgress[url] = [done]; +/******/ const onScriptComplete = (prev, event) => { +/******/ // avoid mem leaks in IE. +/******/ script.onerror = script.onload = null; +/******/ clearTimeout(timeout); +/******/ const doneFns = inProgress[url]; +/******/ delete inProgress[url]; +/******/ script.parentNode?.removeChild(script); +/******/ doneFns?.forEach((fn) => (fn(event))); +/******/ if(prev) return prev(event); +/******/ } +/******/ const timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000); +/******/ script.onerror = onScriptComplete.bind(null, script.onerror); +/******/ script.onload = onScriptComplete.bind(null, script.onload); +/******/ needAttach && document.head.appendChild(script); +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/publicPath */ +/******/ __webpack_require__.p = "dist/"; +/******/ +/******/ /* webpack/runtime/jsonp chunk loading */ +/******/ (() => { +/******/ // no baseURI +/******/ +/******/ // object to store loaded and loading chunks +/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched +/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded +/******/ const installedChunks = { +/******/ "pageA": 0 +/******/ }; +/******/ +/******/ __webpack_require__.f.j = (chunkId, promises) => { +/******/ // JSONP chunk loading for javascript +/******/ let installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined; +/******/ if(installedChunkData !== 0) { // 0 means "already installed". +/******/ +/******/ // a Promise means "currently loading". +/******/ if(installedChunkData) { +/******/ promises.push(installedChunkData[2]); +/******/ } else { +/******/ if(true) { // all chunks have JS +/******/ // setup Promise in chunk cache +/******/ const promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject])); +/******/ promises.push(installedChunkData[2] = promise); +/******/ +/******/ // start chunk loading +/******/ const url = __webpack_require__.p + __webpack_require__.u(chunkId); +/******/ // create error before stack unwound to get useful stacktrace later +/******/ const error = new Error(); +/******/ const loadingEnded = (event) => { +/******/ if(__webpack_require__.o(installedChunks, chunkId)) { +/******/ installedChunkData = installedChunks[chunkId]; +/******/ if(installedChunkData !== 0) installedChunks[chunkId] = undefined; +/******/ if(installedChunkData) { +/******/ const errorType = event && (event.type === 'load' ? 'missing' : event.type); +/******/ const realSrc = event && event.target && event.target.src; +/******/ error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'; +/******/ error.name = 'ChunkLoadError'; +/******/ error.type = errorType; +/******/ error.request = realSrc; +/******/ error.event = event; +/******/ installedChunkData[1](error); +/******/ } +/******/ } +/******/ }; +/******/ __webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId); /******/ } -/******/ installedChunks[chunkId] = undefined; /******/ } -/******/ }; -/******/ head.appendChild(script); +/******/ } +/******/ }; +/******/ +/******/ // no prefetching +/******/ +/******/ // no preloaded +/******/ +/******/ // no HMR +/******/ +/******/ // no HMR manifest +/******/ +/******/ __webpack_require__.O.j = (chunkId) => (installedChunks[chunkId] === 0); +/******/ +/******/ // install a JSONP callback for chunk loading +/******/ const webpackJsonpCallback = (parentChunkLoadingFunction, data) => { +/******/ let [chunkIds, moreModules, runtime] = data; +/******/ // add "moreModules" to the modules object, +/******/ // then flag all "chunkIds" as loaded and fire callback +/******/ var moduleId, chunkId, i = 0; +/******/ if(chunkIds.some((id) => (installedChunks[id] !== 0))) { +/******/ for(moduleId in moreModules) { +/******/ if(__webpack_require__.o(moreModules, moduleId)) { +/******/ __webpack_require__.m[moduleId] = moreModules[moduleId]; +/******/ } +/******/ } +/******/ if(runtime) var result = runtime(__webpack_require__); /******/ } +/******/ if(parentChunkLoadingFunction) parentChunkLoadingFunction(data); +/******/ for(;i < chunkIds.length; i++) { +/******/ chunkId = chunkIds[i]; +/******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) { +/******/ installedChunks[chunkId][0](); +/******/ } +/******/ installedChunks[chunkId] = 0; +/******/ } +/******/ return __webpack_require__.O(result); /******/ } -/******/ return Promise.all(promises); -/******/ }; -/******/ -/******/ // 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, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); -/******/ } -/******/ }; -/******/ -/******/ // define __esModule on exports -/******/ __webpack_require__.r = function(exports) { -/******/ Object.defineProperty(exports, '__esModule', { value: true }); -/******/ }; -/******/ -/******/ // 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 = "dist/"; -/******/ -/******/ // on error function for async loading -/******/ __webpack_require__.oe = function(err) { console.error(err); throw err; }; -/******/ -/******/ var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || []; -/******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); -/******/ jsonpArray.push = webpackJsonpCallback; -/******/ jsonpArray = jsonpArray.slice(); -/******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]); -/******/ var parentJsonpFunction = oldJsonpFunction; -/******/ -/******/ -/******/ // add entry module to deferred list -/******/ deferredModules.push([3,1]); -/******/ // run deferred modules when ready -/******/ return checkDeferredModules(); -/******/ }) +/******/ +/******/ const chunkLoadingGlobal = self["webpackChunk"] = self["webpackChunk"] || []; +/******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0)); +/******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal)); +/******/ })(); +/******/ /************************************************************************/ ```
-``` javascript -/******/ ({ +``` js +/******/ +/******/ // startup +/******/ // Load entry module and return exports +/******/ // This entry module depends on other loaded chunks and execution need to be delayed +/******/ let __webpack_exports__ = __webpack_require__.O(undefined, ["commons"], () => (__webpack_require__(0))) +/******/ __webpack_exports__ = __webpack_require__.O(__webpack_exports__); +/******/ +/******/ })() +; +``` + +# dist/pageB.js -/***/ 3: +```javascript +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ({ + +/***/ 2 /*!******************!*\ - !*** ./pageA.js ***! + !*** ./pageB.js ***! \******************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: __webpack_require__, __webpack_require__.e, __webpack_require__.* */ +(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { var common = __webpack_require__(/*! ./common */ 1); -__webpack_require__.e(/*! AMD require */ 0).then(function() { var __WEBPACK_AMD_REQUIRE_ARRAY__ = [__webpack_require__(/*! ./shared */ 0)]; (function(shared) { - shared("This is page A"); -}).apply(null, __WEBPACK_AMD_REQUIRE_ARRAY__);}).catch(__webpack_require__.oe); +__webpack_require__.e(/*! require.ensure */ "shared_js").then((function(require) { + var shared = __webpack_require__(/*! ./shared */ 3); + shared("This is page B"); +}).bind(null, __webpack_require__))['catch'](__webpack_require__.oe); -/***/ }) +/***/ } -/******/ }); +/******/ }); ``` -# dist/pageB.js +
/* webpack runtime code */ -``` javascript -/******/ (function(modules) { // webpackBootstrap -/******/ // install a JSONP callback for chunk loading -/******/ function webpackJsonpCallback(data) { -/******/ var chunkIds = data[0]; -/******/ var moreModules = data[1]; -/******/ var executeModules = data[2]; -/******/ // add "moreModules" to the modules object, -/******/ // then flag all "chunkIds" as loaded and fire callback -/******/ var moduleId, chunkId, i = 0, resolves = []; -/******/ for(;i < chunkIds.length; i++) { -/******/ chunkId = chunkIds[i]; -/******/ if(installedChunks[chunkId]) { -/******/ resolves.push(installedChunks[chunkId][0]); -/******/ } -/******/ installedChunks[chunkId] = 0; -/******/ } -/******/ for(moduleId in moreModules) { -/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { -/******/ modules[moduleId] = moreModules[moduleId]; -/******/ } -/******/ } -/******/ if(parentJsonpFunction) parentJsonpFunction(data); -/******/ while(resolves.length) { -/******/ resolves.shift()(); -/******/ } -/******/ -/******/ // add entry modules from loaded chunk to deferred list -/******/ deferredModules.push.apply(deferredModules, executeModules || []); -/******/ -/******/ // run deferred modules when all chunks ready -/******/ return checkDeferredModules(); -/******/ }; -/******/ function checkDeferredModules() { -/******/ var result; -/******/ for(var i = 0; i < deferredModules.length; i++) { -/******/ var deferredModule = deferredModules[i]; -/******/ var fulfilled = true; -/******/ for(var j = 1; j < deferredModule.length; j++) { -/******/ var depId = deferredModule[j]; -/******/ if(installedChunks[depId] !== 0) fulfilled = false; -/******/ } -/******/ if(fulfilled) { -/******/ deferredModules.splice(i--, 1); -/******/ result = __webpack_require__(__webpack_require__.s = deferredModule[0]); -/******/ } -/******/ } -/******/ return result; -/******/ } -/******/ +``` js +/************************************************************************/ /******/ // The module cache -/******/ var installedModules = {}; -/******/ -/******/ // object to store loaded and loading chunks -/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched -/******/ // Promise = chunk loading, 0 = chunk loaded -/******/ var installedChunks = { -/******/ 2: 0 -/******/ }; -/******/ -/******/ var deferredModules = []; -/******/ -/******/ // script path function -/******/ function jsonpScriptSrc(chunkId) { -/******/ return __webpack_require__.p + "" + ({}[chunkId]||chunkId) + ".js" -/******/ } -/******/ +/******/ const __webpack_module_cache__ = {}; +/******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { -/******/ /******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) { -/******/ return installedModules[moduleId].exports; +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, +/******/ const module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed /******/ exports: {} /******/ }; -/******/ +/******/ /******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } -/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = __webpack_modules__; +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/chunk loaded */ +/******/ (() => { +/******/ const deferred = []; +/******/ __webpack_require__.O = (result, chunkIds, fn, priority) => { +/******/ if(chunkIds) { +/******/ priority = priority || 0; +/******/ for(var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1]; +/******/ deferred[i] = [chunkIds, fn, priority]; +/******/ return; +/******/ } +/******/ let notFulfilled = Infinity; +/******/ for (var i = 0; i < deferred.length; i++) { +/******/ let [chunkIds, fn, priority] = deferred[i]; +/******/ let fulfilled = true; +/******/ for (var j = 0; j < chunkIds.length; j++) { +/******/ if ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every((key) => (__webpack_require__.O[key](chunkIds[j])))) { +/******/ chunkIds.splice(j--, 1); +/******/ } else { +/******/ fulfilled = false; +/******/ if(priority < notFulfilled) notFulfilled = priority; +/******/ } +/******/ } +/******/ if(fulfilled) { +/******/ deferred.splice(i--, 1) +/******/ const r = fn(); +/******/ if (r !== undefined) result = r; +/******/ } +/******/ } +/******/ return result; +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/ensure chunk */ +/******/ __webpack_require__.f = {}; /******/ // This file contains only the entry chunk. /******/ // The chunk loading function for additional chunks -/******/ __webpack_require__.e = function requireEnsure(chunkId) { -/******/ var promises = []; -/******/ -/******/ -/******/ // JSONP chunk loading for javascript -/******/ -/******/ var installedChunkData = installedChunks[chunkId]; -/******/ if(installedChunkData !== 0) { // 0 means "already installed". -/******/ -/******/ // a Promise means "currently loading". -/******/ if(installedChunkData) { -/******/ promises.push(installedChunkData[2]); -/******/ } else { -/******/ // setup Promise in chunk cache -/******/ var promise = new Promise(function(resolve, reject) { -/******/ installedChunkData = installedChunks[chunkId] = [resolve, reject]; -/******/ }); -/******/ promises.push(installedChunkData[2] = promise); -/******/ -/******/ // start chunk loading -/******/ var head = document.getElementsByTagName('head')[0]; -/******/ var script = document.createElement('script'); -/******/ +/******/ __webpack_require__.e = (chunkId) => { +/******/ return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => { +/******/ __webpack_require__.f[key](chunkId, promises); +/******/ return promises; +/******/ }, [])); +/******/ }; +/******/ +/******/ /* webpack/runtime/get javascript chunk filename */ +/******/ // This function allow to reference async chunks +/******/ __webpack_require__.u = (chunkId) => { +/******/ // return url for filenames based on template +/******/ return "" + chunkId + ".js"; +/******/ }; +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)); +/******/ +/******/ /* webpack/runtime/load script */ +/******/ (() => { +/******/ const inProgress = {}; +/******/ // data-webpack is not used as build has no uniqueName +/******/ // loadScript function to load a script via script tag +/******/ __webpack_require__.l = (url, done, key, chunkId) => { +/******/ if(inProgress[url]) { inProgress[url].push(done); return; } +/******/ let script, needAttach; +/******/ if(key !== undefined) { +/******/ const scripts = document.getElementsByTagName("script"); +/******/ for(var i = 0; i < scripts.length; i++) { +/******/ const s = scripts[i]; +/******/ if(s.getAttribute("src") == url) { script = s; break; } +/******/ } +/******/ } +/******/ if(!script) { +/******/ needAttach = true; +/******/ script = document.createElement('script'); +/******/ /******/ script.charset = 'utf-8'; -/******/ script.timeout = 120; -/******/ /******/ if (__webpack_require__.nc) { /******/ script.setAttribute("nonce", __webpack_require__.nc); /******/ } -/******/ script.src = jsonpScriptSrc(chunkId); -/******/ var timeout = setTimeout(function(){ -/******/ onScriptComplete({ type: 'timeout', target: script }); -/******/ }, 120000); -/******/ script.onerror = script.onload = onScriptComplete; -/******/ function onScriptComplete(event) { -/******/ // avoid mem leaks in IE. -/******/ script.onerror = script.onload = null; -/******/ clearTimeout(timeout); -/******/ var chunk = installedChunks[chunkId]; -/******/ if(chunk !== 0) { -/******/ if(chunk) { -/******/ var errorType = event && (event.type === 'load' ? 'missing' : event.type); -/******/ var realSrc = event && event.target && event.target.src; -/******/ var error = new Error('Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'); -/******/ error.type = errorType; -/******/ error.request = realSrc; -/******/ chunk[1](error); +/******/ +/******/ +/******/ script.src = url; +/******/ } +/******/ inProgress[url] = [done]; +/******/ const onScriptComplete = (prev, event) => { +/******/ // avoid mem leaks in IE. +/******/ script.onerror = script.onload = null; +/******/ clearTimeout(timeout); +/******/ const doneFns = inProgress[url]; +/******/ delete inProgress[url]; +/******/ script.parentNode?.removeChild(script); +/******/ doneFns?.forEach((fn) => (fn(event))); +/******/ if(prev) return prev(event); +/******/ } +/******/ const timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000); +/******/ script.onerror = onScriptComplete.bind(null, script.onerror); +/******/ script.onload = onScriptComplete.bind(null, script.onload); +/******/ needAttach && document.head.appendChild(script); +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/publicPath */ +/******/ __webpack_require__.p = "dist/"; +/******/ +/******/ /* webpack/runtime/jsonp chunk loading */ +/******/ (() => { +/******/ // no baseURI +/******/ +/******/ // object to store loaded and loading chunks +/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched +/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded +/******/ const installedChunks = { +/******/ "pageB": 0 +/******/ }; +/******/ +/******/ __webpack_require__.f.j = (chunkId, promises) => { +/******/ // JSONP chunk loading for javascript +/******/ let installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined; +/******/ if(installedChunkData !== 0) { // 0 means "already installed". +/******/ +/******/ // a Promise means "currently loading". +/******/ if(installedChunkData) { +/******/ promises.push(installedChunkData[2]); +/******/ } else { +/******/ if(true) { // all chunks have JS +/******/ // setup Promise in chunk cache +/******/ const promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject])); +/******/ promises.push(installedChunkData[2] = promise); +/******/ +/******/ // start chunk loading +/******/ const url = __webpack_require__.p + __webpack_require__.u(chunkId); +/******/ // create error before stack unwound to get useful stacktrace later +/******/ const error = new Error(); +/******/ const loadingEnded = (event) => { +/******/ if(__webpack_require__.o(installedChunks, chunkId)) { +/******/ installedChunkData = installedChunks[chunkId]; +/******/ if(installedChunkData !== 0) installedChunks[chunkId] = undefined; +/******/ if(installedChunkData) { +/******/ const errorType = event && (event.type === 'load' ? 'missing' : event.type); +/******/ const realSrc = event && event.target && event.target.src; +/******/ error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'; +/******/ error.name = 'ChunkLoadError'; +/******/ error.type = errorType; +/******/ error.request = realSrc; +/******/ error.event = event; +/******/ installedChunkData[1](error); +/******/ } +/******/ } +/******/ }; +/******/ __webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId); /******/ } -/******/ installedChunks[chunkId] = undefined; /******/ } -/******/ }; -/******/ head.appendChild(script); +/******/ } +/******/ }; +/******/ +/******/ // no prefetching +/******/ +/******/ // no preloaded +/******/ +/******/ // no HMR +/******/ +/******/ // no HMR manifest +/******/ +/******/ __webpack_require__.O.j = (chunkId) => (installedChunks[chunkId] === 0); +/******/ +/******/ // install a JSONP callback for chunk loading +/******/ const webpackJsonpCallback = (parentChunkLoadingFunction, data) => { +/******/ let [chunkIds, moreModules, runtime] = data; +/******/ // add "moreModules" to the modules object, +/******/ // then flag all "chunkIds" as loaded and fire callback +/******/ var moduleId, chunkId, i = 0; +/******/ if(chunkIds.some((id) => (installedChunks[id] !== 0))) { +/******/ for(moduleId in moreModules) { +/******/ if(__webpack_require__.o(moreModules, moduleId)) { +/******/ __webpack_require__.m[moduleId] = moreModules[moduleId]; +/******/ } +/******/ } +/******/ if(runtime) var result = runtime(__webpack_require__); /******/ } +/******/ if(parentChunkLoadingFunction) parentChunkLoadingFunction(data); +/******/ for(;i < chunkIds.length; i++) { +/******/ chunkId = chunkIds[i]; +/******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) { +/******/ installedChunks[chunkId][0](); +/******/ } +/******/ installedChunks[chunkId] = 0; +/******/ } +/******/ return __webpack_require__.O(result); /******/ } -/******/ return Promise.all(promises); -/******/ }; -/******/ -/******/ // 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, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); -/******/ } -/******/ }; -/******/ -/******/ // define __esModule on exports -/******/ __webpack_require__.r = function(exports) { -/******/ Object.defineProperty(exports, '__esModule', { value: true }); -/******/ }; -/******/ -/******/ // 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 = "dist/"; -/******/ -/******/ // on error function for async loading -/******/ __webpack_require__.oe = function(err) { console.error(err); throw err; }; -/******/ -/******/ var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || []; -/******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); -/******/ jsonpArray.push = webpackJsonpCallback; -/******/ jsonpArray = jsonpArray.slice(); -/******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]); -/******/ var parentJsonpFunction = oldJsonpFunction; -/******/ -/******/ -/******/ // add entry module to deferred list -/******/ deferredModules.push([2,1]); -/******/ // run deferred modules when ready -/******/ return checkDeferredModules(); -/******/ }) +/******/ +/******/ const chunkLoadingGlobal = self["webpackChunk"] = self["webpackChunk"] || []; +/******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0)); +/******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal)); +/******/ })(); +/******/ /************************************************************************/ -/******/ ({ - -/***/ 2: -/*!******************!*\ - !*** ./pageB.js ***! - \******************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -var common = __webpack_require__(/*! ./common */ 1); -__webpack_require__.e(/*! require.ensure */ 0).then((function(require) { - var shared = __webpack_require__(/*! ./shared */ 0); - shared("This is page B"); -}).bind(null, __webpack_require__)).catch(__webpack_require__.oe); +``` -/***/ }) +
-/******/ }); +``` js +/******/ +/******/ // startup +/******/ // Load entry module and return exports +/******/ // This entry module depends on other loaded chunks and execution need to be delayed +/******/ let __webpack_exports__ = __webpack_require__.O(undefined, ["commons"], () => (__webpack_require__(2))) +/******/ __webpack_exports__ = __webpack_require__.O(__webpack_exports__); +/******/ +/******/ })() +; ``` -# dist/0.js +# dist/shared_js.js -``` javascript -(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0],[ -/* 0 */ +```javascript +(self["webpackChunk"] = self["webpackChunk"] || []).push([["shared_js"],{ + +/***/ 3 /*!*******************!*\ !*** ./shared.js ***! \*******************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: module, __webpack_require__ */ +/*! CommonJS bailout: module.exports is used directly at 2:0-14 */ +(module, __unused_webpack_exports, __webpack_require__) { var common = __webpack_require__(/*! ./common */ 1); module.exports = function(msg) { console.log(msg); }; -/***/ }) -]]); +/***/ } + +}]); ``` # Info @@ -585,71 +667,85 @@ module.exports = function(msg) { ## Unoptimized ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 - Asset Size Chunks Chunk Names - 0.js 363 bytes 0 [emitted] -commons.js 267 bytes 1 [emitted] commons - pageB.js 8.28 KiB 2 [emitted] pageB - pageA.js 8.32 KiB 3 [emitted] pageA -Entrypoint pageA = commons.js pageA.js -Entrypoint pageB = commons.js pageB.js -chunk {0} 0.js 91 bytes <{1}> <{2}> <{3}> [rendered] - > [2] ./pageB.js 2:0-5:2 - > ./shared [3] ./pageA.js 2:0-4:2 - [0] ./shared.js 91 bytes {0} [built] - require.ensure item ./shared [2] ./pageB.js 2:0-5:2 - cjs require ./shared [2] ./pageB.js 3:14-33 - amd require ./shared [3] ./pageA.js 2:0-4:2 -chunk {1} commons.js (commons) 26 bytes ={2}= ={3}= >{0}< [initial] [rendered] split chunk (cache group: commons) (name: commons) - > ./pageA pageA - > ./pageB pageB - [1] ./common.js 26 bytes {1} [built] - cjs require ./common [0] ./shared.js 1:13-32 - cjs require ./common [2] ./pageB.js 1:13-32 - cjs require ./common [3] ./pageA.js 1:13-32 -chunk {2} pageB.js (pageB) 152 bytes ={1}= >{0}< [entry] [rendered] - > ./pageB pageB - [2] ./pageB.js 152 bytes {2} [built] - single entry ./pageB pageB -chunk {3} pageA.js (pageA) 108 bytes ={1}= >{0}< [entry] [rendered] - > ./pageA pageA - [3] ./pageA.js 108 bytes {3} [built] - single entry ./pageA pageA +asset pageA.js 10.6 KiB [emitted] (name: pageA) +asset pageB.js 10.5 KiB [emitted] (name: pageB) +asset shared_js.js 503 bytes [emitted] +asset commons.js 370 bytes [emitted] (name: commons) (id hint: commons) +Entrypoint pageA 10.9 KiB = commons.js 370 bytes pageA.js 10.6 KiB +Entrypoint pageB 10.9 KiB = commons.js 370 bytes pageB.js 10.5 KiB +chunk (runtime: pageA, pageB) commons.js (commons) (id hint: commons) 26 bytes [initial] [rendered] split chunk (cache group: commons) (name: commons) + > ./pageA pageA + > ./pageB pageB + ./common.js 26 bytes [built] [code generated] + [used exports unknown] + cjs self exports reference ./common.js 1:0-14 + cjs require ./common ./pageA.js 1:13-32 + cjs require ./common ./pageB.js 1:13-32 + cjs require ./common ./shared.js 1:13-32 +chunk (runtime: pageA) pageA.js (pageA) 105 bytes (javascript) 5.89 KiB (runtime) [entry] [rendered] + > ./pageA pageA + runtime modules 5.89 KiB 7 modules + ./pageA.js 105 bytes [built] [code generated] + [used exports unknown] + entry ./pageA pageA +chunk (runtime: pageB) pageB.js (pageB) 148 bytes (javascript) 5.89 KiB (runtime) [entry] [rendered] + > ./pageB pageB + runtime modules 5.89 KiB 7 modules + ./pageB.js 148 bytes [built] [code generated] + [used exports unknown] + entry ./pageB pageB +chunk (runtime: pageA, pageB) shared_js.js 88 bytes [rendered] + > ./shared ./pageA.js 2:0-4:2 + > ./pageB.js 2:0-5:2 + ./shared.js 88 bytes [built] [code generated] + [used exports unknown] + from origin ./pageB.js + require.ensure item ./shared ./pageB.js 2:0-5:2 + cjs require ./shared ./pageB.js 3:14-33 + amd require ./shared ./pageA.js 2:0-4:2 + cjs self exports reference ./shared.js 2:0-14 +webpack X.X.X compiled successfully ``` ## Production mode ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 - Asset Size Chunks Chunk Names - 0.js 120 bytes 0 [emitted] -commons.js 95 bytes 1 [emitted] commons - pageB.js 1.88 KiB 2 [emitted] pageB - pageA.js 1.91 KiB 3 [emitted] pageA -Entrypoint pageA = commons.js pageA.js -Entrypoint pageB = commons.js pageB.js -chunk {0} 0.js 91 bytes <{1}> <{2}> <{3}> [rendered] - > [2] ./pageB.js 2:0-5:2 - > ./shared [3] ./pageA.js 2:0-4:2 - [0] ./shared.js 91 bytes {0} [built] - require.ensure item ./shared [2] ./pageB.js 2:0-5:2 - cjs require ./shared [2] ./pageB.js 3:14-33 - amd require ./shared [3] ./pageA.js 2:0-4:2 -chunk {1} commons.js (commons) 26 bytes ={2}= ={3}= >{0}< [initial] [rendered] split chunk (cache group: commons) (name: commons) - > ./pageA pageA - > ./pageB pageB - [1] ./common.js 26 bytes {1} [built] - cjs require ./common [0] ./shared.js 1:13-32 - cjs require ./common [2] ./pageB.js 1:13-32 - cjs require ./common [3] ./pageA.js 1:13-32 -chunk {2} pageB.js (pageB) 152 bytes ={1}= >{0}< [entry] [rendered] - > ./pageB pageB - [2] ./pageB.js 152 bytes {2} [built] - single entry ./pageB pageB -chunk {3} pageA.js (pageA) 108 bytes ={1}= >{0}< [entry] [rendered] - > ./pageA pageA - [3] ./pageA.js 108 bytes {3} [built] - single entry ./pageA pageA +asset pageA.js 2.22 KiB [emitted] [minimized] (name: pageA) +asset pageB.js 2.2 KiB [emitted] [minimized] (name: pageB) +asset shared_js.js 122 bytes [emitted] [minimized] +asset commons.js 91 bytes [emitted] [minimized] (name: commons) (id hint: commons) +Entrypoint pageA 2.31 KiB = commons.js 91 bytes pageA.js 2.22 KiB +Entrypoint pageB 2.29 KiB = commons.js 91 bytes pageB.js 2.2 KiB +chunk (runtime: pageA, pageB) commons.js (commons) (id hint: commons) 26 bytes [initial] [rendered] split chunk (cache group: commons) (name: commons) + > ./pageA pageA + > ./pageB pageB + ./common.js 26 bytes [built] [code generated] + [used exports unknown] + cjs self exports reference ./common.js 1:0-14 + cjs require ./common ./pageA.js 1:13-32 + cjs require ./common ./pageB.js 1:13-32 + cjs require ./common ./shared.js 1:13-32 +chunk (runtime: pageA) pageA.js (pageA) 105 bytes (javascript) 5.89 KiB (runtime) [entry] [rendered] + > ./pageA pageA + runtime modules 5.89 KiB 7 modules + ./pageA.js 105 bytes [built] [code generated] + [no exports used] + entry ./pageA pageA +chunk (runtime: pageB) pageB.js (pageB) 148 bytes (javascript) 5.89 KiB (runtime) [entry] [rendered] + > ./pageB pageB + runtime modules 5.89 KiB 7 modules + ./pageB.js 148 bytes [built] [code generated] + [no exports used] + entry ./pageB pageB +chunk (runtime: pageA, pageB) shared_js.js 88 bytes [rendered] + > ./shared ./pageA.js 2:0-4:2 + > ./pageB.js 2:0-5:2 + ./shared.js 88 bytes [built] [code generated] + [used exports unknown] + from origin ./pageB.js + require.ensure item ./shared ./pageB.js 2:0-5:2 + cjs require ./shared ./pageB.js 3:14-33 + amd require ./shared ./pageA.js 2:0-4:2 + cjs self exports reference ./shared.js 2:0-14 +webpack X.X.X compiled successfully ``` diff --git a/examples/multiple-entry-points/template.md b/examples/multiple-entry-points/template.md index 4b0ea594b32..2bfea3457a7 100644 --- a/examples/multiple-entry-points/template.md +++ b/examples/multiple-entry-points/template.md @@ -1,6 +1,6 @@ This example shows how to use multiple entry points with a commons chunk. -In this example you have two (HTML) pages `pageA` and `pageB`. You want to create individual bundles for each page. In addition to this you want to create a shared bundle that contains all modules used in both pages (assuming there are many/big modules in common). The pages also use Code Splitting to load a less used part of the features on demand. +In this example, you have two (HTML) pages `pageA` and `pageB`. You want to create individual bundles for each page. In addition to this, you want to create a shared bundle that contains all the modules used in both pages (assuming there are many/big modules in common). The pages also use Code Splitting to load a less used part of the features on demand. You can see how to define multiple entry points via the `entry` option. @@ -8,72 +8,72 @@ You can use You can see the output files: -* `commons.js` contains: - * module `common.js` which is used in both pages -* `pageA.js` contains: (`pageB.js` is similar) - * the module system - * chunk loading logic - * the entry point `pageA.js` - * it would contain any other module that is only used by `pageA` -* `0.chunk.js` is an additional chunk which is used by both pages. It contains: - * module `shared.js` +- `commons.js` contains: + - module `common.js` which is used in both pages +- `pageA.js` contains: (`pageB.js` is similar) + - the module system + - chunk loading logic + - the entry point `pageA.js` + - it would contain any other module that is only used by `pageA` +- `406.js` is an additional chunk which is used by both pages. It contains: + - module `shared.js` You can also see the info that is printed to console. It shows among others: -* the generated files -* the chunks with file, name and id - * see lines starting with `chunk` -* the modules that are in the chunks -* the reasons why the modules are included -* the reasons why a chunk is created - * see lines starting with `>` +- the generated files +- the chunks with file, name, and id + - see lines starting with `chunk` +- the modules that are in the chunks +- the reasons why the modules are included +- the reasons why a chunk is created + - see lines starting with `>` # pageA.js -``` javascript -{{pageA.js}} +```javascript +_{{pageA.js}}_ ``` # pageB.js -``` javascript -{{pageB.js}} +```javascript +_{{pageB.js}}_ ``` # webpack.config.js -``` javascript -{{webpack.config.js}} +```javascript +_{{webpack.config.js}}_ ``` # pageA.html -``` html -{{pageA.html}} +```html +_{{pageA.html}}_ ``` # dist/commons.js -``` javascript -{{dist/commons.js}} +```javascript +_{{dist/commons.js}}_ ``` # dist/pageA.js -``` javascript -{{dist/pageA.js}} +```javascript +_{{dist/pageA.js}}_ ``` # dist/pageB.js -``` javascript -{{dist/pageB.js}} +```javascript +_{{dist/pageB.js}}_ ``` -# dist/0.js +# dist/shared_js.js -``` javascript -{{dist/0.js}} +```javascript +_{{dist/shared_js.js}}_ ``` # Info @@ -81,11 +81,11 @@ You can also see the info that is printed to console. It shows among others: ## Unoptimized ``` -{{stdout}} +_{{stdout}}_ ``` ## Production mode ``` -{{production:stdout}} +_{{production:stdout}}_ ``` diff --git a/examples/multiple-entry-points/webpack.config.js b/examples/multiple-entry-points/webpack.config.js index 9927b2f0f57..260b57c688f 100644 --- a/examples/multiple-entry-points/webpack.config.js +++ b/examples/multiple-entry-points/webpack.config.js @@ -1,5 +1,8 @@ -module.exports = { - // mode: "development || "production", +"use strict"; + +/** @type {import("webpack").Configuration} */ +const config = { + // mode: "development" || "production", entry: { pageA: "./pageA", pageB: "./pageB" @@ -15,6 +18,8 @@ module.exports = { } } }, - occurrenceOrder: true // To keep filename consistent between different modes (for example building only) + chunkIds: "named" // To keep filename consistent between different modes (for example building only) } }; + +module.exports = config; diff --git a/examples/named-chunks/README.md b/examples/named-chunks/README.md index 07017fd847b..f45cecd7a39 100644 --- a/examples/named-chunks/README.md +++ b/examples/named-chunks/README.md @@ -1,6 +1,6 @@ # example.js -``` javascript +```javascript var a = require("a"); require.ensure(["b"], function(require) { @@ -23,264 +23,293 @@ require.ensure(["b"], function(require) { }); ``` - # dist/output.js -
/******/ (function(modules) { /* webpackBootstrap */ }) - -``` javascript -/******/ (function(modules) { // webpackBootstrap -/******/ // install a JSONP callback for chunk loading -/******/ function webpackJsonpCallback(data) { -/******/ var chunkIds = data[0]; -/******/ var moreModules = data[1]; -/******/ -/******/ // add "moreModules" to the modules object, -/******/ // then flag all "chunkIds" as loaded and fire callback -/******/ var moduleId, chunkId, i = 0, resolves = []; -/******/ for(;i < chunkIds.length; i++) { -/******/ chunkId = chunkIds[i]; -/******/ if(installedChunks[chunkId]) { -/******/ resolves.push(installedChunks[chunkId][0]); -/******/ } -/******/ installedChunks[chunkId] = 0; -/******/ } -/******/ for(moduleId in moreModules) { -/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { -/******/ modules[moduleId] = moreModules[moduleId]; -/******/ } -/******/ } -/******/ if(parentJsonpFunction) parentJsonpFunction(data); -/******/ while(resolves.length) { -/******/ resolves.shift()(); -/******/ } -/******/ -/******/ }; -/******/ -/******/ +```javascript +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ([ +/* 0 */, +/* 1 */ +/*!***************************!*\ + !*** ./node_modules/a.js ***! + \***************************/ +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: */ +/***/ (() => { + +// module a + +/***/ }) +/******/ ]); +``` + +
/* webpack runtime code */ + +``` js +/************************************************************************/ /******/ // The module cache -/******/ var installedModules = {}; -/******/ -/******/ // object to store loaded and loading chunks -/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched -/******/ // Promise = chunk loading, 0 = chunk loaded -/******/ var installedChunks = { -/******/ 2: 0 -/******/ }; -/******/ -/******/ -/******/ -/******/ // script path function -/******/ function jsonpScriptSrc(chunkId) { -/******/ return __webpack_require__.p + "" + chunkId + ".output.js" -/******/ } -/******/ +/******/ const __webpack_module_cache__ = {}; +/******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { -/******/ /******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) { -/******/ return installedModules[moduleId].exports; +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, +/******/ const module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed /******/ exports: {} /******/ }; -/******/ +/******/ /******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } -/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = __webpack_modules__; +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/ensure chunk */ +/******/ __webpack_require__.f = {}; /******/ // This file contains only the entry chunk. /******/ // The chunk loading function for additional chunks -/******/ __webpack_require__.e = function requireEnsure(chunkId) { -/******/ var promises = []; -/******/ -/******/ -/******/ // JSONP chunk loading for javascript -/******/ -/******/ var installedChunkData = installedChunks[chunkId]; -/******/ if(installedChunkData !== 0) { // 0 means "already installed". -/******/ -/******/ // a Promise means "currently loading". -/******/ if(installedChunkData) { -/******/ promises.push(installedChunkData[2]); -/******/ } else { -/******/ // setup Promise in chunk cache -/******/ var promise = new Promise(function(resolve, reject) { -/******/ installedChunkData = installedChunks[chunkId] = [resolve, reject]; -/******/ }); -/******/ promises.push(installedChunkData[2] = promise); -/******/ -/******/ // start chunk loading -/******/ var head = document.getElementsByTagName('head')[0]; -/******/ var script = document.createElement('script'); -/******/ +/******/ __webpack_require__.e = (chunkId) => { +/******/ return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => { +/******/ __webpack_require__.f[key](chunkId, promises); +/******/ return promises; +/******/ }, [])); +/******/ }; +/******/ +/******/ /* webpack/runtime/get javascript chunk filename */ +/******/ // This function allow to reference async chunks +/******/ __webpack_require__.u = (chunkId) => { +/******/ // return url for filenames based on template +/******/ return "" + chunkId + ".output.js"; +/******/ }; +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)); +/******/ +/******/ /* webpack/runtime/load script */ +/******/ (() => { +/******/ const inProgress = {}; +/******/ // data-webpack is not used as build has no uniqueName +/******/ // loadScript function to load a script via script tag +/******/ __webpack_require__.l = (url, done, key, chunkId) => { +/******/ if(inProgress[url]) { inProgress[url].push(done); return; } +/******/ let script, needAttach; +/******/ if(key !== undefined) { +/******/ const scripts = document.getElementsByTagName("script"); +/******/ for(var i = 0; i < scripts.length; i++) { +/******/ const s = scripts[i]; +/******/ if(s.getAttribute("src") == url) { script = s; break; } +/******/ } +/******/ } +/******/ if(!script) { +/******/ needAttach = true; +/******/ script = document.createElement('script'); +/******/ /******/ script.charset = 'utf-8'; -/******/ script.timeout = 120; -/******/ /******/ if (__webpack_require__.nc) { /******/ script.setAttribute("nonce", __webpack_require__.nc); /******/ } -/******/ script.src = jsonpScriptSrc(chunkId); -/******/ var timeout = setTimeout(function(){ -/******/ onScriptComplete({ type: 'timeout', target: script }); -/******/ }, 120000); -/******/ script.onerror = script.onload = onScriptComplete; -/******/ function onScriptComplete(event) { -/******/ // avoid mem leaks in IE. -/******/ script.onerror = script.onload = null; -/******/ clearTimeout(timeout); -/******/ var chunk = installedChunks[chunkId]; -/******/ if(chunk !== 0) { -/******/ if(chunk) { -/******/ var errorType = event && (event.type === 'load' ? 'missing' : event.type); -/******/ var realSrc = event && event.target && event.target.src; -/******/ var error = new Error('Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'); -/******/ error.type = errorType; -/******/ error.request = realSrc; -/******/ chunk[1](error); +/******/ +/******/ +/******/ script.src = url; +/******/ } +/******/ inProgress[url] = [done]; +/******/ const onScriptComplete = (prev, event) => { +/******/ // avoid mem leaks in IE. +/******/ script.onerror = script.onload = null; +/******/ clearTimeout(timeout); +/******/ const doneFns = inProgress[url]; +/******/ delete inProgress[url]; +/******/ script.parentNode?.removeChild(script); +/******/ doneFns?.forEach((fn) => (fn(event))); +/******/ if(prev) return prev(event); +/******/ } +/******/ const timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000); +/******/ script.onerror = onScriptComplete.bind(null, script.onerror); +/******/ script.onload = onScriptComplete.bind(null, script.onload); +/******/ needAttach && document.head.appendChild(script); +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/publicPath */ +/******/ __webpack_require__.p = "dist/"; +/******/ +/******/ /* webpack/runtime/jsonp chunk loading */ +/******/ (() => { +/******/ // no baseURI +/******/ +/******/ // object to store loaded and loading chunks +/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched +/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded +/******/ const installedChunks = { +/******/ "main": 0 +/******/ }; +/******/ +/******/ __webpack_require__.f.j = (chunkId, promises) => { +/******/ // JSONP chunk loading for javascript +/******/ let installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined; +/******/ if(installedChunkData !== 0) { // 0 means "already installed". +/******/ +/******/ // a Promise means "currently loading". +/******/ if(installedChunkData) { +/******/ promises.push(installedChunkData[2]); +/******/ } else { +/******/ if(true) { // all chunks have JS +/******/ // setup Promise in chunk cache +/******/ const promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject])); +/******/ promises.push(installedChunkData[2] = promise); +/******/ +/******/ // start chunk loading +/******/ const url = __webpack_require__.p + __webpack_require__.u(chunkId); +/******/ // create error before stack unwound to get useful stacktrace later +/******/ const error = new Error(); +/******/ const loadingEnded = (event) => { +/******/ if(__webpack_require__.o(installedChunks, chunkId)) { +/******/ installedChunkData = installedChunks[chunkId]; +/******/ if(installedChunkData !== 0) installedChunks[chunkId] = undefined; +/******/ if(installedChunkData) { +/******/ const errorType = event && (event.type === 'load' ? 'missing' : event.type); +/******/ const realSrc = event && event.target && event.target.src; +/******/ error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'; +/******/ error.name = 'ChunkLoadError'; +/******/ error.type = errorType; +/******/ error.request = realSrc; +/******/ error.event = event; +/******/ installedChunkData[1](error); +/******/ } +/******/ } +/******/ }; +/******/ __webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId); /******/ } -/******/ installedChunks[chunkId] = undefined; /******/ } -/******/ }; -/******/ head.appendChild(script); +/******/ } +/******/ }; +/******/ +/******/ // no prefetching +/******/ +/******/ // no preloaded +/******/ +/******/ // no HMR +/******/ +/******/ // no HMR manifest +/******/ +/******/ // no on chunks loaded +/******/ +/******/ // install a JSONP callback for chunk loading +/******/ const webpackJsonpCallback = (parentChunkLoadingFunction, data) => { +/******/ let [chunkIds, moreModules, runtime] = data; +/******/ // add "moreModules" to the modules object, +/******/ // then flag all "chunkIds" as loaded and fire callback +/******/ var moduleId, chunkId, i = 0; +/******/ if(chunkIds.some((id) => (installedChunks[id] !== 0))) { +/******/ for(moduleId in moreModules) { +/******/ if(__webpack_require__.o(moreModules, moduleId)) { +/******/ __webpack_require__.m[moduleId] = moreModules[moduleId]; +/******/ } +/******/ } +/******/ if(runtime) var result = runtime(__webpack_require__); /******/ } +/******/ if(parentChunkLoadingFunction) parentChunkLoadingFunction(data); +/******/ for(;i < chunkIds.length; i++) { +/******/ chunkId = chunkIds[i]; +/******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) { +/******/ installedChunks[chunkId][0](); +/******/ } +/******/ installedChunks[chunkId] = 0; +/******/ } +/******/ /******/ } -/******/ return Promise.all(promises); -/******/ }; -/******/ -/******/ // 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, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); -/******/ } -/******/ }; -/******/ -/******/ // define __esModule on exports -/******/ __webpack_require__.r = function(exports) { -/******/ Object.defineProperty(exports, '__esModule', { value: true }); -/******/ }; -/******/ -/******/ // 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 = "dist/"; -/******/ -/******/ // on error function for async loading -/******/ __webpack_require__.oe = function(err) { console.error(err); throw err; }; -/******/ -/******/ var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || []; -/******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); -/******/ jsonpArray.push = webpackJsonpCallback; -/******/ jsonpArray = jsonpArray.slice(); -/******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]); -/******/ var parentJsonpFunction = oldJsonpFunction; -/******/ -/******/ -/******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 3); -/******/ }) +/******/ +/******/ const chunkLoadingGlobal = self["webpackChunk"] = self["webpackChunk"] || []; +/******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0)); +/******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal)); +/******/ })(); +/******/ /************************************************************************/ ```
-``` javascript -/******/ ([ -/* 0 */, -/* 1 */, -/* 2 */ -/*!***************************!*\ - !*** ./node_modules/a.js ***! - \***************************/ -/*! no static exports found */ -/***/ (function(module, exports) { - -// module a - -/***/ }), -/* 3 */ +``` js +// This entry needs to be wrapped in an IIFE because it needs to be isolated against other modules in the chunk. +(() => { /*!********************!*\ !*** ./example.js ***! \********************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -var a = __webpack_require__(/*! a */ 2); +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: __webpack_require__, __webpack_require__.e, __webpack_require__.* */ +var a = __webpack_require__(/*! a */ 1); -__webpack_require__.e(/*! require.ensure | my own chunk */ 1).then((function(require) { +__webpack_require__.e(/*! require.ensure | my own chunk */ "my own chunk").then((function(require) { // a named chunk - var c = __webpack_require__(/*! c */ 4); -}).bind(null, __webpack_require__)).catch(__webpack_require__.oe); + var c = __webpack_require__(/*! c */ 3); +}).bind(null, __webpack_require__))['catch'](__webpack_require__.oe); -__webpack_require__.e(/*! require.ensure | my own chunk */ 1).then((function(require) { +__webpack_require__.e(/*! require.ensure | my own chunk */ "my own chunk").then((function(require) { // another chunk with the same name - var d = __webpack_require__(/*! d */ 1); -}).bind(null, __webpack_require__)).catch(__webpack_require__.oe); + var d = __webpack_require__(/*! d */ 4); +}).bind(null, __webpack_require__))['catch'](__webpack_require__.oe); -__webpack_require__.e(/*! require.ensure | my own chunk */ 1).then((function(require) { +__webpack_require__.e(/*! require.ensure | my own chunk */ "my own chunk").then((function(require) { // the same again -}).bind(null, __webpack_require__)).catch(__webpack_require__.oe); +}).bind(null, __webpack_require__))['catch'](__webpack_require__.oe); -__webpack_require__.e(/*! require.ensure */ 0).then((function(require) { +__webpack_require__.e(/*! require.ensure */ "node_modules_b_js-node_modules_d_js").then((function(require) { // chunk without name - var d = __webpack_require__(/*! d */ 1); -}).bind(null, __webpack_require__)).catch(__webpack_require__.oe); + var d = __webpack_require__(/*! d */ 4); +}).bind(null, __webpack_require__))['catch'](__webpack_require__.oe); +})(); -/***/ }) -/******/ ]); +/******/ })() +; ``` -# dist/0.output.js +# dist/my own chunk.output.js -``` javascript -(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0],[ -/* 0 */ +```javascript +(self["webpackChunk"] = self["webpackChunk"] || []).push([["my own chunk"],[ +/* 0 */, +/* 1 */, +/* 2 */ /*!***************************!*\ !*** ./node_modules/b.js ***! \***************************/ -/*! no static exports found */ -/***/ (function(module, exports) { +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: */ +/***/ (() => { // module b /***/ }), -/* 1 */ +/* 3 */ +/*!***************************!*\ + !*** ./node_modules/c.js ***! + \***************************/ +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: */ +/***/ (() => { + +// module c + +/***/ }), +/* 4 */ /*!***************************!*\ !*** ./node_modules/d.js ***! \***************************/ -/*! no static exports found */ -/***/ (function(module, exports) { +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: */ +/***/ (() => { // module d @@ -288,40 +317,33 @@ __webpack_require__.e(/*! require.ensure */ 0).then((function(require) { ]]); ``` -# dist/1.output.js +# dist/node_modules_b_js-node_modules_d_js.output.js -``` javascript -(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[1],[ -/* 0 */ +```javascript +(self["webpackChunk"] = self["webpackChunk"] || []).push([["node_modules_b_js-node_modules_d_js"],[ +/* 0 */, +/* 1 */, +/* 2 */ /*!***************************!*\ !*** ./node_modules/b.js ***! \***************************/ -/*! no static exports found */ -/***/ (function(module, exports) { +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: */ +/***/ (() => { // module b /***/ }), -/* 1 */ -/*!***************************!*\ - !*** ./node_modules/d.js ***! - \***************************/ -/*! no static exports found */ -/***/ (function(module, exports) { - -// module d - -/***/ }), -/* 2 */, /* 3 */, /* 4 */ /*!***************************!*\ - !*** ./node_modules/c.js ***! + !*** ./node_modules/d.js ***! \***************************/ -/*! no static exports found */ -/***/ (function(module, exports) { +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: */ +/***/ (() => { -// module c +// module d /***/ }) ]]); @@ -332,49 +354,85 @@ __webpack_require__.e(/*! require.ensure */ 0).then((function(require) { ## Unoptimized ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 - Asset Size Chunks Chunk Names -0.output.js 463 bytes 0 [emitted] -1.output.js 677 bytes 1 [emitted] my own chunk - output.js 8.13 KiB 2 [emitted] main -Entrypoint main = output.js -chunk {0} 0.output.js 22 bytes <{2}> [rendered] - > [3] ./example.js 17:0-20:2 - 2 modules -chunk {1} 1.output.js (my own chunk) 33 bytes <{2}> [rendered] - > [3] ./example.js 13:0-15:18 - > [3] ./example.js 3:0-6:18 - > [3] ./example.js 8:0-11:18 - 3 modules -chunk {2} output.js (main) 452 bytes >{0}< >{1}< [entry] [rendered] - > .\example.js main - [3] ./example.js 441 bytes {2} [built] - single entry .\example.js main - + 1 hidden module +asset output.js 9.71 KiB [emitted] (name: main) +asset my own chunk.output.js 746 bytes [emitted] (name: my own chunk) +asset node_modules_b_js-node_modules_d_js.output.js 562 bytes [emitted] +chunk (runtime: main) output.js (main) 432 bytes (javascript) 4.95 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 4.95 KiB 6 modules + dependent modules 11 bytes [dependent] 1 module + ./example.js 421 bytes [built] [code generated] + [used exports unknown] + entry ./example.js main +chunk (runtime: main) my own chunk.output.js (my own chunk) 33 bytes [rendered] + > ./example.js 13:0-15:18 + > ./example.js 3:0-6:18 + > ./example.js 8:0-11:18 + ./node_modules/b.js 11 bytes [built] [code generated] + [used exports unknown] + require.ensure item b ./example.js 3:0-6:18 + require.ensure item b ./example.js 8:0-11:18 + require.ensure item b ./example.js 17:0-20:2 + ./node_modules/c.js 11 bytes [built] [code generated] + [used exports unknown] + cjs require c ./example.js 5:9-21 + ./node_modules/d.js 11 bytes [built] [code generated] + [used exports unknown] + cjs require d ./example.js 10:9-21 + cjs require d ./example.js 19:9-21 +chunk (runtime: main) node_modules_b_js-node_modules_d_js.output.js 22 bytes [rendered] + > ./example.js 17:0-20:2 + ./node_modules/b.js 11 bytes [built] [code generated] + [used exports unknown] + require.ensure item b ./example.js 3:0-6:18 + require.ensure item b ./example.js 8:0-11:18 + require.ensure item b ./example.js 17:0-20:2 + ./node_modules/d.js 11 bytes [built] [code generated] + [used exports unknown] + cjs require d ./example.js 10:9-21 + cjs require d ./example.js 19:9-21 +webpack X.X.X compiled successfully ``` ## Production mode ``` -Hash: 0a1b2c3d4e5f6a7b8c9d -Version: webpack 4.8.0 - Asset Size Chunks Chunk Names -0.output.js 92 bytes 0 [emitted] -1.output.js 112 bytes 1, 0 [emitted] my own chunk - output.js 1.84 KiB 2 [emitted] main -Entrypoint main = output.js -chunk {0} 0.output.js 22 bytes <{2}> [rendered] - > [3] ./example.js 17:0-20:2 - 2 modules -chunk {1} 1.output.js (my own chunk) 33 bytes <{2}> [rendered] - > [3] ./example.js 13:0-15:18 - > [3] ./example.js 3:0-6:18 - > [3] ./example.js 8:0-11:18 - 3 modules -chunk {2} output.js (main) 452 bytes >{0}< >{1}< [entry] [rendered] - > .\example.js main - [3] ./example.js 441 bytes {2} [built] - single entry .\example.js main - + 1 hidden module +asset output.js 1.97 KiB [emitted] [minimized] (name: main) +asset my own chunk.output.js 131 bytes [emitted] [minimized] (name: my own chunk) +asset node_modules_b_js-node_modules_d_js.output.js 108 bytes [emitted] [minimized] +chunk (runtime: main) output.js (main) 432 bytes (javascript) 4.95 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 4.95 KiB 6 modules + dependent modules 11 bytes [dependent] 1 module + ./example.js 421 bytes [built] [code generated] + [no exports used] + entry ./example.js main +chunk (runtime: main) my own chunk.output.js (my own chunk) 33 bytes [rendered] + > ./example.js 13:0-15:18 + > ./example.js 3:0-6:18 + > ./example.js 8:0-11:18 + ./node_modules/b.js 11 bytes [built] [code generated] + [used exports unknown] + require.ensure item b ./example.js 3:0-6:18 + require.ensure item b ./example.js 8:0-11:18 + require.ensure item b ./example.js 17:0-20:2 + ./node_modules/c.js 11 bytes [built] [code generated] + [used exports unknown] + cjs require c ./example.js 5:9-21 + ./node_modules/d.js 11 bytes [built] [code generated] + [used exports unknown] + cjs require d ./example.js 10:9-21 + cjs require d ./example.js 19:9-21 +chunk (runtime: main) node_modules_b_js-node_modules_d_js.output.js 22 bytes [rendered] + > ./example.js 17:0-20:2 + ./node_modules/b.js 11 bytes [built] [code generated] + [used exports unknown] + require.ensure item b ./example.js 3:0-6:18 + require.ensure item b ./example.js 8:0-11:18 + require.ensure item b ./example.js 17:0-20:2 + ./node_modules/d.js 11 bytes [built] [code generated] + [used exports unknown] + cjs require d ./example.js 10:9-21 + cjs require d ./example.js 19:9-21 +webpack X.X.X compiled successfully ``` diff --git a/examples/named-chunks/template.md b/examples/named-chunks/template.md index ef6baf49ea2..b3447100444 100644 --- a/examples/named-chunks/template.md +++ b/examples/named-chunks/template.md @@ -1,26 +1,25 @@ # example.js -``` javascript -{{example.js}} +```javascript +_{{example.js}}_ ``` - # dist/output.js -``` javascript -{{dist/output.js}} +```javascript +_{{dist/output.js}}_ ``` -# dist/0.output.js +# dist/my own chunk.output.js -``` javascript -{{dist/0.output.js}} +```javascript +_{{dist/my own chunk.output.js}}_ ``` -# dist/1.output.js +# dist/node_modules_b_js-node_modules_d_js.output.js -``` javascript -{{dist/1.output.js}} +```javascript +_{{dist/node_modules_b_js-node_modules_d_js.output.js}}_ ``` # Info @@ -28,11 +27,11 @@ ## Unoptimized ``` -{{stdout}} +_{{stdout}}_ ``` ## Production mode ``` -{{production:stdout}} +_{{production:stdout}}_ ``` diff --git a/examples/named-chunks/webpack.config.js b/examples/named-chunks/webpack.config.js index 0d554bf62ea..1261cf9b395 100644 --- a/examples/named-chunks/webpack.config.js +++ b/examples/named-chunks/webpack.config.js @@ -1,5 +1,10 @@ -module.exports = { +"use strict"; + +/** @type {import("webpack").Configuration} */ +const config = { optimization: { - occurrenceOrder: true // To keep filename consistent between different modes (for example building only) + chunkIds: "named" } }; + +module.exports = config; diff --git a/examples/node_modules/module.js b/examples/node_modules/module.js index f23403c2c0f..d6df8480e22 100644 --- a/examples/node_modules/module.js +++ b/examples/node_modules/module.js @@ -1 +1 @@ -module.exports = "module"; \ No newline at end of file +module.exports = "module"; diff --git a/examples/nodejs-addons/README.md b/examples/nodejs-addons/README.md new file mode 100644 index 00000000000..c1f09d4a8ae --- /dev/null +++ b/examples/nodejs-addons/README.md @@ -0,0 +1,81 @@ +This example illustrates how to use [Node.js addons](https://nodejs.org/api/addons.html). + +# example.js + +```javascript +import { dlopen } from 'node:process'; +import { fileURLToPath } from 'node:url'; + +const file = new URL("./file.node", import.meta.url); +const myModule = { exports: {} }; + +try { + dlopen(myModule, fileURLToPath(file)); +} catch (err) { + console.log(err) + // Handling errors +} + +console.log(myModule.exports.hello()); +// Outputs: world +``` + +# webpack.config.js + +```javascript +"use strict"; + +/** @type {import("webpack").Configuration} */ +const config = { + // mode: "development" || "production", + target: "node", + output: { + // We strong recommend use `publicPath: 'auto'` or do not set `publicPath` at all to generate relative URLs + // publicPath: 'auto' + }, + module: { + rules: [ + { + test: /\.node$/, + type: "asset/resource" + } + ] + } +}; + +module.exports = config; +``` + +# Info + +## Unoptimized + +``` +asset 5664f09ab8adf033e173.node 16.5 KiB [emitted] [immutable] [from: file.node] (auxiliary name: main) +asset output.js 6.4 KiB [emitted] (name: main) +chunk (runtime: main) output.js (main) 457 bytes (javascript) 16.5 KiB (asset) 1.6 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 1.6 KiB 6 modules + dependent modules 16.5 KiB (asset) 126 bytes (javascript) [dependent] 3 modules + ./example.js 331 bytes [built] [code generated] + [no exports] + [used exports unknown] + entry ./example.js main +webpack X.X.X compiled successfully +``` + +## Production mode + +``` +asset 5664f09ab8adf033e173.node 16.5 KiB [emitted] [immutable] [from: file.node] (auxiliary name: main) +asset output.js 463 bytes [emitted] [minimized] (name: main) +chunk (runtime: main) output.js (main) 457 bytes (javascript) 16.5 KiB (asset) 264 bytes (runtime) [entry] [rendered] + > ./example.js main + runtime modules 264 bytes 2 modules + dependent modules 16.5 KiB (asset) 42 bytes (javascript) [dependent] 1 module + ./example.js + 2 modules 415 bytes [not cacheable] [built] [code generated] + [no exports] + [no exports used] + entry ./example.js main +webpack X.X.X compiled successfully +``` diff --git a/examples/nodejs-addons/binding.gyp b/examples/nodejs-addons/binding.gyp new file mode 100644 index 00000000000..de0ccf993a4 --- /dev/null +++ b/examples/nodejs-addons/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "file", + "sources": [ "file.cc" ] + } + ] +} diff --git a/examples/nodejs-addons/build.js b/examples/nodejs-addons/build.js new file mode 100644 index 00000000000..795841944eb --- /dev/null +++ b/examples/nodejs-addons/build.js @@ -0,0 +1,25 @@ +global.NO_PUBLIC_PATH = true; + +const cp = require("child_process"); +const path = require("path"); +const fs = require("fs"); + +cp.exec(`node-gyp --target=${process.version} configure build`, (error, stdout, stderr) => { + if (stderr) { + console.log(stderr); + } + + if (error !== null) { + console.log(error); + } + + fs.copyFile(path.resolve(__dirname, "./build/Release/file.node"), path.resolve(__dirname, './file.node'), (err) => { + if (err) { + console.log(err); + } + + require("../build-common"); + }); +}); + + diff --git a/examples/nodejs-addons/example.js b/examples/nodejs-addons/example.js new file mode 100644 index 00000000000..41d2403e4be --- /dev/null +++ b/examples/nodejs-addons/example.js @@ -0,0 +1,15 @@ +import { dlopen } from 'node:process'; +import { fileURLToPath } from 'node:url'; + +const file = new URL("./file.node", import.meta.url); +const myModule = { exports: {} }; + +try { + dlopen(myModule, fileURLToPath(file)); +} catch (err) { + console.log(err) + // Handling errors +} + +console.log(myModule.exports.hello()); +// Outputs: world diff --git a/examples/nodejs-addons/file.cc b/examples/nodejs-addons/file.cc new file mode 100644 index 00000000000..b94aecb3c76 --- /dev/null +++ b/examples/nodejs-addons/file.cc @@ -0,0 +1,21 @@ +// Include uv.h and v8.h ahead of node.h to verify that node.h doesn't need to +// be included first. Disable clang-format as it will sort the include lists. +// clang-format off +#include +#include +#include +// clang-format on + +static void Method(const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = args.GetIsolate(); + args.GetReturnValue().Set( + v8::String::NewFromUtf8(isolate, "world").ToLocalChecked()); +} + +static void InitModule(v8::Local exports, + v8::Local module, + v8::Local context) { + NODE_SET_METHOD(exports, "hello", Method); +} + +NODE_MODULE(NODE_GYP_MODULE_NAME, InitModule) diff --git a/examples/nodejs-addons/file.node b/examples/nodejs-addons/file.node new file mode 100755 index 00000000000..91c5b56063a Binary files /dev/null and b/examples/nodejs-addons/file.node differ diff --git a/examples/nodejs-addons/template.md b/examples/nodejs-addons/template.md new file mode 100644 index 00000000000..9ac79d27260 --- /dev/null +++ b/examples/nodejs-addons/template.md @@ -0,0 +1,27 @@ +This example illustrates how to use [Node.js addons](https://nodejs.org/api/addons.html). + +# example.js + +```javascript +_{{example.js}}_ +``` + +# webpack.config.js + +```javascript +_{{webpack.config.js}}_ +``` + +# Info + +## Unoptimized + +``` +_{{stdout}}_ +``` + +## Production mode + +``` +_{{production:stdout}}_ +``` diff --git a/examples/nodejs-addons/webpack.config.js b/examples/nodejs-addons/webpack.config.js new file mode 100644 index 00000000000..dc29fa2dd3f --- /dev/null +++ b/examples/nodejs-addons/webpack.config.js @@ -0,0 +1,21 @@ +"use strict"; + +/** @type {import("webpack").Configuration} */ +const config = { + // mode: "development" || "production", + target: "node", + output: { + // We strong recommend use `publicPath: 'auto'` or do not set `publicPath` at all to generate relative URLs + // publicPath: 'auto' + }, + module: { + rules: [ + { + test: /\.node$/, + type: "asset/resource" + } + ] + } +}; + +module.exports = config; diff --git a/examples/persistent-caching/README.md b/examples/persistent-caching/README.md new file mode 100644 index 00000000000..f246f2029f5 --- /dev/null +++ b/examples/persistent-caching/README.md @@ -0,0 +1,94 @@ +# example.js + +```javascript +console.log(process.env.NODE_ENV); + +import "./example.css"; +import "react"; +import "react-dom"; +import "acorn"; +import "core-js"; +import "date-fns"; +import "lodash"; +import * as _ from "lodash-es"; +console.log(_); +``` + +# webpack.config.js + +```javascript +"use strict"; + +const path = require("path"); + +/** @type {(env: "development" | "production") => import("webpack").Configuration} */ +const config = (env = "development") => ({ + mode: env, + infrastructureLogging: { + // Optional: print more verbose logging about caching + level: "verbose" + }, + cache: { + type: "filesystem", + + // changing the cacheDirectory is optional, + // by default it will be in `node_modules/.cache` + cacheDirectory: path.resolve(__dirname, ".cache"), + + // Add additional dependencies to the build + buildDependencies: { + // recommended to invalidate cache on config changes + // This also makes all dependencies of this file build dependencies + config: [__filename] + // By default webpack and loaders are build dependencies + } + }, + module: { + rules: [ + { + test: /\.css$/, + use: ["style-loader", "css-loader"] + } + ] + } +}); + +module.exports = config; +``` + +# Info + +## Unoptimized + +``` +asset output.js 3.68 MiB [emitted] (name: main) +chunk (runtime: main) output.js (main) 2.29 MiB (javascript) 1.71 KiB (runtime) [entry] + > ./example.js main + cached modules 2.29 MiB (javascript) 1.71 KiB (runtime) [cached] 1537 modules +webpack X.X.X compiled successfully +``` + +## Production mode + +``` +asset output.js 543 KiB [emitted] [minimized] [big] (name: main) 1 related asset +chunk (runtime: main) output.js (main) 2.23 MiB (javascript) 3.24 KiB (runtime) [entry] + > ./example.js main + cached modules 2.23 MiB (javascript) 3.24 KiB (runtime) [cached] 42 modules + +WARNING in asset size limit: The following asset(s) exceed the recommended size limit (244 KiB). +This can impact web performance. +Assets: + output.js (543 KiB) + +WARNING in entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB). This can impact web performance. +Entrypoints: + main (543 KiB) + output.js + +WARNING in webpack performance recommendations: +You can limit the size of your bundles by using import() or require.ensure to lazy load some parts of your application. +For more info visit https://webpack.js.org/guides/code-splitting/ + +webpack X.X.X compiled with 3 warnings +``` diff --git a/examples/persistent-caching/build.js b/examples/persistent-caching/build.js new file mode 100644 index 00000000000..41c29c9d169 --- /dev/null +++ b/examples/persistent-caching/build.js @@ -0,0 +1 @@ +require("../build-common"); \ No newline at end of file diff --git a/examples/persistent-caching/example.css b/examples/persistent-caching/example.css new file mode 100644 index 00000000000..f0d5b13bffd --- /dev/null +++ b/examples/persistent-caching/example.css @@ -0,0 +1,3 @@ +body { + background: red; +} diff --git a/examples/persistent-caching/example.js b/examples/persistent-caching/example.js new file mode 100644 index 00000000000..f04e6a6675a --- /dev/null +++ b/examples/persistent-caching/example.js @@ -0,0 +1,11 @@ +console.log(process.env.NODE_ENV); + +import "./example.css"; +import "react"; +import "react-dom"; +import "acorn"; +import "core-js"; +import "date-fns"; +import "lodash"; +import * as _ from "lodash-es"; +console.log(_); diff --git a/examples/persistent-caching/template.md b/examples/persistent-caching/template.md new file mode 100644 index 00000000000..2df3585bde6 --- /dev/null +++ b/examples/persistent-caching/template.md @@ -0,0 +1,25 @@ +# example.js + +```javascript +_{{example.js}}_ +``` + +# webpack.config.js + +```javascript +_{{webpack.config.js}}_ +``` + +# Info + +## Unoptimized + +``` +_{{stdout}}_ +``` + +## Production mode + +``` +_{{production:stdout}}_ +``` diff --git a/examples/persistent-caching/webpack.config.js b/examples/persistent-caching/webpack.config.js new file mode 100644 index 00000000000..8c297c6daa2 --- /dev/null +++ b/examples/persistent-caching/webpack.config.js @@ -0,0 +1,37 @@ +"use strict"; + +const path = require("path"); + +/** @type {(env: "development" | "production") => import("webpack").Configuration} */ +const config = (env = "development") => ({ + mode: env, + infrastructureLogging: { + // Optional: print more verbose logging about caching + level: "verbose" + }, + cache: { + type: "filesystem", + + // changing the cacheDirectory is optional, + // by default it will be in `node_modules/.cache` + cacheDirectory: path.resolve(__dirname, ".cache"), + + // Add additional dependencies to the build + buildDependencies: { + // recommended to invalidate cache on config changes + // This also makes all dependencies of this file build dependencies + config: [__filename] + // By default webpack and loaders are build dependencies + } + }, + module: { + rules: [ + { + test: /\.css$/, + use: ["style-loader", "css-loader"] + } + ] + } +}); + +module.exports = config; diff --git a/examples/reexport-components/README.md b/examples/reexport-components/README.md new file mode 100644 index 00000000000..000e1a2cdd5 --- /dev/null +++ b/examples/reexport-components/README.md @@ -0,0 +1,466 @@ +# example.js + +```javascript +// insert router here +import(`./pages/${page}`); +``` + +# pages/Dashboard.js + +```javascript +import { Button, Checkbox } from "../components"; + +const Dashboard = () => { + return ( + <> + + +

Computing fibonacci without worker:

+ +

+	

Computing fibonacci with worker:

+ +

+`;
+
+const history = document.getElementById("history");
+const message = document.getElementById("message");
+const send = document.getElementById("send");
+const fib1 = document.getElementById("fib1");
+const output1 = document.getElementById("output1");
+const fib2 = document.getElementById("fib2");
+const output2 = document.getElementById("output2");
+
+/// CHAT with shared worker ///
+
+const chatWorker = new SharedWorker(
+	new URL("./chat-worker.js", import.meta.url),
+	{
+		name: "chat",
+		type: "module"
+	}
+);
+
+let historyTimeout;
+const scheduleUpdateHistory = () => {
+	clearTimeout(historyTimeout);
+	historyTimeout = setTimeout(() => {
+		chatWorker.port.postMessage({ type: "history" });
+	}, 1000);
+};
+scheduleUpdateHistory();
+
+const from = `User ${Math.floor(Math.random() * 10000)}`;
+
+send.addEventListener("click", e => {
+	chatWorker.port.postMessage({
+		type: "message",
+		content: message.value,
+		from
+	});
+	message.value = "";
+	message.focus();
+	e.preventDefault();
+});
+
+chatWorker.port.onmessage = event => {
+	const msg = event.data;
+	switch (msg.type) {
+		case "history":
+			history.innerText = msg.history.join("\n");
+			scheduleUpdateHistory();
+			break;
+	}
+};
+
+/// FIBONACCI without worker ///
+
+fib1.addEventListener("change", async () => {
+	try {
+		const value = parseInt(fib1.value, 10);
+		const { fibonacci } = await import("./fibonacci");
+		const result = fibonacci(value);
+		output1.innerText = `fib(${value}) = ${result}`;
+	} catch (e) {
+		output1.innerText = e.message;
+	}
+});
+
+/// FIBONACCI with worker ///
+
+const fibWorker = new Worker(new URL("./fib-worker.js", import.meta.url), {
+	name: "fibonacci",
+	type: "module"
+	/* webpackEntryOptions: { filename: "workers/[name].js" } */
+});
+
+fib2.addEventListener("change", () => {
+	try {
+		const value = parseInt(fib2.value, 10);
+		fibWorker.postMessage(`${value}`);
+	} catch (e) {
+		output2.innerText = e.message;
+	}
+});
+
+fibWorker.onmessage = event => {
+	output2.innerText = event.data;
+};
+```
+
+# fib-worker.js
+
+```javascript
+onmessage = async event => {
+	const { fibonacci } = await import("./fibonacci");
+	const value = JSON.parse(event.data);
+	postMessage(`fib(${value}) = ${fibonacci(value)}`);
+};
+```
+
+# fibonacci.js
+
+```javascript
+export function fibonacci(n) {
+	return n < 1 ? 0 : n <= 2 ? 1 : fibonacci(n - 1) + fibonacci(n - 2);
+}
+```
+
+# chat-worker.js
+
+```javascript
+import { history, add } from "./chat-module";
+
+onconnect = function (e) {
+	for (const port of e.ports) {
+		port.onmessage = event => {
+			const msg = event.data;
+			switch (msg.type) {
+				case "message":
+					add(msg.content, msg.from);
+				// fallthrough
+				case "history":
+					port.postMessage({
+						type: "history",
+						history
+					});
+					break;
+			}
+		};
+	}
+};
+```
+
+# chat-module.js
+
+```javascript
+export const history = [];
+
+export const add = (content, from) => {
+	if (history.length > 10) history.shift();
+	history.push(`${from}: ${content}`);
+};
+```
+
+# dist/main.js
+
+```javascript
+/******/ (() => { // webpackBootstrap
+/******/ 	var __webpack_modules__ = ({});
+```
+
+
/* webpack runtime code */ + +``` js +/************************************************************************/ +/******/ // The module cache +/******/ const __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ const 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; +/******/ } +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = __webpack_modules__; +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/define property getters */ +/******/ // define getter/value functions for harmony exports +/******/ __webpack_require__.d = (exports, definition) => { +/******/ if(Array.isArray(definition)) { +/******/ var i = 0; +/******/ while(i < definition.length) { +/******/ var key = definition[i++]; +/******/ var binding = definition[i++]; +/******/ if(!__webpack_require__.o(exports, key)) { +/******/ if(binding === 0) { +/******/ Object.defineProperty(exports, key, { enumerable: true, value: definition[i++] }); +/******/ } else { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: binding }); +/******/ } +/******/ } else if(binding === 0) { i++; } +/******/ } +/******/ } else { +/******/ for(var key in definition) { +/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); +/******/ } +/******/ } +/******/ } +/******/ }; +/******/ +/******/ /* webpack/runtime/ensure chunk */ +/******/ __webpack_require__.f = {}; +/******/ // This file contains only the entry chunk. +/******/ // The chunk loading function for additional chunks +/******/ __webpack_require__.e = (chunkId) => { +/******/ return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => { +/******/ __webpack_require__.f[key](chunkId, promises); +/******/ return promises; +/******/ }, [])); +/******/ }; +/******/ +/******/ /* webpack/runtime/get javascript chunk filename */ +/******/ // This function allow to reference async chunks +/******/ __webpack_require__.u = (chunkId) => { +/******/ // return url for filenames not based on template +/******/ if (chunkId === 721) return "workers/fibonacci.js"; +/******/ // return url for filenames based on template +/******/ return "" + (chunkId === 377 ? "chat" : chunkId) + ".js"; +/******/ }; +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)); +/******/ +/******/ /* webpack/runtime/load script */ +/******/ (() => { +/******/ const inProgress = {}; +/******/ // data-webpack is not used as build has no uniqueName +/******/ // loadScript function to load a script via script tag +/******/ __webpack_require__.l = (url, done, key, chunkId) => { +/******/ if(inProgress[url]) { inProgress[url].push(done); return; } +/******/ let script, needAttach; +/******/ if(key !== undefined) { +/******/ const scripts = document.getElementsByTagName("script"); +/******/ for(var i = 0; i < scripts.length; i++) { +/******/ const s = scripts[i]; +/******/ if(s.getAttribute("src") == url) { script = s; break; } +/******/ } +/******/ } +/******/ if(!script) { +/******/ needAttach = true; +/******/ script = document.createElement('script'); +/******/ +/******/ script.charset = 'utf-8'; +/******/ if (__webpack_require__.nc) { +/******/ script.setAttribute("nonce", __webpack_require__.nc); +/******/ } +/******/ +/******/ +/******/ script.src = url; +/******/ } +/******/ inProgress[url] = [done]; +/******/ const onScriptComplete = (prev, event) => { +/******/ // avoid mem leaks in IE. +/******/ script.onerror = script.onload = null; +/******/ clearTimeout(timeout); +/******/ const doneFns = inProgress[url]; +/******/ delete inProgress[url]; +/******/ script.parentNode?.removeChild(script); +/******/ doneFns?.forEach((fn) => (fn(event))); +/******/ if(prev) return prev(event); +/******/ } +/******/ const timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000); +/******/ script.onerror = onScriptComplete.bind(null, script.onerror); +/******/ script.onload = onScriptComplete.bind(null, script.onload); +/******/ needAttach && document.head.appendChild(script); +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/publicPath */ +/******/ __webpack_require__.p = "/dist/"; +/******/ +/******/ /* webpack/runtime/jsonp chunk loading */ +/******/ (() => { +/******/ __webpack_require__.b = (typeof document !== 'undefined' && document.baseURI) || self.location.href; +/******/ +/******/ // object to store loaded and loading chunks +/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched +/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded +/******/ const installedChunks = { +/******/ 792: 0 +/******/ }; +/******/ +/******/ __webpack_require__.f.j = (chunkId, promises) => { +/******/ // JSONP chunk loading for javascript +/******/ let installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined; +/******/ if(installedChunkData !== 0) { // 0 means "already installed". +/******/ +/******/ // a Promise means "currently loading". +/******/ if(installedChunkData) { +/******/ promises.push(installedChunkData[2]); +/******/ } else { +/******/ if(true) { // all chunks have JS +/******/ // setup Promise in chunk cache +/******/ const promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject])); +/******/ promises.push(installedChunkData[2] = promise); +/******/ +/******/ // start chunk loading +/******/ const url = __webpack_require__.p + __webpack_require__.u(chunkId); +/******/ // create error before stack unwound to get useful stacktrace later +/******/ const error = new Error(); +/******/ const loadingEnded = (event) => { +/******/ if(__webpack_require__.o(installedChunks, chunkId)) { +/******/ installedChunkData = installedChunks[chunkId]; +/******/ if(installedChunkData !== 0) installedChunks[chunkId] = undefined; +/******/ if(installedChunkData) { +/******/ const errorType = event && (event.type === 'load' ? 'missing' : event.type); +/******/ const realSrc = event && event.target && event.target.src; +/******/ error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'; +/******/ error.name = 'ChunkLoadError'; +/******/ error.type = errorType; +/******/ error.request = realSrc; +/******/ error.event = event; +/******/ installedChunkData[1](error); +/******/ } +/******/ } +/******/ }; +/******/ __webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId); +/******/ } +/******/ } +/******/ } +/******/ }; +/******/ +/******/ // no prefetching +/******/ +/******/ // no preloaded +/******/ +/******/ // no HMR +/******/ +/******/ // no HMR manifest +/******/ +/******/ // no on chunks loaded +/******/ +/******/ // install a JSONP callback for chunk loading +/******/ const webpackJsonpCallback = (parentChunkLoadingFunction, data) => { +/******/ let [chunkIds, moreModules, runtime] = data; +/******/ // add "moreModules" to the modules object, +/******/ // then flag all "chunkIds" as loaded and fire callback +/******/ var moduleId, chunkId, i = 0; +/******/ if(chunkIds.some((id) => (installedChunks[id] !== 0))) { +/******/ for(moduleId in moreModules) { +/******/ if(__webpack_require__.o(moreModules, moduleId)) { +/******/ __webpack_require__.m[moduleId] = moreModules[moduleId]; +/******/ } +/******/ } +/******/ if(runtime) var result = runtime(__webpack_require__); +/******/ } +/******/ if(parentChunkLoadingFunction) parentChunkLoadingFunction(data); +/******/ for(;i < chunkIds.length; i++) { +/******/ chunkId = chunkIds[i]; +/******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) { +/******/ installedChunks[chunkId][0](); +/******/ } +/******/ installedChunks[chunkId] = 0; +/******/ } +/******/ +/******/ } +/******/ +/******/ const chunkLoadingGlobal = self["webpackChunk"] = self["webpackChunk"] || []; +/******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0)); +/******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal)); +/******/ })(); +/******/ +/************************************************************************/ +``` + +
+ +``` js +let __webpack_exports__ = {}; +/*!********************!*\ + !*** ./example.js ***! + \********************/ +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: __webpack_require__.p, __webpack_require__.b, __webpack_require__.u, __webpack_require__.e, __webpack_require__, __webpack_require__.* */ +/*! ModuleConcatenation bailout: Module is not in strict mode */ +document.body.innerHTML = ` +

+	
+ + +
+

Computing fibonacci without worker:

+ +

+	

Computing fibonacci with worker:

+ +

+`;
+
+const history = document.getElementById("history");
+const message = document.getElementById("message");
+const send = document.getElementById("send");
+const fib1 = document.getElementById("fib1");
+const output1 = document.getElementById("output1");
+const fib2 = document.getElementById("fib2");
+const output2 = document.getElementById("output2");
+
+/// CHAT with shared worker ///
+
+const chatWorker = new SharedWorker(
+	new URL(/* worker import */ __webpack_require__.p + __webpack_require__.u(377), __webpack_require__.b),
+	{
+		name: "chat",
+		type: undefined
+	}
+);
+
+let historyTimeout;
+const scheduleUpdateHistory = () => {
+	clearTimeout(historyTimeout);
+	historyTimeout = setTimeout(() => {
+		chatWorker.port.postMessage({ type: "history" });
+	}, 1000);
+};
+scheduleUpdateHistory();
+
+const from = `User ${Math.floor(Math.random() * 10000)}`;
+
+send.addEventListener("click", e => {
+	chatWorker.port.postMessage({
+		type: "message",
+		content: message.value,
+		from
+	});
+	message.value = "";
+	message.focus();
+	e.preventDefault();
+});
+
+chatWorker.port.onmessage = event => {
+	const msg = event.data;
+	switch (msg.type) {
+		case "history":
+			history.innerText = msg.history.join("\n");
+			scheduleUpdateHistory();
+			break;
+	}
+};
+
+/// FIBONACCI without worker ///
+
+fib1.addEventListener("change", async () => {
+	try {
+		const value = parseInt(fib1.value, 10);
+		const { fibonacci } = await __webpack_require__.e(/*! import() */ 129).then(__webpack_require__.bind(__webpack_require__, /*! ./fibonacci */ 3));
+		const result = fibonacci(value);
+		output1.innerText = `fib(${value}) = ${result}`;
+	} catch (e) {
+		output1.innerText = e.message;
+	}
+});
+
+/// FIBONACCI with worker ///
+
+const fibWorker = new Worker(new URL(/* worker import */ __webpack_require__.p + __webpack_require__.u(721), __webpack_require__.b), {
+	name: "fibonacci",
+	type: undefined
+	/* webpackEntryOptions: { filename: "workers/[name].js" } */
+});
+
+fib2.addEventListener("change", () => {
+	try {
+		const value = parseInt(fib2.value, 10);
+		fibWorker.postMessage(`${value}`);
+	} catch (e) {
+		output2.innerText = e.message;
+	}
+});
+
+fibWorker.onmessage = event => {
+	output2.innerText = event.data;
+};
+
+/******/ })()
+;
+```
+
+# dist/chat.js
+
+```javascript
+/******/ (() => { // webpackBootstrap
+/******/ 	"use strict";
+/*!************************************!*\
+  !*** ./chat-worker.js + 1 modules ***!
+  \************************************/
+/*! namespace exports */
+/*! runtime requirements:  */
+
+;// ./chat-module.js
+const chat_module_history = [];
+
+const add = (content, from) => {
+	if (chat_module_history.length > 10) chat_module_history.shift();
+	chat_module_history.push(`${from}: ${content}`);
+};
+
+;// ./chat-worker.js
+
+
+onconnect = function (e) {
+	for (const port of e.ports) {
+		port.onmessage = event => {
+			const msg = event.data;
+			switch (msg.type) {
+				case "message":
+					add(msg.content, msg.from);
+				// fallthrough
+				case "history":
+					port.postMessage({
+						type: "history",
+						history: chat_module_history
+					});
+					break;
+			}
+		};
+	}
+};
+
+/******/ })()
+;
+```
+
+```javascript
+(()=>{"use strict";const s=[];onconnect=function(t){for(const o of t.ports)o.onmessage=t=>{const e=t.data;switch(e.type){case"message":n=e.content,c=e.from,s.length>10&&s.shift(),s.push(`${c}: ${n}`);case"history":o.postMessage({type:"history",history:s})}var n,c}}})();
+```
+
+# dist/workers/fibonacci.js
+
+```javascript
+/******/ (() => { // webpackBootstrap
+/******/ 	var __webpack_modules__ = ({});
+```
+
+
/* webpack runtime code */ + +``` js +/************************************************************************/ +/******/ // The module cache +/******/ const __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ const 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; +/******/ } +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = __webpack_modules__; +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/define property getters */ +/******/ // define getter/value functions for harmony exports +/******/ __webpack_require__.d = (exports, definition) => { +/******/ if(Array.isArray(definition)) { +/******/ var i = 0; +/******/ while(i < definition.length) { +/******/ var key = definition[i++]; +/******/ var binding = definition[i++]; +/******/ if(!__webpack_require__.o(exports, key)) { +/******/ if(binding === 0) { +/******/ Object.defineProperty(exports, key, { enumerable: true, value: definition[i++] }); +/******/ } else { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: binding }); +/******/ } +/******/ } else if(binding === 0) { i++; } +/******/ } +/******/ } else { +/******/ for(var key in definition) { +/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); +/******/ } +/******/ } +/******/ } +/******/ }; +/******/ +/******/ /* webpack/runtime/ensure chunk */ +/******/ __webpack_require__.f = {}; +/******/ // This file contains only the entry chunk. +/******/ // The chunk loading function for additional chunks +/******/ __webpack_require__.e = (chunkId) => { +/******/ return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => { +/******/ __webpack_require__.f[key](chunkId, promises); +/******/ return promises; +/******/ }, [])); +/******/ }; +/******/ +/******/ /* webpack/runtime/get javascript chunk filename */ +/******/ // This function allow to reference async chunks +/******/ __webpack_require__.u = (chunkId) => { +/******/ // return url for filenames based on template +/******/ return "" + chunkId + ".js"; +/******/ }; +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)); +/******/ +/******/ /* webpack/runtime/publicPath */ +/******/ __webpack_require__.p = "/dist/"; +/******/ +/******/ /* webpack/runtime/importScripts chunk loading */ +/******/ (() => { +/******/ // no baseURI +/******/ +/******/ // object to store loaded chunks +/******/ // "1" means "already loaded" +/******/ var installedChunks = { +/******/ 721: 1 +/******/ }; +/******/ +/******/ // importScripts chunk loading +/******/ var installChunk = (data) => { +/******/ let [chunkIds, moreModules, runtime] = data; +/******/ for(var moduleId in moreModules) { +/******/ if(__webpack_require__.o(moreModules, moduleId)) { +/******/ __webpack_require__.m[moduleId] = moreModules[moduleId]; +/******/ } +/******/ } +/******/ if(runtime) runtime(__webpack_require__); +/******/ while(chunkIds.length) +/******/ installedChunks[chunkIds.pop()] = 1; +/******/ parentChunkLoadingFunction(data); +/******/ }; +/******/ __webpack_require__.f.i = (chunkId, promises) => { +/******/ // "1" is the signal for "already loaded" +/******/ if(!installedChunks[chunkId]) { +/******/ if(true) { // all chunks have JS +/******/ importScripts(__webpack_require__.p + __webpack_require__.u(chunkId)); +/******/ } +/******/ } +/******/ }; +/******/ +/******/ var chunkLoadingGlobal = self["webpackChunk"] = self["webpackChunk"] || []; +/******/ var parentChunkLoadingFunction = chunkLoadingGlobal.push.bind(chunkLoadingGlobal); +/******/ chunkLoadingGlobal.forEach(installChunk); +/******/ chunkLoadingGlobal.push = installChunk; +/******/ +/******/ // no HMR +/******/ +/******/ // no HMR manifest +/******/ })(); +/******/ +/************************************************************************/ +``` + +
+ +``` js +let __webpack_exports__ = {}; +/*!***********************!*\ + !*** ./fib-worker.js ***! + \***********************/ +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: __webpack_require__.e, __webpack_require__, __webpack_require__.* */ +/*! ModuleConcatenation bailout: Module is not in strict mode */ +onmessage = async event => { + const { fibonacci } = await __webpack_require__.e(/*! import() */ 129).then(__webpack_require__.bind(__webpack_require__, /*! ./fibonacci */ 3)); + const value = JSON.parse(event.data); + postMessage(`fib(${value}) = ${fibonacci(value)}`); +}; + +/******/ })() +; +``` + +```javascript +(()=>{var e={};const r={};function t(o){const a=r[o];if(void 0!==a)return a.exports;const n=r[o]={exports:{}};return e[o](n,n.exports,t),n.exports}t.m=e,t.d=(e,r)=>{if(Array.isArray(r))for(var o=0;oPromise.all(Object.keys(t.f).reduce((r,o)=>(t.f[o](e,r),r),[])),t.u=e=>e+".js",t.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),t.p="/dist/",(()=>{var e={721:1},r=r=>{let[o,n,s]=r;for(var p in n)t.o(n,p)&&(t.m[p]=n[p]);for(s&&s(t);o.length;)e[o.pop()]=1;a(r)};t.f.i=(r,o)=>{e[r]||importScripts(t.p+t.u(r))};var o=self.webpackChunk=self.webpackChunk||[],a=o.push.bind(o);o.forEach(r),o.push=r})(),onmessage=async e=>{const{fibonacci:r}=await t.e(129).then(t.bind(t,129)),o=JSON.parse(e.data);postMessage(`fib(${o}) = ${r(o)}`)}})(); +``` + +# dist/129.js + +```javascript +"use strict"; +(self["webpackChunk"] = self["webpackChunk"] || []).push([[129],{ + +/***/ 3 +/*!**********************!*\ + !*** ./fibonacci.js ***! + \**********************/ +/*! namespace exports */ +/*! export fibonacci [provided] [used in main, 9a81d90cfd0dfd13d748] [usage prevents renaming] */ +/*! runtime requirements: __webpack_exports__, __webpack_require__.d, __webpack_require__.* */ +(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ fibonacci: () => (/* binding */ fibonacci) +/* harmony export */ }); +function fibonacci(n) { + return n < 1 ? 0 : n <= 2 ? 1 : fibonacci(n - 1) + fibonacci(n - 2); +} + + +/***/ } + +}]); +``` + +# Info + +## Unoptimized + +``` +asset main.js 12.3 KiB [emitted] (name: main) +asset workers/fibonacci.js 5.45 KiB [emitted] (name: fibonacci) +asset chat.js 839 bytes [emitted] (name: chat) +asset 129.js 729 bytes [emitted] +chunk (runtime: 9a81d90cfd0dfd13d748, main) 129.js 103 bytes [rendered] + > ./fibonacci ./example.js 70:30-51 + > ./fibonacci ./fib-worker.js 2:29-50 + ./fibonacci.js 103 bytes [built] [code generated] + [exports: fibonacci] + [all exports used] + import() ./fibonacci ./example.js 70:30-51 + import() ./fibonacci ./fib-worker.js 2:29-50 +chunk (runtime: 1fad8bf8de78b0a77bfd) chat.js (chat) 527 bytes [entry] [rendered] + > ./example.js 25:19-31:1 + ./chat-worker.js + 1 modules 527 bytes [built] [code generated] + [no exports] + [no exports used] + new Worker() ./chat-worker.js ./example.js 25:19-31:1 +chunk (runtime: 9a81d90cfd0dfd13d748) workers/fibonacci.js (fibonacci) 176 bytes (javascript) 2.37 KiB (runtime) [entry] [rendered] + > ./example.js 80:18-84:2 + runtime modules 2.37 KiB 6 modules + ./fib-worker.js 176 bytes [built] [code generated] + [no exports used] + new Worker() ./fib-worker.js ./example.js 80:18-84:2 +chunk (runtime: main) main.js (main) 2.25 KiB (javascript) 5.9 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 5.9 KiB 7 modules + ./example.js 2.25 KiB [built] [code generated] + [no exports used] + entry ./example.js main +webpack X.X.X compiled successfully +``` + +## Production mode + +``` +asset main.js 3.54 KiB [emitted] [minimized] (name: main) +asset workers/fibonacci.js 1010 bytes [emitted] [minimized] (name: fibonacci) +asset chat.js 270 bytes [emitted] [minimized] (name: chat) +asset 129.js 156 bytes [emitted] [minimized] +chunk (runtime: 9a81d90cfd0dfd13d748, main) 129.js 103 bytes [rendered] + > ./fibonacci ./fib-worker.js 2:29-50 + > ./fibonacci ./example.js 70:30-51 + ./fibonacci.js 103 bytes [built] [code generated] + [exports: fibonacci] + [all exports used] + import() ./fibonacci ./example.js 70:30-51 + import() ./fibonacci ./fib-worker.js 2:29-50 +chunk (runtime: 1fad8bf8de78b0a77bfd) chat.js (chat) 527 bytes [entry] [rendered] + > ./example.js 25:19-31:1 + ./chat-worker.js + 1 modules 527 bytes [built] [code generated] + [no exports] + [no exports used] + new Worker() ./chat-worker.js ./example.js 25:19-31:1 +chunk (runtime: 9a81d90cfd0dfd13d748) workers/fibonacci.js (fibonacci) 176 bytes (javascript) 2.37 KiB (runtime) [entry] [rendered] + > ./example.js 80:18-84:2 + runtime modules 2.37 KiB 6 modules + ./fib-worker.js 176 bytes [built] [code generated] + [no exports used] + new Worker() ./fib-worker.js ./example.js 80:18-84:2 +chunk (runtime: main) main.js (main) 2.25 KiB (javascript) 5.9 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 5.9 KiB 7 modules + ./example.js 2.25 KiB [built] [code generated] + [no exports used] + entry ./example.js main +webpack X.X.X compiled successfully +``` diff --git a/examples/worker/build.js b/examples/worker/build.js new file mode 100644 index 00000000000..5768b058787 --- /dev/null +++ b/examples/worker/build.js @@ -0,0 +1,3 @@ +global.NO_TARGET_ARGS = true; +global.NO_PUBLIC_PATH = true; +require("../build-common"); diff --git a/examples/worker/chat-module.js b/examples/worker/chat-module.js new file mode 100644 index 00000000000..716a104a9dc --- /dev/null +++ b/examples/worker/chat-module.js @@ -0,0 +1,6 @@ +export const history = []; + +export const add = (content, from) => { + if (history.length > 10) history.shift(); + history.push(`${from}: ${content}`); +}; diff --git a/examples/worker/chat-worker.js b/examples/worker/chat-worker.js new file mode 100644 index 00000000000..66fa65165a9 --- /dev/null +++ b/examples/worker/chat-worker.js @@ -0,0 +1,20 @@ +import { history, add } from "./chat-module"; + +onconnect = function (e) { + for (const port of e.ports) { + port.onmessage = event => { + const msg = event.data; + switch (msg.type) { + case "message": + add(msg.content, msg.from); + // fallthrough + case "history": + port.postMessage({ + type: "history", + history + }); + break; + } + }; + } +}; diff --git a/examples/worker/example.js b/examples/worker/example.js new file mode 100644 index 00000000000..fcbe23f092f --- /dev/null +++ b/examples/worker/example.js @@ -0,0 +1,97 @@ +document.body.innerHTML = ` +

+	
+ + +
+

Computing fibonacci without worker:

+ +

+	

Computing fibonacci with worker:

+ +

+`;
+
+const history = document.getElementById("history");
+const message = document.getElementById("message");
+const send = document.getElementById("send");
+const fib1 = document.getElementById("fib1");
+const output1 = document.getElementById("output1");
+const fib2 = document.getElementById("fib2");
+const output2 = document.getElementById("output2");
+
+/// CHAT with shared worker ///
+
+const chatWorker = new SharedWorker(
+	new URL("./chat-worker.js", import.meta.url),
+	{
+		name: "chat",
+		type: "module"
+	}
+);
+
+let historyTimeout;
+const scheduleUpdateHistory = () => {
+	clearTimeout(historyTimeout);
+	historyTimeout = setTimeout(() => {
+		chatWorker.port.postMessage({ type: "history" });
+	}, 1000);
+};
+scheduleUpdateHistory();
+
+const from = `User ${Math.floor(Math.random() * 10000)}`;
+
+send.addEventListener("click", e => {
+	chatWorker.port.postMessage({
+		type: "message",
+		content: message.value,
+		from
+	});
+	message.value = "";
+	message.focus();
+	e.preventDefault();
+});
+
+chatWorker.port.onmessage = event => {
+	const msg = event.data;
+	switch (msg.type) {
+		case "history":
+			history.innerText = msg.history.join("\n");
+			scheduleUpdateHistory();
+			break;
+	}
+};
+
+/// FIBONACCI without worker ///
+
+fib1.addEventListener("change", async () => {
+	try {
+		const value = parseInt(fib1.value, 10);
+		const { fibonacci } = await import("./fibonacci");
+		const result = fibonacci(value);
+		output1.innerText = `fib(${value}) = ${result}`;
+	} catch (e) {
+		output1.innerText = e.message;
+	}
+});
+
+/// FIBONACCI with worker ///
+
+const fibWorker = new Worker(new URL("./fib-worker.js", import.meta.url), {
+	name: "fibonacci",
+	type: "module"
+	/* webpackEntryOptions: { filename: "workers/[name].js" } */
+});
+
+fib2.addEventListener("change", () => {
+	try {
+		const value = parseInt(fib2.value, 10);
+		fibWorker.postMessage(`${value}`);
+	} catch (e) {
+		output2.innerText = e.message;
+	}
+});
+
+fibWorker.onmessage = event => {
+	output2.innerText = event.data;
+};
diff --git a/examples/worker/fib-worker.js b/examples/worker/fib-worker.js
new file mode 100644
index 00000000000..42efa83cf4e
--- /dev/null
+++ b/examples/worker/fib-worker.js
@@ -0,0 +1,5 @@
+onmessage = async event => {
+	const { fibonacci } = await import("./fibonacci");
+	const value = JSON.parse(event.data);
+	postMessage(`fib(${value}) = ${fibonacci(value)}`);
+};
diff --git a/examples/worker/fibonacci.js b/examples/worker/fibonacci.js
new file mode 100644
index 00000000000..282fcec2fca
--- /dev/null
+++ b/examples/worker/fibonacci.js
@@ -0,0 +1,3 @@
+export function fibonacci(n) {
+	return n < 1 ? 0 : n <= 2 ? 1 : fibonacci(n - 1) + fibonacci(n - 2);
+}
diff --git a/examples/worker/index.html b/examples/worker/index.html
new file mode 100644
index 00000000000..fd8adb597c8
--- /dev/null
+++ b/examples/worker/index.html
@@ -0,0 +1,10 @@
+
+
+	
+		
+		Worker example
+	
+	
+		
+	
+
diff --git a/examples/worker/template.md b/examples/worker/template.md
new file mode 100644
index 00000000000..6a93ddfd9b5
--- /dev/null
+++ b/examples/worker/template.md
@@ -0,0 +1,75 @@
+# example.js
+
+```javascript
+_{{example.js}}_
+```
+
+# fib-worker.js
+
+```javascript
+_{{fib-worker.js}}_
+```
+
+# fibonacci.js
+
+```javascript
+_{{fibonacci.js}}_
+```
+
+# chat-worker.js
+
+```javascript
+_{{chat-worker.js}}_
+```
+
+# chat-module.js
+
+```javascript
+_{{chat-module.js}}_
+```
+
+# dist/main.js
+
+```javascript
+_{{dist/main.js}}_
+```
+
+# dist/chat.js
+
+```javascript
+_{{dist/chat.js}}_
+```
+
+```javascript
+_{{production:dist/chat.js}}_
+```
+
+# dist/workers/fibonacci.js
+
+```javascript
+_{{dist/workers/fibonacci.js}}_
+```
+
+```javascript
+_{{production:dist/workers/fibonacci.js}}_
+```
+
+# dist/129.js
+
+```javascript
+_{{dist/129.js}}_
+```
+
+# Info
+
+## Unoptimized
+
+```
+_{{stdout}}_
+```
+
+## Production mode
+
+```
+_{{production:stdout}}_
+```
diff --git a/examples/worker/webpack.config.js b/examples/worker/webpack.config.js
new file mode 100644
index 00000000000..08ae0d171de
--- /dev/null
+++ b/examples/worker/webpack.config.js
@@ -0,0 +1,22 @@
+"use strict";
+
+const path = require("path");
+
+/** @type {import("webpack").Configuration} */
+const config = {
+	entry: "./example.js",
+	output: {
+		path: path.join(__dirname, "dist"),
+		filename: "[name].js",
+		chunkFilename: "[name].js",
+		publicPath: "/dist/"
+	},
+	optimization: {
+		concatenateModules: true,
+		usedExports: true,
+		providedExports: true,
+		chunkIds: "deterministic" // To keep filename consistent between different modes (for example building only)
+	}
+};
+
+module.exports = config;
diff --git a/generate-types-config.js b/generate-types-config.js
new file mode 100644
index 00000000000..89205e3496b
--- /dev/null
+++ b/generate-types-config.js
@@ -0,0 +1,12 @@
+"use strict";
+
+module.exports = {
+	nameMapping: {
+		FsStats: /^Stats Import fs/,
+		validateFunction: /^validate Import/,
+		Configuration: /^WebpackOptions /,
+		MultiConfiguration: /^MultiWebpackOptions /
+	},
+	exclude: [/^devServer in WebpackOptions /],
+	include: [/^(_module|_compilation|_compiler) in NormalModuleLoaderContext /]
+};
diff --git a/hot/dev-server.js b/hot/dev-server.js
index a4cb0f50d24..2e493e04bc8 100644
--- a/hot/dev-server.js
+++ b/hot/dev-server.js
@@ -2,24 +2,35 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
-/*globals window __webpack_hash__ */
+/* globals __webpack_hash__ */
+// Universal regular-HMR client (web + Node): runs `module.hot.check` whenever a
+// `webpackHotUpdate` signal arrives on ./emitter (pushed by the dev-server).
 if (module.hot) {
+	/** @type {undefined|string} */
 	var lastHash;
 	var upToDate = function upToDate() {
-		return lastHash.indexOf(__webpack_hash__) >= 0;
+		return /** @type {string} */ (lastHash).indexOf(__webpack_hash__) >= 0;
 	};
 	var log = require("./log");
 	var check = function check() {
 		module.hot
 			.check(true)
-			.then(function(updatedModules) {
+			.then(function (updatedModules) {
 				if (!updatedModules) {
-					log("warning", "[HMR] Cannot find update. Need to do a full reload!");
+					log(
+						"warning",
+						"[HMR] Cannot find update. " +
+							(typeof window !== "undefined"
+								? "Need to do a full reload!"
+								: "Please reload manually!")
+					);
 					log(
 						"warning",
 						"[HMR] (Probably because of restarting the webpack-dev-server)"
 					);
-					window.location.reload();
+					if (typeof window !== "undefined") {
+						window.location.reload();
+					}
 					return;
 				}
 
@@ -33,28 +44,48 @@ if (module.hot) {
 					log("info", "[HMR] App is up to date.");
 				}
 			})
-			.catch(function(err) {
+			.catch(function (err) {
 				var status = module.hot.status();
 				if (["abort", "fail"].indexOf(status) >= 0) {
 					log(
 						"warning",
-						"[HMR] Cannot apply update. Need to do a full reload!"
+						"[HMR] Cannot apply update. " +
+							(typeof window !== "undefined"
+								? "Need to do a full reload!"
+								: "Please reload manually!")
 					);
-					log("warning", "[HMR] " + err.stack || err.message);
-					window.location.reload();
+					log("warning", "[HMR] " + log.formatError(err));
+					if (typeof window !== "undefined") {
+						window.location.reload();
+					}
 				} else {
-					log("warning", "[HMR] Update failed: " + err.stack || err.message);
+					log("warning", "[HMR] Update failed: " + log.formatError(err));
 				}
 			});
 	};
+	/** @type {EventTarget | NodeJS.EventEmitter} */
 	var hotEmitter = require("./emitter");
-	hotEmitter.on("webpackHotUpdate", function(currentHash) {
-		lastHash = currentHash;
+	/**
+	 * @param {CustomEvent<{ currentHash: string }>} event event or hash
+	 */
+	var handler = function (event) {
+		lastHash = typeof event === "string" ? event : event.detail.currentHash;
 		if (!upToDate() && module.hot.status() === "idle") {
 			log("info", "[HMR] Checking for updates on the server...");
 			check();
 		}
-	});
+	};
+
+	if (typeof EventTarget !== "undefined" && hotEmitter instanceof EventTarget) {
+		hotEmitter.addEventListener(
+			"webpackHotUpdate",
+			/** @type {EventListener} */
+			(handler)
+		);
+	} else {
+		hotEmitter.on("webpackHotUpdate", handler);
+	}
+
 	log("info", "[HMR] Waiting for update signal from WDS...");
 } else {
 	throw new Error("[HMR] Hot Module Replacement is disabled.");
diff --git a/hot/emitter-event-target.js b/hot/emitter-event-target.js
new file mode 100644
index 00000000000..375d9c4b55f
--- /dev/null
+++ b/hot/emitter-event-target.js
@@ -0,0 +1,7 @@
+if (typeof EventTarget !== "function") {
+	throw new Error(
+		"Environment doesn't support lazy compilation (requires EventTarget)"
+	);
+}
+
+module.exports = new EventTarget();
diff --git a/hot/lazy-compilation-node.js b/hot/lazy-compilation-node.js
new file mode 100644
index 00000000000..88b4863a427
--- /dev/null
+++ b/hot/lazy-compilation-node.js
@@ -0,0 +1,66 @@
+"use strict";
+
+/* global __resourceQuery */
+
+var urlBase = decodeURIComponent(__resourceQuery.slice(1));
+
+/**
+ * @param {{ data: string, onError: (err: Error) => void, active: boolean, module: module }} options options
+ * @returns {() => void} function to destroy response
+ */
+exports.keepAlive = function (options) {
+	var data = options.data;
+
+	/**
+	 * @param {Error} err error
+	 */
+	function errorHandler(err) {
+		err.message =
+			"Problem communicating active modules to the server: " + err.message;
+		options.onError(err);
+	}
+
+	/** @type {Promise} */
+	var mod = require("./load-http")(urlBase.startsWith("https"));
+
+	/** @type {import("http").ClientRequest} */
+	var request;
+	/** @type {import("http").IncomingMessage} */
+	var response;
+
+	mod.then(function (client) {
+		request = client.request(
+			urlBase + data,
+			{
+				agent: false,
+				headers: { accept: "text/event-stream" }
+			},
+			function (res) {
+				response = res;
+				response.on("error", errorHandler);
+
+				if (!options.active && !options.module.hot) {
+					console.log(
+						"Hot Module Replacement is not enabled. Waiting for process restart..."
+					);
+				}
+			}
+		);
+
+		request.on("error", errorHandler);
+		request.end();
+	});
+
+	return function () {
+		if (response) {
+			response.destroy();
+		}
+	};
+};
+
+/**
+ * @param {string} value new url value
+ */
+exports.setUrl = function (value) {
+	urlBase = value;
+};
diff --git a/hot/lazy-compilation-universal.js b/hot/lazy-compilation-universal.js
new file mode 100644
index 00000000000..dc2502aa52d
--- /dev/null
+++ b/hot/lazy-compilation-universal.js
@@ -0,0 +1,18 @@
+"use strict";
+
+/* global __resourceQuery */
+
+var isNodeLikeEnv =
+	typeof global !== "undefined" && typeof global.process !== "undefined";
+
+var handler = isNodeLikeEnv
+	? require("./lazy-compilation-node")
+	: require("./lazy-compilation-web");
+
+handler.setUrl(decodeURIComponent(__resourceQuery.slice(1)));
+
+/**
+ * @param {{ data: string, onError: (err: Error) => void, active: boolean, module: module }} options options
+ * @returns {() => void} function to destroy response
+ */
+module.exports = handler;
diff --git a/hot/lazy-compilation-web.js b/hot/lazy-compilation-web.js
new file mode 100644
index 00000000000..04c26bb1872
--- /dev/null
+++ b/hot/lazy-compilation-web.js
@@ -0,0 +1,93 @@
+"use strict";
+
+/* global __resourceQuery */
+
+if (typeof EventSource !== "function") {
+	throw new Error(
+		"Environment doesn't support lazy compilation (requires EventSource)"
+	);
+}
+
+var urlBase = decodeURIComponent(__resourceQuery.slice(1));
+/** @type {EventSource | undefined} */
+var activeEventSource;
+var activeKeys = new Map();
+var errorHandlers = new Set();
+
+var updateEventSource = function updateEventSource() {
+	if (activeEventSource) activeEventSource.close();
+	if (activeKeys.size) {
+		activeEventSource = new EventSource(
+			urlBase + Array.from(activeKeys.keys()).join("@")
+		);
+		/**
+		 * @this {EventSource}
+		 * @param {Event & { message?: string, filename?: string, lineno?: number, colno?: number, error?: Error }} event event
+		 */
+		activeEventSource.onerror = function (event) {
+			errorHandlers.forEach(function (onError) {
+				onError(
+					new Error(
+						"Problem communicating active modules to the server: " +
+							event.message +
+							" " +
+							event.filename +
+							":" +
+							event.lineno +
+							":" +
+							event.colno +
+							" " +
+							event.error
+					)
+				);
+			});
+		};
+	} else {
+		activeEventSource = undefined;
+	}
+};
+
+/**
+ * @param {{ data: string, onError: (err: Error) => void, active: boolean, module: module }} options options
+ * @returns {() => void} function to destroy response
+ */
+exports.keepAlive = function (options) {
+	var data = options.data;
+	var onError = options.onError;
+
+	errorHandlers.add(onError);
+
+	var value = activeKeys.get(data) || 0;
+
+	activeKeys.set(data, value + 1);
+
+	if (value === 0) {
+		updateEventSource();
+	}
+
+	if (!options.active && !options.module.hot) {
+		console.log(
+			"Hot Module Replacement is not enabled. Waiting for process restart..."
+		);
+	}
+
+	return function () {
+		errorHandlers.delete(onError);
+		setTimeout(function () {
+			var value = activeKeys.get(data);
+			if (value === 1) {
+				activeKeys.delete(data);
+				updateEventSource();
+			} else {
+				activeKeys.set(data, value - 1);
+			}
+		}, 1000);
+	};
+};
+
+/**
+ * @param {string} value new url value
+ */
+exports.setUrl = function (value) {
+	urlBase = value;
+};
diff --git a/hot/load-http.js b/hot/load-http.js
new file mode 100644
index 00000000000..641076eec68
--- /dev/null
+++ b/hot/load-http.js
@@ -0,0 +1,7 @@
+/**
+ * @param {boolean} isHTTPS true when need https module, otherwise false
+ * @returns {Promise}
+ */
+module.exports = function (isHTTPS) {
+	return isHTTPS ? import("https") : import("http");
+};
diff --git a/hot/log-apply-result.js b/hot/log-apply-result.js
index b63e757418d..cb46366dd44 100644
--- a/hot/log-apply-result.js
+++ b/hot/log-apply-result.js
@@ -2,8 +2,13 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
-module.exports = function(updatedModules, renewedModules) {
-	var unacceptedModules = updatedModules.filter(function(moduleId) {
+
+/**
+ * @param {(string | number)[]} updatedModules updated modules
+ * @param {(string | number)[] | null} renewedModules renewed modules
+ */
+module.exports = function (updatedModules, renewedModules) {
+	var unacceptedModules = updatedModules.filter(function (moduleId) {
 		return renewedModules && renewedModules.indexOf(moduleId) < 0;
 	});
 	var log = require("./log");
@@ -13,7 +18,7 @@ module.exports = function(updatedModules, renewedModules) {
 			"warning",
 			"[HMR] The following modules couldn't be hot updated: (They would need a full reload!)"
 		);
-		unacceptedModules.forEach(function(moduleId) {
+		unacceptedModules.forEach(function (moduleId) {
 			log("warning", "[HMR]  - " + moduleId);
 		});
 	}
@@ -22,7 +27,7 @@ module.exports = function(updatedModules, renewedModules) {
 		log("info", "[HMR] Nothing hot updated.");
 	} else {
 		log("info", "[HMR] Updated modules:");
-		renewedModules.forEach(function(moduleId) {
+		renewedModules.forEach(function (moduleId) {
 			if (typeof moduleId === "string" && moduleId.indexOf("!") !== -1) {
 				var parts = moduleId.split("!");
 				log.groupCollapsed("info", "[HMR]  - " + parts.pop());
@@ -32,13 +37,13 @@ module.exports = function(updatedModules, renewedModules) {
 				log("info", "[HMR]  - " + moduleId);
 			}
 		});
-		var numberIds = renewedModules.every(function(moduleId) {
+		var numberIds = renewedModules.every(function (moduleId) {
 			return typeof moduleId === "number";
 		});
 		if (numberIds)
 			log(
 				"info",
-				"[HMR] Consider using the NamedModulesPlugin for module names."
+				'[HMR] Consider using the optimization.moduleIds: "named" for module names.'
 			);
 	}
 };
diff --git a/hot/log.js b/hot/log.js
index d9e09b221de..d3e46e1bf86 100644
--- a/hot/log.js
+++ b/hot/log.js
@@ -1,7 +1,14 @@
+/** @typedef {"info" | "warning" | "error"} LogLevel */
+
+/** @type {LogLevel} */
 var logLevel = "info";
 
 function dummy() {}
 
+/**
+ * @param {LogLevel} level log level
+ * @returns {boolean} true, if should log
+ */
 function shouldLog(level) {
 	var shouldLog =
 		(logLevel === "info" && level === "info") ||
@@ -10,15 +17,23 @@ function shouldLog(level) {
 	return shouldLog;
 }
 
+/**
+ * @param {(msg?: string) => void} logFn log function
+ * @returns {(level: LogLevel, msg?: string) => void} function that logs when log level is sufficient
+ */
 function logGroup(logFn) {
-	return function(level, msg) {
+	return function (level, msg) {
 		if (shouldLog(level)) {
 			logFn(msg);
 		}
 	};
 }
 
-module.exports = function(level, msg) {
+/**
+ * @param {LogLevel} level log level
+ * @param {string|Error} msg message
+ */
+module.exports = function (level, msg) {
 	if (shouldLog(level)) {
 		if (level === "info") {
 			console.log(msg);
@@ -30,6 +45,21 @@ module.exports = function(level, msg) {
 	}
 };
 
+/**
+ * @param {Error} err error
+ * @returns {string} formatted error
+ */
+module.exports.formatError = function (err) {
+	var message = err.message;
+	var stack = err.stack;
+	if (!stack) {
+		return message;
+	} else if (stack.indexOf(message) < 0) {
+		return message + "\n" + stack;
+	}
+	return stack;
+};
+
 var group = console.group || dummy;
 var groupCollapsed = console.groupCollapsed || dummy;
 var groupEnd = console.groupEnd || dummy;
@@ -40,6 +70,9 @@ module.exports.groupCollapsed = logGroup(groupCollapsed);
 
 module.exports.groupEnd = logGroup(groupEnd);
 
-module.exports.setLogLevel = function(level) {
+/**
+ * @param {LogLevel} level log level
+ */
+module.exports.setLogLevel = function (level) {
 	logLevel = level;
 };
diff --git a/hot/only-dev-server.js b/hot/only-dev-server.js
index cf452dc6e33..785dd33d461 100644
--- a/hot/only-dev-server.js
+++ b/hot/only-dev-server.js
@@ -2,17 +2,18 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
-/*globals __webpack_hash__ */
+/* globals __webpack_hash__ */
 if (module.hot) {
+	/** @type {undefined | string} */
 	var lastHash;
 	var upToDate = function upToDate() {
-		return lastHash.indexOf(__webpack_hash__) >= 0;
+		return /** @type {string} */ (lastHash).indexOf(__webpack_hash__) >= 0;
 	};
 	var log = require("./log");
 	var check = function check() {
 		module.hot
 			.check()
-			.then(function(updatedModules) {
+			.then(function (updatedModules) {
 				if (!updatedModules) {
 					log("warning", "[HMR] Cannot find update. Need to do a full reload!");
 					log(
@@ -27,21 +28,21 @@ if (module.hot) {
 						ignoreUnaccepted: true,
 						ignoreDeclined: true,
 						ignoreErrored: true,
-						onUnaccepted: function(data) {
+						onUnaccepted: function (data) {
 							log(
 								"warning",
 								"Ignored an update to unaccepted module " +
 									data.chain.join(" -> ")
 							);
 						},
-						onDeclined: function(data) {
+						onDeclined: function (data) {
 							log(
 								"warning",
 								"Ignored an update to declined module " +
 									data.chain.join(" -> ")
 							);
 						},
-						onErrored: function(data) {
+						onErrored: function (data) {
 							log("error", data.error);
 							log(
 								"warning",
@@ -53,7 +54,7 @@ if (module.hot) {
 							);
 						}
 					})
-					.then(function(renewedModules) {
+					.then(function (renewedModules) {
 						if (!upToDate()) {
 							check();
 						}
@@ -65,25 +66,26 @@ if (module.hot) {
 						}
 					});
 			})
-			.catch(function(err) {
+			.catch(function (err) {
 				var status = module.hot.status();
 				if (["abort", "fail"].indexOf(status) >= 0) {
 					log(
 						"warning",
 						"[HMR] Cannot check for update. Need to do a full reload!"
 					);
-					log("warning", "[HMR] " + err.stack || err.message);
+					log("warning", "[HMR] " + log.formatError(err));
 				} else {
-					log(
-						"warning",
-						"[HMR] Update check failed: " + err.stack || err.message
-					);
+					log("warning", "[HMR] Update check failed: " + log.formatError(err));
 				}
 			});
 	};
+	/** @type {EventTarget | NodeJS.EventEmitter} */
 	var hotEmitter = require("./emitter");
-	hotEmitter.on("webpackHotUpdate", function(currentHash) {
-		lastHash = currentHash;
+	/**
+	 * @param {CustomEvent<{ currentHash: string }>} event event or hash
+	 */
+	var handler = function (event) {
+		lastHash = typeof event === "string" ? event : event.detail.currentHash;
 		if (!upToDate()) {
 			var status = module.hot.status();
 			if (status === "idle") {
@@ -98,7 +100,18 @@ if (module.hot) {
 				);
 			}
 		}
-	});
+	};
+
+	if (typeof EventTarget !== "undefined" && hotEmitter instanceof EventTarget) {
+		hotEmitter.addEventListener(
+			"webpackHotUpdate",
+			/** @type {EventListener} */
+			(handler)
+		);
+	} else {
+		hotEmitter.on("webpackHotUpdate", handler);
+	}
+
 	log("info", "[HMR] Waiting for update signal from WDS...");
 } else {
 	throw new Error("[HMR] Hot Module Replacement is disabled.");
diff --git a/hot/poll.js b/hot/poll.js
index f615accef7a..a35693cfa6f 100644
--- a/hot/poll.js
+++ b/hot/poll.js
@@ -2,16 +2,20 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
-/*globals __resourceQuery */
+/* globals __resourceQuery */
 if (module.hot) {
-	var hotPollInterval = +__resourceQuery.substr(1) || 10 * 60 * 1000;
+	// eslint-disable-next-line no-implicit-coercion
+	var hotPollInterval = +__resourceQuery.slice(1) || 10 * 60 * 1000;
 	var log = require("./log");
 
+	/**
+	 * @param {boolean=} fromUpdate true when called from update
+	 */
 	var checkForUpdate = function checkForUpdate(fromUpdate) {
 		if (module.hot.status() === "idle") {
 			module.hot
 				.check(true)
-				.then(function(updatedModules) {
+				.then(function (updatedModules) {
 					if (!updatedModules) {
 						if (fromUpdate) log("info", "[HMR] Update applied.");
 						return;
@@ -19,14 +23,14 @@ if (module.hot) {
 					require("./log-apply-result")(updatedModules, updatedModules);
 					checkForUpdate(true);
 				})
-				.catch(function(err) {
+				.catch(function (err) {
 					var status = module.hot.status();
 					if (["abort", "fail"].indexOf(status) >= 0) {
 						log("warning", "[HMR] Cannot apply update.");
-						log("warning", "[HMR] " + err.stack || err.message);
+						log("warning", "[HMR] " + log.formatError(err));
 						log("warning", "[HMR] You need to restart the application!");
 					} else {
-						log("warning", "[HMR] Update failed: " + err.stack || err.message);
+						log("warning", "[HMR] Update failed: " + log.formatError(err));
 					}
 				});
 		}
diff --git a/hot/signal.js b/hot/signal.js
index d3ce50e8cc5..36a0cbe38c7 100644
--- a/hot/signal.js
+++ b/hot/signal.js
@@ -2,13 +2,17 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
-/*globals __resourceQuery */
+/* globals __resourceQuery */
 if (module.hot) {
 	var log = require("./log");
+
+	/**
+	 * @param {boolean=} fromUpdate true when called from update
+	 */
 	var checkForUpdate = function checkForUpdate(fromUpdate) {
 		module.hot
 			.check()
-			.then(function(updatedModules) {
+			.then(function (updatedModules) {
 				if (!updatedModules) {
 					if (fromUpdate) log("info", "[HMR] Update applied.");
 					else log("warning", "[HMR] Cannot find update.");
@@ -18,7 +22,7 @@ if (module.hot) {
 				return module.hot
 					.apply({
 						ignoreUnaccepted: true,
-						onUnaccepted: function(data) {
+						onUnaccepted: function (data) {
 							log(
 								"warning",
 								"Ignored an update to unaccepted module " +
@@ -26,26 +30,26 @@ if (module.hot) {
 							);
 						}
 					})
-					.then(function(renewedModules) {
+					.then(function (renewedModules) {
 						require("./log-apply-result")(updatedModules, renewedModules);
 
 						checkForUpdate(true);
 						return null;
 					});
 			})
-			.catch(function(err) {
+			.catch(function (err) {
 				var status = module.hot.status();
 				if (["abort", "fail"].indexOf(status) >= 0) {
 					log("warning", "[HMR] Cannot apply update.");
-					log("warning", "[HMR] " + err.stack || err.message);
+					log("warning", "[HMR] " + log.formatError(err));
 					log("warning", "[HMR] You need to restart the application!");
 				} else {
-					log("warning", "[HMR] Update failed: " + err.stack || err.message);
+					log("warning", "[HMR] Update failed: " + (err.stack || err.message));
 				}
 			});
 	};
 
-	process.on(__resourceQuery.substr(1) || "SIGUSR2", function() {
+	process.on(__resourceQuery.slice(1) || "SIGUSR2", function () {
 		if (module.hot.status() !== "idle") {
 			log(
 				"warning",
diff --git a/jest.config.js b/jest.config.js
new file mode 100644
index 00000000000..9d973864c3c
--- /dev/null
+++ b/jest.config.js
@@ -0,0 +1,69 @@
+"use strict";
+
+/** @type {import("jest").Config} */
+const config = {
+	testTimeout: 30000,
+	prettierPath: require.resolve("prettier-2"),
+	forceExit: true,
+	setupFiles: [
+		"/test/bun-sandbox-setup.js",
+		"/test/deno-worker-setup.js"
+	],
+	setupFilesAfterEnv: ["/test/setupTestFramework.js"],
+	testMatch: [
+		"/test/*.test.js",
+		"/test/*.basictest.js",
+		"/test/*.longtest.js",
+		"/test/*.unittest.js",
+		"/test/*.spectest.js"
+	],
+	watchPathIgnorePatterns: [
+		"/.git",
+		"/node_modules",
+		"/test/wpt",
+		"/test/js",
+		"/test/browsertest/js",
+		"/test/fixtures/temp-cache-fixture",
+		"/test/fixtures/temp-",
+		"/benchmark",
+		"/assembly",
+		"/tooling",
+		"/examples/*/dist",
+		"/coverage",
+		"/.eslintcache"
+	],
+	modulePathIgnorePatterns: [
+		"/.git",
+		"/test/wpt",
+		"/node_modules/webpack/node_modules",
+		"/test/js",
+		"/test/browsertest/js",
+		"/test/fixtures/temp-cache-fixture",
+		"/test/fixtures/temp-",
+		"/benchmark",
+		"/examples/*/dist",
+		"/coverage",
+		"/.eslintcache"
+	],
+	transformIgnorePatterns: [""],
+	coverageDirectory: "/coverage",
+	coveragePathIgnorePatterns: [
+		"\\.runtime\\.js$",
+		// Serialized to the minimizer's worker pool as source (like runtime code):
+		// coverage instrumentation would inject counters the worker can't resolve.
+		"[\\\\/]lib[\\\\/](?:css[\\\\/]cssMinify|html[\\\\/]htmlMinify)\\.js$",
+		"/test",
+		"/schemas",
+		"/examples",
+		"/node_modules"
+	],
+	testEnvironment: "./test/harness/patch-node-env.js",
+	snapshotResolver: "./test/harness/snapshot/resolver.js",
+	coverageReporters: ["json"],
+	snapshotFormat: {
+		escapeString: true,
+		printBasicPrototype: true
+	}
+};
+
+module.exports = config;
diff --git a/lib/APIPlugin.js b/lib/APIPlugin.js
index 6cd80646dcd..6b91558f380 100644
--- a/lib/APIPlugin.js
+++ b/lib/APIPlugin.js
@@ -2,83 +2,520 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
+const {
+	getExternalModuleNodeCommonjsInitFragment
+} = require("./ExternalModule");
+const {
+	JAVASCRIPT_MODULE_TYPE_AUTO,
+	JAVASCRIPT_MODULE_TYPE_DYNAMIC,
+	JAVASCRIPT_MODULE_TYPE_ESM
+} = require("./ModuleTypeConstants");
+const RuntimeGlobals = require("./RuntimeGlobals");
 const ConstDependency = require("./dependencies/ConstDependency");
-const ParserHelpers = require("./ParserHelpers");
-
-const NullFactory = require("./NullFactory");
-
-/* eslint-disable camelcase */
-const REPLACEMENTS = {
-	__webpack_require__: "__webpack_require__",
-	__webpack_public_path__: "__webpack_require__.p",
-	__webpack_modules__: "__webpack_require__.m",
-	__webpack_chunk_load__: "__webpack_require__.e",
-	__non_webpack_require__: "require",
-	__webpack_nonce__: "__webpack_require__.nc",
-	"require.onError": "__webpack_require__.oe"
-};
-const NO_WEBPACK_REQUIRE = {
-	__non_webpack_require__: true
+const ModuleInitFragmentDependency = require("./dependencies/ModuleInitFragmentDependency");
+const RuntimeRequirementsDependency = require("./dependencies/RuntimeRequirementsDependency");
+const WebpackError = require("./errors/WebpackError");
+const BasicEvaluatedExpression = require("./javascript/BasicEvaluatedExpression");
+const JavascriptModulesPlugin = require("./javascript/JavascriptModulesPlugin");
+const {
+	evaluateToString,
+	toConstantDependency
+} = require("./javascript/JavascriptParserHelpers");
+const ChunkNameRuntimeModule = require("./runtime/ChunkNameRuntimeModule");
+const GetFullHashRuntimeModule = require("./runtime/GetFullHashRuntimeModule");
+const memoize = require("./util/memoize");
+const { forEachRuntime } = require("./util/runtime");
+
+const getConcatenatedModule = memoize(() =>
+	require("./optimize/ConcatenatedModule")
+);
+
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */
+/** @typedef {import("./Module").BuildInfo} BuildInfo */
+/** @typedef {import("./javascript/JavascriptModule").JavascriptModuleBuildInfo} JavascriptModuleBuildInfo */
+/** @typedef {import("./Compilation")} Compilation */
+/** @typedef {import("./ChunkGraph")} ChunkGraph */
+/** @typedef {import("./Module")} Module */
+/** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */
+/** @typedef {import("./javascript/JavascriptParser").Range} Range */
+
+// Modules that reassign `__webpack_public_path__` at runtime, by compilation. A baked
+// analyzable specifier can't reflect that, so those forms fall back. Also recorded per
+// module in `buildInfo`, because a module restored from the persistent cache is never
+// re-parsed and would otherwise lose the flag.
+/** @type {WeakMap>} */
+const runtimePublicPathOverride = new WeakMap();
+
+/**
+ * @param {Compilation} compilation compilation
+ * @param {Module} module the module doing the reassigning
+ * @returns {void}
+ */
+const addRuntimePublicPathOverride = (compilation, module) => {
+	const modules = runtimePublicPathOverride.get(compilation);
+	if (modules === undefined) {
+		runtimePublicPathOverride.set(compilation, new Set([module]));
+	} else {
+		modules.add(module);
+	}
 };
-const REPLACEMENT_TYPES = {
-	__webpack_public_path__: "string",
-	__webpack_require__: "function",
-	__webpack_modules__: "object",
-	__webpack_chunk_load__: "function",
-	__webpack_nonce__: "string"
+
+/**
+ * @param {Compilation} compilation compilation
+ * @returns {boolean} true when any module reassigns `__webpack_public_path__` at runtime
+ */
+const usesRuntimePublicPathOverride = (compilation) =>
+	runtimePublicPathOverride.has(compilation);
+
+// `__webpack_require__.p` belongs to a runtime, so a reassignment only reaches the
+// runtimes the reassigning module is instantiated in. Computed once the chunk graph
+// can answer that, which is any time code is being generated.
+/** @type {WeakMap>} */
+const overriddenRuntimes = new WeakMap();
+
+/**
+ * Whether the public path is reassigned in any runtime `module` belongs to. Falls back
+ * to the whole compilation when there is no chunk graph to place the module in.
+ * @param {Compilation} compilation compilation
+ * @param {ChunkGraph=} chunkGraph the chunk graph
+ * @param {Module=} module the module a reference is emitted into
+ * @returns {boolean} true when a reassignment can reach this module's runtimes
+ */
+const runtimeUsesPublicPathOverride = (compilation, chunkGraph, module) => {
+	const modules = runtimePublicPathOverride.get(compilation);
+	if (modules === undefined) return false;
+	if (chunkGraph === undefined || module === undefined) return true;
+	// Keyed by the chunk graph, not the compilation: `executeModule` asks with a
+	// throw-away one whose answer must not outlive it.
+	let runtimes = overriddenRuntimes.get(chunkGraph);
+	if (runtimes === undefined) {
+		/** @type {Set} */
+		const collected = new Set();
+		const ConcatenatedModule = getConcatenatedModule();
+		for (const overriding of modules) {
+			// Concatenation may have absorbed the reassigning module, and the chunk graph
+			// places only the `ConcatenatedModule` that replaced it.
+			for (const runtime of chunkGraph.getModuleRuntimes(
+				ConcatenatedModule.getChunkGraphModule(compilation, overriding)
+			)) {
+				forEachRuntime(runtime, (key) => {
+					collected.add(/** @type {string} */ (key));
+				});
+			}
+		}
+		runtimes = collected;
+		overriddenRuntimes.set(chunkGraph, collected);
+	}
+	const overridden = runtimes;
+	if (overridden.size === 0) return false;
+	/** @type {Set} */
+	const own = new Set();
+	for (const runtime of chunkGraph.getModuleRuntimes(module)) {
+		forEachRuntime(runtime, (key) => {
+			own.add(/** @type {string} */ (key));
+		});
+	}
+	for (const key of own) {
+		if (overridden.has(key)) return true;
+	}
+	return false;
 };
-/* eslint-enable camelcase */
+
+/**
+ * Returns the replacement definitions used for webpack API identifiers.
+ * @returns {Record} replacements
+ */
+function getReplacements() {
+	return {
+		__webpack_require__: {
+			expr: RuntimeGlobals.require,
+			req: [RuntimeGlobals.require],
+			type: "function",
+			assign: false
+		},
+		__webpack_global__: {
+			expr: RuntimeGlobals.require,
+			req: [RuntimeGlobals.require],
+			type: "function",
+			assign: false
+		},
+		__webpack_public_path__: {
+			expr: RuntimeGlobals.publicPath,
+			req: [RuntimeGlobals.publicPath],
+			type: "string",
+			assign: true
+		},
+		__webpack_base_uri__: {
+			expr: RuntimeGlobals.baseURI,
+			req: [RuntimeGlobals.baseURI],
+			type: "string",
+			assign: true
+		},
+		__webpack_modules__: {
+			expr: RuntimeGlobals.moduleFactories,
+			req: [RuntimeGlobals.moduleFactories],
+			type: "object",
+			assign: false
+		},
+		__webpack_chunk_load__: {
+			expr: RuntimeGlobals.ensureChunk,
+			req: [RuntimeGlobals.ensureChunk],
+			type: "function",
+			assign: true
+		},
+		__non_webpack_require__: {
+			expr: "require",
+			req: null,
+			type: undefined, // type is not known, depends on environment
+			assign: true
+		},
+		__webpack_nonce__: {
+			expr: RuntimeGlobals.scriptNonce,
+			req: [RuntimeGlobals.scriptNonce],
+			type: "string",
+			assign: true
+		},
+		__webpack_hash__: {
+			expr: `${RuntimeGlobals.getFullHash}()`,
+			req: [RuntimeGlobals.getFullHash],
+			type: "string",
+			assign: false
+		},
+		__webpack_css_server_styles__: {
+			expr: `${RuntimeGlobals.getCssServerStyles}()`,
+			req: [RuntimeGlobals.getCssServerStyles],
+			type: "string",
+			assign: false
+		},
+		__webpack_chunkname__: {
+			expr: RuntimeGlobals.chunkName,
+			req: [RuntimeGlobals.chunkName],
+			type: "string",
+			assign: false
+		},
+		__webpack_get_script_filename__: {
+			expr: RuntimeGlobals.getChunkScriptFilename,
+			req: [RuntimeGlobals.getChunkScriptFilename],
+			type: "function",
+			assign: true
+		},
+		__webpack_runtime_id__: {
+			expr: RuntimeGlobals.runtimeId,
+			req: [RuntimeGlobals.runtimeId],
+			assign: false
+		},
+		"require.onError": {
+			expr: RuntimeGlobals.uncaughtErrorHandler,
+			req: [RuntimeGlobals.uncaughtErrorHandler],
+			type: undefined, // type is not known, could be function or undefined
+			assign: true // is never a pattern
+		},
+		__system_context__: {
+			expr: RuntimeGlobals.systemContext,
+			req: [RuntimeGlobals.systemContext],
+			type: "object",
+			assign: false
+		},
+		__webpack_share_scopes__: {
+			expr: RuntimeGlobals.shareScopeMap,
+			req: [RuntimeGlobals.shareScopeMap],
+			type: "object",
+			assign: false
+		},
+		__webpack_init_sharing__: {
+			expr: RuntimeGlobals.initializeSharing,
+			req: [RuntimeGlobals.initializeSharing],
+			type: "function",
+			assign: true
+		}
+	};
+}
+
+const PLUGIN_NAME = "APIPlugin";
 
 class APIPlugin {
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
 	apply(compiler) {
 		compiler.hooks.compilation.tap(
-			"APIPlugin",
+			PLUGIN_NAME,
 			(compilation, { normalModuleFactory }) => {
-				compilation.dependencyFactories.set(ConstDependency, new NullFactory());
+				const moduleOutput = compilation.options.output.module;
+				const nodeTarget = compiler.platform.node;
+				const nodeEsm = moduleOutput && nodeTarget;
+
+				const REPLACEMENTS = getReplacements();
+				if (nodeEsm) {
+					REPLACEMENTS.__non_webpack_require__.expr =
+						"__WEBPACK_EXTERNAL_createRequire_require";
+				}
+
+				// A cached module skips parsing, so replay its recorded override flag.
+				compilation.hooks.stillValidModule.tap(PLUGIN_NAME, (module) => {
+					const buildInfo =
+						/** @type {JavascriptModuleBuildInfo | undefined} */
+						(module.buildInfo);
+					if (buildInfo && buildInfo.usingPublicPathOverride) {
+						addRuntimePublicPathOverride(compilation, module);
+					}
+				});
+
 				compilation.dependencyTemplates.set(
 					ConstDependency,
 					new ConstDependency.Template()
 				);
+				compilation.dependencyTemplates.set(
+					ModuleInitFragmentDependency,
+					new ModuleInitFragmentDependency.Template()
+				);
 
-				const handler = parser => {
-					Object.keys(REPLACEMENTS).forEach(key => {
-						parser.hooks.expression
-							.for(key)
-							.tap(
-								"APIPlugin",
-								NO_WEBPACK_REQUIRE[key]
-									? ParserHelpers.toConstantDependency(
-											parser,
-											REPLACEMENTS[key]
-									  )
-									: ParserHelpers.toConstantDependencyWithWebpackRequire(
-											parser,
-											REPLACEMENTS[key]
-									  )
-							);
-						parser.hooks.evaluateTypeof
-							.for(key)
-							.tap(
-								"APIPlugin",
-								ParserHelpers.evaluateToString(REPLACEMENT_TYPES[key])
-							);
+				compilation.hooks.runtimeRequirementInTree
+					.for(RuntimeGlobals.chunkName)
+					.tap(PLUGIN_NAME, (chunk) => {
+						compilation.addRuntimeModule(
+							chunk,
+							new ChunkNameRuntimeModule(/** @type {string} */ (chunk.name))
+						);
+						return true;
+					});
+
+				compilation.hooks.runtimeRequirementInTree
+					.for(RuntimeGlobals.getFullHash)
+					.tap(PLUGIN_NAME, (chunk, _set) => {
+						compilation.addRuntimeModule(chunk, new GetFullHashRuntimeModule());
+						return true;
+					});
+
+				const hooks = JavascriptModulesPlugin.getCompilationHooks(compilation);
+
+				hooks.renderModuleContent.tap(
+					PLUGIN_NAME,
+					(source, module, renderContext) => {
+						if (
+							/** @type {JavascriptModuleBuildInfo} */ (module.buildInfo)
+								.needCreateRequire
+						) {
+							const chunkInitFragments = [
+								getExternalModuleNodeCommonjsInitFragment(
+									renderContext.runtimeTemplate
+								)
+							];
+
+							renderContext.chunkInitFragments.push(...chunkInitFragments);
+						}
+
+						return source;
+					}
+				);
+
+				/**
+				 * Handles the hook callback for this code path.
+				 * @param {JavascriptParser} parser the parser
+				 */
+				const handler = (parser) => {
+					parser.hooks.preDeclarator.tap(PLUGIN_NAME, (declarator) => {
+						if (
+							parser.scope.topLevelScope === true &&
+							declarator.id.type === "Identifier" &&
+							declarator.id.name === "module"
+						) {
+							/** @type {BuildInfo} */
+							(parser.state.module.buildInfo).moduleArgument =
+								"__webpack_module__";
+						}
 					});
+
+					/**
+					 * @param {import("estree").Statement | import("estree").ModuleDeclaration | import("estree").MaybeNamedFunctionDeclaration | import("estree").MaybeNamedClassDeclaration} statement statement
+					 */
+					const moduleDeclarationHandler = (statement) => {
+						if (
+							parser.scope.topLevelScope === true &&
+							(statement.type === "FunctionDeclaration" ||
+								statement.type === "ClassDeclaration") &&
+							statement.id &&
+							statement.id.name === "module"
+						) {
+							/** @type {BuildInfo} */
+							(parser.state.module.buildInfo).moduleArgument =
+								"__webpack_module__";
+						}
+					};
+					parser.hooks.preStatementByType
+						.for("FunctionDeclaration")
+						.tap(PLUGIN_NAME, moduleDeclarationHandler);
+					parser.hooks.preStatementByType
+						.for("ClassDeclaration")
+						.tap(PLUGIN_NAME, moduleDeclarationHandler);
+
+					for (const key of Object.keys(REPLACEMENTS)) {
+						const info = REPLACEMENTS[key];
+						parser.hooks.expression.for(key).tap(PLUGIN_NAME, (expression) => {
+							const dep = toConstantDependency(parser, info.expr, info.req);
+
+							if (key === "__non_webpack_require__" && moduleOutput) {
+								if (nodeTarget) {
+									/** @type {JavascriptModuleBuildInfo} */
+									(parser.state.module.buildInfo).needCreateRequire = true;
+								} else {
+									const warning = new WebpackError(
+										`${PLUGIN_NAME}\n__non_webpack_require__ is only allowed in target node`
+									);
+									warning.loc = parser.getLocation(expression);
+									warning.module = parser.state.module;
+									compilation.warnings.push(warning);
+								}
+							}
+
+							return dep(expression);
+						});
+						if (info.assign === false) {
+							parser.hooks.assign.for(key).tap(PLUGIN_NAME, (expr) => {
+								const err = new WebpackError(`${key} must not be assigned`);
+								err.loc = parser.getLocation(expr);
+								throw err;
+							});
+						} else if (key === "__webpack_public_path__") {
+							// Writing the slot needs the scope it lives on, not the runtime
+							// module computing the value it replaces; a read asks for that.
+							const writePublicPath = toConstantDependency(parser, info.expr, [
+								RuntimeGlobals.requireScope
+							]);
+							parser.hooks.assign.for(key).tap(PLUGIN_NAME, (expr) => {
+								/** @type {JavascriptModuleBuildInfo} */
+								(parser.state.module.buildInfo).usingPublicPathOverride = true;
+								addRuntimePublicPathOverride(compilation, parser.state.module);
+								// A destructuring target is no expression to replace; the read
+								// handler still spells the global, with a read's requirement.
+								if (expr.left.type !== "Identifier") return;
+								return writePublicPath(expr.left);
+							});
+						}
+						if (info.type) {
+							parser.hooks.evaluateTypeof
+								.for(key)
+								.tap(PLUGIN_NAME, evaluateToString(info.type));
+						}
+					}
+
+					parser.hooks.expression
+						.for("__webpack_layer__")
+						.tap(PLUGIN_NAME, (expr) => {
+							const dep = new ConstDependency(
+								JSON.stringify(parser.state.module.layer),
+								/** @type {Range} */ (expr.range)
+							);
+							dep.loc = parser.getLocation(expr);
+							parser.state.module.addPresentationalDependency(dep);
+							return true;
+						});
+					parser.hooks.evaluateIdentifier
+						.for("__webpack_layer__")
+						.tap(PLUGIN_NAME, (expr) =>
+							(parser.state.module.layer === null
+								? new BasicEvaluatedExpression().setNull()
+								: new BasicEvaluatedExpression().setString(
+										parser.state.module.layer
+									)
+							).setRange(/** @type {Range} */ (expr.range))
+						);
+					parser.hooks.evaluateTypeof
+						.for("__webpack_layer__")
+						.tap(PLUGIN_NAME, (expr) =>
+							new BasicEvaluatedExpression()
+								.setString(
+									parser.state.module.layer === null ? "object" : "string"
+								)
+								.setRange(/** @type {Range} */ (expr.range))
+						);
+
+					parser.hooks.expression
+						.for("__webpack_module__.id")
+						.tap(PLUGIN_NAME, (expr) => {
+							/** @type {JavascriptModuleBuildInfo} */
+							(parser.state.module.buildInfo).moduleConcatenationBailout =
+								"__webpack_module__.id";
+							const moduleArgument = parser.state.module.moduleArgument;
+							if (moduleArgument === "__webpack_module__") {
+								const dep = new RuntimeRequirementsDependency([
+									RuntimeGlobals.moduleId
+								]);
+								dep.loc = parser.getLocation(expr);
+								parser.state.module.addPresentationalDependency(dep);
+							} else {
+								const initDep = new ModuleInitFragmentDependency(
+									`var __webpack_internal_module_id__ = ${moduleArgument}.id;\n`,
+									[RuntimeGlobals.moduleId],
+									"__webpack_internal_module_id__"
+								);
+								parser.state.module.addPresentationalDependency(initDep);
+								const dep = new ConstDependency(
+									"__webpack_internal_module_id__",
+									/** @type {Range} */ (expr.range),
+									[]
+								);
+								dep.loc = parser.getLocation(expr);
+								parser.state.module.addPresentationalDependency(dep);
+							}
+							return true;
+						});
+
+					parser.hooks.expression
+						.for("__webpack_module__")
+						.tap(PLUGIN_NAME, (expr) => {
+							/** @type {JavascriptModuleBuildInfo} */
+							(parser.state.module.buildInfo).moduleConcatenationBailout =
+								"__webpack_module__";
+							const moduleArgument = parser.state.module.moduleArgument;
+							if (moduleArgument === "__webpack_module__") {
+								const dep = new RuntimeRequirementsDependency([
+									RuntimeGlobals.module
+								]);
+								dep.loc = parser.getLocation(expr);
+								parser.state.module.addPresentationalDependency(dep);
+							} else {
+								const initDep = new ModuleInitFragmentDependency(
+									`var __webpack_internal_module__ = ${moduleArgument};\n`,
+									[RuntimeGlobals.module],
+									"__webpack_internal_module__"
+								);
+								parser.state.module.addPresentationalDependency(initDep);
+								const dep = new ConstDependency(
+									"__webpack_internal_module__",
+									/** @type {Range} */ (expr.range),
+									[]
+								);
+								dep.loc = parser.getLocation(expr);
+								parser.state.module.addPresentationalDependency(dep);
+							}
+							return true;
+						});
+					parser.hooks.evaluateTypeof
+						.for("__webpack_module__")
+						.tap(PLUGIN_NAME, evaluateToString("object"));
 				};
 
 				normalModuleFactory.hooks.parser
-					.for("javascript/auto")
-					.tap("APIPlugin", handler);
+					.for(JAVASCRIPT_MODULE_TYPE_AUTO)
+					.tap(PLUGIN_NAME, handler);
 				normalModuleFactory.hooks.parser
-					.for("javascript/dynamic")
-					.tap("APIPlugin", handler);
+					.for(JAVASCRIPT_MODULE_TYPE_DYNAMIC)
+					.tap(PLUGIN_NAME, handler);
 				normalModuleFactory.hooks.parser
-					.for("javascript/esm")
-					.tap("APIPlugin", handler);
+					.for(JAVASCRIPT_MODULE_TYPE_ESM)
+					.tap(PLUGIN_NAME, handler);
 			}
 		);
 	}
 }
 
 module.exports = APIPlugin;
+module.exports.runtimeUsesPublicPathOverride = runtimeUsesPublicPathOverride;
+module.exports.usesRuntimePublicPathOverride = usesRuntimePublicPathOverride;
diff --git a/lib/AmdMainTemplatePlugin.js b/lib/AmdMainTemplatePlugin.js
deleted file mode 100644
index ff715ec6c17..00000000000
--- a/lib/AmdMainTemplatePlugin.js
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Tobias Koppers @sokra
- */
-
-"use strict";
-
-const { ConcatSource } = require("webpack-sources");
-const Template = require("./Template");
-
-/** @typedef {import("./Compilation")} Compilation */
-
-class AmdMainTemplatePlugin {
-	/**
-	 * @param {string} name the library name
-	 */
-	constructor(name) {
-		/** @type {string} */
-		this.name = name;
-	}
-
-	/**
-	 * @param {Compilation} compilation the compilation instance
-	 * @returns {void}
-	 */
-	apply(compilation) {
-		const { mainTemplate, chunkTemplate } = compilation;
-
-		const onRenderWithEntry = (source, chunk, hash) => {
-			const externals = chunk.getModules().filter(m => m.external);
-			const externalsDepsArray = JSON.stringify(
-				externals.map(
-					m => (typeof m.request === "object" ? m.request.amd : m.request)
-				)
-			);
-			const externalsArguments = externals
-				.map(
-					m => `__WEBPACK_EXTERNAL_MODULE_${Template.toIdentifier(`${m.id}`)}__`
-				)
-				.join(", ");
-
-			if (this.name) {
-				const name = mainTemplate.getAssetPath(this.name, {
-					hash,
-					chunk
-				});
-
-				return new ConcatSource(
-					`define(${JSON.stringify(
-						name
-					)}, ${externalsDepsArray}, function(${externalsArguments}) { return `,
-					source,
-					"});"
-				);
-			} else if (externalsArguments) {
-				return new ConcatSource(
-					`define(${externalsDepsArray}, function(${externalsArguments}) { return `,
-					source,
-					"});"
-				);
-			} else {
-				return new ConcatSource("define(function() { return ", source, "});");
-			}
-		};
-
-		for (const template of [mainTemplate, chunkTemplate]) {
-			template.hooks.renderWithEntry.tap(
-				"AmdMainTemplatePlugin",
-				onRenderWithEntry
-			);
-		}
-
-		mainTemplate.hooks.globalHashPaths.tap("AmdMainTemplatePlugin", paths => {
-			if (this.name) {
-				paths.push(this.name);
-			}
-			return paths;
-		});
-
-		mainTemplate.hooks.hash.tap("AmdMainTemplatePlugin", hash => {
-			hash.update("exports amd");
-			hash.update(this.name);
-		});
-	}
-}
-
-module.exports = AmdMainTemplatePlugin;
diff --git a/lib/AsyncDependenciesBlock.js b/lib/AsyncDependenciesBlock.js
index 3bf22d2e297..da183aab6de 100644
--- a/lib/AsyncDependenciesBlock.js
+++ b/lib/AsyncDependenciesBlock.js
@@ -2,65 +2,130 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
+
 const DependenciesBlock = require("./DependenciesBlock");
+const makeSerializable = require("./util/makeSerializable");
+
+/** @typedef {import("./ChunkGroup").ChunkGroupOptions} ChunkGroupOptions */
+/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */
+/** @typedef {import("./Dependency").UpdateHashContext} UpdateHashContext */
+/** @typedef {import("./Entrypoint").EntryOptions} EntryOptions */
+/** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */
+/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */
+/** @typedef {import("./util/Hash")} Hash */
+
+/** @typedef {(ChunkGroupOptions & { entryOptions?: EntryOptions } & { circular?: boolean })} GroupOptions */
 
-module.exports = class AsyncDependenciesBlock extends DependenciesBlock {
-	constructor(groupOptions, module, loc, request) {
+class AsyncDependenciesBlock extends DependenciesBlock {
+	/**
+	 * @param {GroupOptions | string | null} groupOptions options for the group
+	 * @param {(DependencyLocation | null)=} loc the line of code
+	 * @param {(string | null)=} request the request
+	 */
+	constructor(groupOptions, loc, request) {
 		super();
 		if (typeof groupOptions === "string") {
 			groupOptions = { name: groupOptions };
 		} else if (!groupOptions) {
 			groupOptions = { name: undefined };
 		}
+		if (typeof groupOptions.circular !== "boolean") {
+			// default allow circular references
+			groupOptions.circular = true;
+		}
+		/** @type {GroupOptions} */
 		this.groupOptions = groupOptions;
-		this.chunkGroup = undefined;
-		this.module = module;
+		/** @type {DependencyLocation | null | undefined} */
 		this.loc = loc;
+		/** @type {string | null | undefined} */
 		this.request = request;
+		/** @type {undefined | string} */
+		this._stringifiedGroupOptions = undefined;
 	}
 
+	/**
+	 * @returns {ChunkGroupOptions["name"]} The name of the chunk
+	 */
 	get chunkName() {
 		return this.groupOptions.name;
 	}
 
+	/**
+	 * @param {string | undefined} value The new chunk name
+	 * @returns {void}
+	 */
 	set chunkName(value) {
-		this.groupOptions.name = value;
-	}
-
-	get chunks() {
-		throw new Error("Moved to AsyncDependenciesBlock.chunkGroup");
+		if (this.groupOptions.name !== value) {
+			this.groupOptions.name = value;
+			this._stringifiedGroupOptions = undefined;
+		}
 	}
 
-	set chunks(value) {
-		throw new Error("Moved to AsyncDependenciesBlock.chunkGroup");
+	/**
+	 * @returns {boolean} Whether circular references are allowed
+	 */
+	get circular() {
+		return Boolean(this.groupOptions.circular);
 	}
 
-	updateHash(hash) {
-		hash.update(JSON.stringify(this.groupOptions));
+	/**
+	 * Updates the hash with the data contributed by this instance.
+	 * @param {Hash} hash the hash used to track dependencies
+	 * @param {UpdateHashContext} context context
+	 * @returns {void}
+	 */
+	updateHash(hash, context) {
+		const { chunkGraph } = context;
+		if (this._stringifiedGroupOptions === undefined) {
+			this._stringifiedGroupOptions = JSON.stringify(this.groupOptions);
+		}
+		const chunkGroup = chunkGraph.getBlockChunkGroup(this);
 		hash.update(
-			(this.chunkGroup &&
-				this.chunkGroup.chunks
-					.map(chunk => {
-						return chunk.id !== null ? chunk.id : "";
-					})
-					.join(",")) ||
-				""
+			`${this._stringifiedGroupOptions}${chunkGroup ? chunkGroup.id : ""}`
 		);
-		super.updateHash(hash);
+		super.updateHash(hash, context);
 	}
 
-	disconnect() {
-		this.chunkGroup = undefined;
-		super.disconnect();
+	/**
+	 * Serializes this instance into the provided serializer context.
+	 * @param {ObjectSerializerContext} context context
+	 */
+	serialize(context) {
+		const { write } = context;
+		write(this.groupOptions);
+		write(this.loc);
+		write(this.request);
+		super.serialize(context);
 	}
 
-	unseal() {
-		this.chunkGroup = undefined;
-		super.unseal();
+	/**
+	 * Restores this instance from the provided deserializer context.
+	 * @param {ObjectDeserializerContext} context context
+	 */
+	deserialize(context) {
+		const { read } = context;
+		this.groupOptions = read();
+		this.loc = read();
+		this.request = read();
+		super.deserialize(context);
 	}
+}
+
+makeSerializable(AsyncDependenciesBlock, "webpack/lib/AsyncDependenciesBlock");
 
-	sortItems() {
-		super.sortItems();
+Object.defineProperty(AsyncDependenciesBlock.prototype, "module", {
+	get() {
+		throw new Error(
+			"module property was removed from AsyncDependenciesBlock (it's not needed)"
+		);
+	},
+	set() {
+		throw new Error(
+			"module property was removed from AsyncDependenciesBlock (it's not needed)"
+		);
 	}
-};
+});
+
+module.exports = AsyncDependenciesBlock;
diff --git a/lib/AsyncDependencyToInitialChunkError.js b/lib/AsyncDependencyToInitialChunkError.js
deleted file mode 100644
index a0631aa3d50..00000000000
--- a/lib/AsyncDependencyToInitialChunkError.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Sean Larkin @thelarkinn
-*/
-"use strict";
-
-const WebpackError = require("./WebpackError");
-
-/** @typedef {import("./Module")} Module */
-
-class AsyncDependencyToInitialChunkError extends WebpackError {
-	/**
-	 * Creates an instance of AsyncDependencyToInitialChunkError.
-	 * @param {string} chunkName Name of Chunk
-	 * @param {Module} module module tied to dependency
-	 * @param {TODO} loc location of dependency
-	 */
-	constructor(chunkName, module, loc) {
-		super(
-			`It's not allowed to load an initial chunk on demand. The chunk name "${chunkName}" is already used by an entrypoint.`
-		);
-
-		this.name = "AsyncDependencyToInitialChunkError";
-		this.module = module;
-		this.loc = loc;
-
-		Error.captureStackTrace(this, this.constructor);
-	}
-}
-
-module.exports = AsyncDependencyToInitialChunkError;
diff --git a/lib/AutomaticPrefetchPlugin.js b/lib/AutomaticPrefetchPlugin.js
index 26123de53f5..3f42ee0ed69 100644
--- a/lib/AutomaticPrefetchPlugin.js
+++ b/lib/AutomaticPrefetchPlugin.js
@@ -2,23 +2,31 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
 const asyncLib = require("neo-async");
-const PrefetchDependency = require("./dependencies/PrefetchDependency");
 const NormalModule = require("./NormalModule");
+const PrefetchDependency = require("./dependencies/PrefetchDependency");
 
-/** @typedef {import("./Compiler.js")} Compiler */
+/** @typedef {import("./Compiler")} Compiler */
 
+const PLUGIN_NAME = "AutomaticPrefetchPlugin";
+
+/**
+ * Records modules from one compilation and adds them back as prefetch
+ * dependencies in the next compilation.
+ */
 class AutomaticPrefetchPlugin {
 	/**
-	 * Apply the plugin
-	 * @param {Compiler} compiler Webpack Compiler
+	 * Registers hooks that remember previously built normal modules and enqueue
+	 * them as `PrefetchDependency` requests during the next make phase.
+	 * @param {Compiler} compiler the compiler instance
 	 * @returns {void}
 	 */
 	apply(compiler) {
 		compiler.hooks.compilation.tap(
-			"AutomaticPrefetchPlugin",
+			PLUGIN_NAME,
 			(compilation, { normalModuleFactory }) => {
 				compilation.dependencyFactories.set(
 					PrefetchDependency,
@@ -26,32 +34,38 @@ class AutomaticPrefetchPlugin {
 				);
 			}
 		);
+		/** @type {{ context: string | null, request: string }[] | null} */
 		let lastModules = null;
-		compiler.hooks.afterCompile.tap("AutomaticPrefetchPlugin", compilation => {
-			lastModules = compilation.modules
-				.filter(m => m instanceof NormalModule)
-				.map(m => ({
-					context: m.context,
-					request: m.request
-				}));
-		});
-		compiler.hooks.make.tapAsync(
-			"AutomaticPrefetchPlugin",
-			(compilation, callback) => {
-				if (!lastModules) return callback();
-				asyncLib.forEach(
-					lastModules,
-					(m, callback) => {
-						compilation.prefetch(
-							m.context || compiler.context,
-							new PrefetchDependency(m.request),
-							callback
-						);
-					},
-					callback
-				);
+		compiler.hooks.afterCompile.tap(PLUGIN_NAME, (compilation) => {
+			lastModules = [];
+
+			for (const m of compilation.modules) {
+				if (m instanceof NormalModule) {
+					lastModules.push({
+						context: m.context,
+						request: m.request
+					});
+				}
 			}
-		);
+		});
+		compiler.hooks.make.tapAsync(PLUGIN_NAME, (compilation, callback) => {
+			if (!lastModules) return callback();
+			asyncLib.each(
+				lastModules,
+				(m, callback) => {
+					compilation.addModuleChain(
+						m.context || compiler.context,
+						new PrefetchDependency(`!!${m.request}`),
+						callback
+					);
+				},
+				(err) => {
+					lastModules = null;
+					callback(err);
+				}
+			);
+		});
 	}
 }
+
 module.exports = AutomaticPrefetchPlugin;
diff --git a/lib/BannerPlugin.js b/lib/BannerPlugin.js
index 1e9aa343c0e..71171a725c8 100644
--- a/lib/BannerPlugin.js
+++ b/lib/BannerPlugin.js
@@ -1,69 +1,112 @@
 /*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Tobias Koppers @sokra
- */
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
 
 "use strict";
 
 const { ConcatSource } = require("webpack-sources");
+const Compilation = require("./Compilation");
 const ModuleFilenameHelpers = require("./ModuleFilenameHelpers");
 const Template = require("./Template");
 
-const validateOptions = require("schema-utils");
-const schema = require("../schemas/plugins/BannerPlugin.json");
+/** @typedef {import("webpack-sources").Source} Source */
+/** @typedef {import("../declarations/plugins/BannerPlugin").BannerPluginArgument} BannerPluginArgument */
+/** @typedef {import("../declarations/plugins/BannerPlugin").BannerPluginOptions} BannerPluginOptions */
+/** @typedef {import("./Compilation").PathDataChunk} PathDataChunk */
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./Chunk")} Chunk */
+
+/** @typedef {(data: { hash?: string, chunk: Chunk, filename: string }) => string} BannerFunction */
 
-const wrapComment = str => {
+/**
+ * Wraps banner text in a JavaScript block comment, preserving multi-line
+ * formatting and escaping accidental comment terminators.
+ * @param {string} str string to wrap
+ * @returns {string} wrapped string
+ */
+const wrapComment = (str) => {
 	if (!str.includes("\n")) {
 		return Template.toComment(str);
 	}
 	return `/*!\n * ${str
 		.replace(/\*\//g, "* /")
 		.split("\n")
-		.join("\n * ")}\n */`;
+		.join("\n * ")
+		.replace(/\s+\n/g, "\n")
+		.trimEnd()}\n */`;
 };
 
+const PLUGIN_NAME = "BannerPlugin";
+
+/**
+ * Prepends or appends banner text to emitted assets that match the configured
+ * file filters.
+ */
 class BannerPlugin {
+	/**
+	 * Normalizes banner options and compiles the configured banner source into a
+	 * function that can render per-asset banner text.
+	 * @param {BannerPluginArgument} options options object
+	 */
 	constructor(options) {
-		if (arguments.length > 1) {
-			throw new Error(
-				"BannerPlugin only takes one argument (pass an options object)"
-			);
-		}
-
-		validateOptions(schema, options, "Banner Plugin");
-
 		if (typeof options === "string" || typeof options === "function") {
 			options = {
 				banner: options
 			};
 		}
 
-		this.options = options || {};
+		/** @type {BannerPluginOptions} */
+		this.options = options;
 
-		if (typeof options.banner === "function") {
-			const getBanner = this.options.banner;
+		const bannerOption = options.banner;
+		if (typeof bannerOption === "function") {
+			const getBanner = bannerOption;
+			/** @type {BannerFunction} */
 			this.banner = this.options.raw
 				? getBanner
-				: data => wrapComment(getBanner(data));
+				: /** @type {BannerFunction} */ (data) => wrapComment(getBanner(data));
 		} else {
 			const banner = this.options.raw
-				? this.options.banner
-				: wrapComment(this.options.banner);
+				? bannerOption
+				: wrapComment(bannerOption);
+			/** @type {BannerFunction} */
 			this.banner = () => banner;
 		}
 	}
 
+	/**
+	 * Validates the configured options and injects rendered banner comments into
+	 * matching compilation assets at the configured process-assets stage.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
 	apply(compiler) {
+		compiler.hooks.validate.tap(PLUGIN_NAME, () => {
+			compiler.validate(
+				() => require("../schemas/plugins/BannerPlugin.json"),
+				this.options,
+				{
+					name: "Banner Plugin",
+					baseDataPath: "options"
+				},
+				(options) => require("../schemas/plugins/BannerPlugin.check")(options)
+			);
+		});
 		const options = this.options;
 		const banner = this.banner;
 		const matchObject = ModuleFilenameHelpers.matchObject.bind(
 			undefined,
 			options
 		);
-
-		compiler.hooks.compilation.tap("BannerPlugin", compilation => {
-			compilation.hooks.optimizeChunkAssets.tap("BannerPlugin", chunks => {
-				for (const chunk of chunks) {
+		/** @type {WeakMap} */
+		const cache = new WeakMap();
+		const stage =
+			this.options.stage || Compilation.PROCESS_ASSETS_STAGE_ADDITIONS;
+
+		compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
+			compilation.hooks.processAssets.tap({ name: PLUGIN_NAME, stage }, () => {
+				for (const chunk of compilation.chunks) {
 					if (options.entryOnly && !chunk.canBeInitial()) {
 						continue;
 					}
@@ -73,40 +116,26 @@ class BannerPlugin {
 							continue;
 						}
 
-						let basename;
-						let query = "";
-						let filename = file;
-						const hash = compilation.hash;
-						const querySplit = filename.indexOf("?");
-
-						if (querySplit >= 0) {
-							query = filename.substr(querySplit);
-							filename = filename.substr(0, querySplit);
-						}
-
-						const lastSlashIndex = filename.lastIndexOf("/");
+						/** @type {PathDataChunk} */
+						const data = { chunk, filename: file };
 
-						if (lastSlashIndex === -1) {
-							basename = filename;
-						} else {
-							basename = filename.substr(lastSlashIndex + 1);
-						}
-
-						const data = {
-							hash,
-							chunk,
-							filename,
-							basename,
-							query
-						};
-
-						const comment = compilation.getPath(banner(data), data);
-
-						compilation.assets[file] = new ConcatSource(
-							comment,
-							"\n",
-							compilation.assets[file]
+						const comment = compilation.getPath(
+							/** @type {string | import("./TemplatedPathPlugin").TemplatePathFn} */
+							(banner),
+							data
 						);
+
+						compilation.updateAsset(file, (old) => {
+							const cached = cache.get(old);
+							if (!cached || cached.comment !== comment) {
+								const source = options.footer
+									? new ConcatSource(old, "\n", comment)
+									: new ConcatSource(comment, "\n", old);
+								cache.set(old, { source, comment });
+								return source;
+							}
+							return cached.source;
+						});
 					}
 				}
 			});
diff --git a/lib/BasicEvaluatedExpression.js b/lib/BasicEvaluatedExpression.js
deleted file mode 100644
index 65db864f9b0..00000000000
--- a/lib/BasicEvaluatedExpression.js
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-
-"use strict";
-
-const TypeUnknown = 0;
-const TypeNull = 1;
-const TypeString = 2;
-const TypeNumber = 3;
-const TypeBoolean = 4;
-const TypeRegExp = 5;
-const TypeConditional = 6;
-const TypeArray = 7;
-const TypeConstArray = 8;
-const TypeIdentifier = 9;
-const TypeWrapped = 10;
-const TypeTemplateString = 11;
-
-class BasicEvaluatedExpression {
-	constructor() {
-		this.type = TypeUnknown;
-		this.range = null;
-		this.falsy = false;
-		this.truthy = false;
-		this.bool = null;
-		this.number = null;
-		this.regExp = null;
-		this.string = null;
-		this.quasis = null;
-		this.array = null;
-		this.items = null;
-		this.options = null;
-		this.prefix = null;
-		this.postfix = null;
-	}
-
-	isNull() {
-		return this.type === TypeNull;
-	}
-
-	isString() {
-		return this.type === TypeString;
-	}
-
-	isNumber() {
-		return this.type === TypeNumber;
-	}
-
-	isBoolean() {
-		return this.type === TypeBoolean;
-	}
-
-	isRegExp() {
-		return this.type === TypeRegExp;
-	}
-
-	isConditional() {
-		return this.type === TypeConditional;
-	}
-
-	isArray() {
-		return this.type === TypeArray;
-	}
-
-	isConstArray() {
-		return this.type === TypeConstArray;
-	}
-
-	isIdentifier() {
-		return this.type === TypeIdentifier;
-	}
-
-	isWrapped() {
-		return this.type === TypeWrapped;
-	}
-
-	isTemplateString() {
-		return this.type === TypeTemplateString;
-	}
-
-	isTruthy() {
-		return this.truthy;
-	}
-
-	isFalsy() {
-		return this.falsy;
-	}
-
-	asBool() {
-		if (this.truthy) return true;
-		if (this.falsy) return false;
-		if (this.isBoolean()) return this.bool;
-		if (this.isNull()) return false;
-		if (this.isString()) return this.string !== "";
-		if (this.isNumber()) return this.number !== 0;
-		if (this.isRegExp()) return true;
-		if (this.isArray()) return true;
-		if (this.isConstArray()) return true;
-		if (this.isWrapped()) {
-			return (this.prefix && this.prefix.asBool()) ||
-				(this.postfix && this.postfix.asBool())
-				? true
-				: undefined;
-		}
-		if (this.isTemplateString()) {
-			for (const quasi of this.quasis) {
-				if (quasi.asBool()) return true;
-			}
-			// can't tell if string will be empty without executing
-		}
-		return undefined;
-	}
-
-	setString(string) {
-		this.type = TypeString;
-		this.string = string;
-		return this;
-	}
-
-	setNull() {
-		this.type = TypeNull;
-		return this;
-	}
-
-	setNumber(number) {
-		this.type = TypeNumber;
-		this.number = number;
-		return this;
-	}
-
-	setBoolean(bool) {
-		this.type = TypeBoolean;
-		this.bool = bool;
-		return this;
-	}
-
-	setRegExp(regExp) {
-		this.type = TypeRegExp;
-		this.regExp = regExp;
-		return this;
-	}
-
-	setIdentifier(identifier) {
-		this.type = TypeIdentifier;
-		this.identifier = identifier;
-		return this;
-	}
-
-	setWrapped(prefix, postfix) {
-		this.type = TypeWrapped;
-		this.prefix = prefix;
-		this.postfix = postfix;
-		return this;
-	}
-
-	setOptions(options) {
-		this.type = TypeConditional;
-		this.options = options;
-		return this;
-	}
-
-	addOptions(options) {
-		if (!this.options) {
-			this.type = TypeConditional;
-			this.options = [];
-		}
-		for (const item of options) {
-			this.options.push(item);
-		}
-		return this;
-	}
-
-	setItems(items) {
-		this.type = TypeArray;
-		this.items = items;
-		return this;
-	}
-
-	setArray(array) {
-		this.type = TypeConstArray;
-		this.array = array;
-		return this;
-	}
-
-	setTemplateString(quasis) {
-		this.type = TypeTemplateString;
-		this.quasis = quasis;
-		return this;
-	}
-
-	setTruthy() {
-		this.falsy = false;
-		this.truthy = true;
-		return this;
-	}
-
-	setFalsy() {
-		this.falsy = true;
-		this.truthy = false;
-		return this;
-	}
-
-	setRange(range) {
-		this.range = range;
-		return this;
-	}
-}
-
-module.exports = BasicEvaluatedExpression;
diff --git a/lib/Cache.js b/lib/Cache.js
new file mode 100644
index 00000000000..8c1abbbf875
--- /dev/null
+++ b/lib/Cache.js
@@ -0,0 +1,190 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const { AsyncParallelHook, AsyncSeriesBailHook, SyncHook } = require("tapable");
+const {
+	makeWebpackError,
+	makeWebpackErrorCallback
+} = require("./errors/HookWebpackError");
+
+/**
+ * Cache validation token whose string representation identifies the build
+ * inputs associated with a cached value.
+ * @typedef {object} Etag
+ * @property {() => string} toString
+ */
+
+/**
+ * Completion callback used by cache operations that either fail with a `Error` or resolve with a typed result.
+ * @template T
+ * @callback CallbackCache
+ * @param {Error | null} err
+ * @param {T=} result
+ * @returns {void}
+ */
+
+/** @typedef {EXPECTED_ANY} Data */
+
+/**
+ * Handler invoked after a cache read succeeds so additional cache layers can
+ * react to the retrieved value.
+ * @template T
+ * @callback GotHandler
+ * @param {T} result
+ * @param {() => void} callback
+ * @returns {void}
+ */
+
+/**
+ * Creates a callback wrapper that waits for a fixed number of completions and
+ * forwards the first error immediately.
+ * @param {number} times times
+ * @param {(err?: Error | null) => void} callback callback
+ * @returns {(err?: Error | null) => void} callback
+ */
+const needCalls = (times, callback) => (err) => {
+	if (--times === 0) {
+		return callback(err);
+	}
+	if (err && times > 0) {
+		times = 0;
+		return callback(err);
+	}
+};
+
+/**
+ * Abstract cache interface backed by tapable hooks for reading, writing, idle
+ * transitions, and shutdown across webpack cache implementations.
+ */
+class Cache {
+	/**
+	 * Initializes the cache lifecycle hooks implemented by cache backends.
+	 */
+	constructor() {
+		this.hooks = {
+			/** @type {AsyncSeriesBailHook<[string, Etag | null, GotHandler[]], Data>} */
+			get: new AsyncSeriesBailHook(["identifier", "etag", "gotHandlers"]),
+			/** @type {AsyncParallelHook<[string, Etag | null, Data]>} */
+			store: new AsyncParallelHook(["identifier", "etag", "data"]),
+			/** @type {AsyncParallelHook<[Iterable]>} */
+			storeBuildDependencies: new AsyncParallelHook(["dependencies"]),
+			/** @type {SyncHook<[]>} */
+			beginIdle: new SyncHook([]),
+			/** @type {AsyncParallelHook<[]>} */
+			endIdle: new AsyncParallelHook([]),
+			/** @type {AsyncParallelHook<[]>} */
+			shutdown: new AsyncParallelHook([])
+		};
+	}
+
+	/**
+	 * Retrieves a cached value and lets registered `gotHandlers` observe the
+	 * result before the caller receives it.
+	 * @template T
+	 * @param {string} identifier the cache identifier
+	 * @param {Etag | null} etag the etag
+	 * @param {CallbackCache} callback signals when the value is retrieved
+	 * @returns {void}
+	 */
+	get(identifier, etag, callback) {
+		/** @type {GotHandler[]} */
+		const gotHandlers = [];
+		this.hooks.get.callAsync(identifier, etag, gotHandlers, (err, result) => {
+			if (err) {
+				callback(makeWebpackError(err, "Cache.hooks.get"));
+				return;
+			}
+			if (result === null) {
+				result = undefined;
+			}
+			if (gotHandlers.length > 1) {
+				const innerCallback = needCalls(gotHandlers.length, () =>
+					callback(null, result)
+				);
+				for (const gotHandler of gotHandlers) {
+					gotHandler(result, innerCallback);
+				}
+			} else if (gotHandlers.length === 1) {
+				gotHandlers[0](result, () => callback(null, result));
+			} else {
+				callback(null, result);
+			}
+		});
+	}
+
+	/**
+	 * Stores a cache entry for the identifier and etag through the registered
+	 * cache backend hooks.
+	 * @template T
+	 * @param {string} identifier the cache identifier
+	 * @param {Etag | null} etag the etag
+	 * @param {T} data the value to store
+	 * @param {CallbackCache} callback signals when the value is stored
+	 * @returns {void}
+	 */
+	store(identifier, etag, data, callback) {
+		this.hooks.store.callAsync(
+			identifier,
+			etag,
+			data,
+			makeWebpackErrorCallback(callback, "Cache.hooks.store")
+		);
+	}
+
+	/**
+	 * Persists the set of build dependencies required to determine whether the
+	 * cache can be restored in a future compilation.
+	 * @param {Iterable} dependencies list of all build dependencies
+	 * @param {CallbackCache} callback signals when the dependencies are stored
+	 * @returns {void}
+	 */
+	storeBuildDependencies(dependencies, callback) {
+		this.hooks.storeBuildDependencies.callAsync(
+			dependencies,
+			makeWebpackErrorCallback(callback, "Cache.hooks.storeBuildDependencies")
+		);
+	}
+
+	/**
+	 * Signals that webpack is entering an idle phase and cache backends may flush
+	 * or compact pending work.
+	 * @returns {void}
+	 */
+	beginIdle() {
+		this.hooks.beginIdle.call();
+	}
+
+	/**
+	 * Signals that webpack is leaving the idle phase and waits for cache
+	 * backends to finish any asynchronous resume work.
+	 * @param {CallbackCache} callback signals when the call finishes
+	 * @returns {void}
+	 */
+	endIdle(callback) {
+		this.hooks.endIdle.callAsync(
+			makeWebpackErrorCallback(callback, "Cache.hooks.endIdle")
+		);
+	}
+
+	/**
+	 * Shuts down every registered cache backend and waits for cleanup to finish.
+	 * @param {CallbackCache} callback signals when the call finishes
+	 * @returns {void}
+	 */
+	shutdown(callback) {
+		this.hooks.shutdown.callAsync(
+			makeWebpackErrorCallback(callback, "Cache.hooks.shutdown")
+		);
+	}
+}
+
+Cache.STAGE_MEMORY = -10;
+Cache.STAGE_DEFAULT = 0;
+Cache.STAGE_DISK = 10;
+Cache.STAGE_NETWORK = 20;
+
+module.exports = Cache;
diff --git a/lib/CacheFacade.js b/lib/CacheFacade.js
new file mode 100644
index 00000000000..21ff9659e21
--- /dev/null
+++ b/lib/CacheFacade.js
@@ -0,0 +1,390 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const { forEachBail } = require("enhanced-resolve");
+const asyncLib = require("neo-async");
+const getLazyHashedEtag = require("./cache/getLazyHashedEtag");
+const mergeEtags = require("./cache/mergeEtags");
+
+/** @typedef {import("./Cache")} Cache */
+/** @typedef {import("./Cache").Etag} Etag */
+/** @typedef {import("./cache/getLazyHashedEtag").HashableObject} HashableObject */
+/** @typedef {import("./util/Hash").HashFunction} HashFunction */
+
+/**
+ * Defines the callback cache callback.
+ * @template T
+ * @callback CallbackCache
+ * @param {(Error | null)=} err
+ * @param {(T | null)=} result
+ * @returns {void}
+ */
+
+/**
+ * Defines the callback normal error cache callback.
+ * @template T
+ * @callback CallbackNormalErrorCache
+ * @param {(Error | null)=} err
+ * @param {T=} result
+ * @returns {void}
+ */
+
+class MultiItemCache {
+	/**
+	 * Creates an instance of MultiItemCache.
+	 * @param {ItemCacheFacade[]} items item caches
+	 */
+	constructor(items) {
+		/** @type {ItemCacheFacade[]} */
+		this._items = items;
+		// @ts-expect-error expected - returns the single ItemCacheFacade when passed an array of length 1
+		// eslint-disable-next-line no-constructor-return
+		if (items.length === 1) return /** @type {ItemCacheFacade} */ (items[0]);
+	}
+
+	/**
+	 * Returns value.
+	 * @template T
+	 * @param {CallbackCache} callback signals when the value is retrieved
+	 * @returns {void}
+	 */
+	get(callback) {
+		forEachBail(this._items, (item, callback) => item.get(callback), callback);
+	}
+
+	/**
+	 * Returns promise with the data.
+	 * @template T
+	 * @returns {Promise} promise with the data
+	 */
+	getPromise() {
+		/**
+		 * Returns promise with the data.
+		 * @param {number} i index
+		 * @returns {Promise} promise with the data
+		 */
+		const next = (i) =>
+			this._items[i].getPromise().then((result) => {
+				if (result !== undefined) return result;
+				if (++i < this._items.length) return next(i);
+			});
+		return next(0);
+	}
+
+	/**
+	 * Processes the provided data.
+	 * @template T
+	 * @param {T} data the value to store
+	 * @param {CallbackCache} callback signals when the value is stored
+	 * @returns {void}
+	 */
+	store(data, callback) {
+		asyncLib.each(
+			this._items,
+			(item, callback) => item.store(data, callback),
+			callback
+		);
+	}
+
+	/**
+	 * Stores the provided data.
+	 * @template T
+	 * @param {T} data the value to store
+	 * @returns {Promise} promise signals when the value is stored
+	 */
+	storePromise(data) {
+		return Promise.all(this._items.map((item) => item.storePromise(data))).then(
+			() => {}
+		);
+	}
+}
+
+class ItemCacheFacade {
+	/**
+	 * Creates an instance of ItemCacheFacade.
+	 * @param {Cache} cache the root cache
+	 * @param {string} name the child cache item name
+	 * @param {Etag | null} etag the etag
+	 */
+	constructor(cache, name, etag) {
+		/** @type {Cache} */
+		this._cache = cache;
+		/** @type {string} */
+		this._name = name;
+		/** @type {Etag | null} */
+		this._etag = etag;
+	}
+
+	/**
+	 * Returns value.
+	 * @template T
+	 * @param {CallbackCache} callback signals when the value is retrieved
+	 * @returns {void}
+	 */
+	get(callback) {
+		this._cache.get(this._name, this._etag, callback);
+	}
+
+	/**
+	 * Returns promise with the data.
+	 * @template T
+	 * @returns {Promise} promise with the data
+	 */
+	getPromise() {
+		return new Promise((resolve, reject) => {
+			this._cache.get(this._name, this._etag, (err, data) => {
+				if (err) {
+					reject(err);
+				} else {
+					resolve(data);
+				}
+			});
+		});
+	}
+
+	/**
+	 * Processes the provided data.
+	 * @template T
+	 * @param {T} data the value to store
+	 * @param {CallbackCache} callback signals when the value is stored
+	 * @returns {void}
+	 */
+	store(data, callback) {
+		this._cache.store(this._name, this._etag, data, callback);
+	}
+
+	/**
+	 * Stores the provided data.
+	 * @template T
+	 * @param {T} data the value to store
+	 * @returns {Promise} promise signals when the value is stored
+	 */
+	storePromise(data) {
+		return new Promise((resolve, reject) => {
+			this._cache.store(this._name, this._etag, data, (err) => {
+				if (err) {
+					reject(err);
+				} else {
+					resolve();
+				}
+			});
+		});
+	}
+
+	/**
+	 * Processes the provided computer.
+	 * @template T
+	 * @param {(callback: CallbackNormalErrorCache) => void} computer function to compute the value if not cached
+	 * @param {CallbackNormalErrorCache} callback signals when the value is retrieved
+	 * @returns {void}
+	 */
+	provide(computer, callback) {
+		this.get((err, cacheEntry) => {
+			if (err) return callback(err);
+			if (cacheEntry !== undefined) return cacheEntry;
+			computer((err, result) => {
+				if (err) return callback(err);
+				this.store(result, (err) => {
+					if (err) return callback(err);
+					callback(null, result);
+				});
+			});
+		});
+	}
+
+	/**
+	 * Returns promise with the data.
+	 * @template T
+	 * @param {() => Promise | T} computer function to compute the value if not cached
+	 * @returns {Promise} promise with the data
+	 */
+	async providePromise(computer) {
+		const cacheEntry = await this.getPromise();
+		if (cacheEntry !== undefined) return cacheEntry;
+		const result = await computer();
+		await this.storePromise(result);
+		return result;
+	}
+}
+
+class CacheFacade {
+	/**
+	 * Creates an instance of CacheFacade.
+	 * @param {Cache} cache the root cache
+	 * @param {string} name the child cache name
+	 * @param {HashFunction=} hashFunction the hash function to use
+	 */
+	constructor(cache, name, hashFunction) {
+		/** @type {Cache} */
+		this._cache = cache;
+		/** @type {string} */
+		this._name = name;
+		/** @type {HashFunction | undefined} */
+		this._hashFunction = hashFunction;
+	}
+
+	/**
+	 * Returns whether a cache backend is active.
+	 * @returns {boolean} true, when get or store can have an effect
+	 */
+	isEnabled() {
+		return this._cache.hooks.get.isUsed() || this._cache.hooks.store.isUsed();
+	}
+
+	/**
+	 * Returns child cache.
+	 * @param {string} name the child cache name#
+	 * @returns {CacheFacade} child cache
+	 */
+	getChildCache(name) {
+		return new CacheFacade(
+			this._cache,
+			`${this._name}|${name}`,
+			this._hashFunction
+		);
+	}
+
+	/**
+	 * Returns item cache.
+	 * @param {string} identifier the cache identifier
+	 * @param {Etag | null} etag the etag
+	 * @returns {ItemCacheFacade} item cache
+	 */
+	getItemCache(identifier, etag) {
+		return new ItemCacheFacade(
+			this._cache,
+			`${this._name}|${identifier}`,
+			etag
+		);
+	}
+
+	/**
+	 * Gets lazy hashed etag.
+	 * @param {HashableObject} obj an hashable object
+	 * @returns {Etag} an etag that is lazy hashed
+	 */
+	getLazyHashedEtag(obj) {
+		return getLazyHashedEtag(obj, this._hashFunction);
+	}
+
+	/**
+	 * Merges the provided values into a single result.
+	 * @param {Etag} a an etag
+	 * @param {Etag} b another etag
+	 * @returns {Etag} an etag that represents both
+	 */
+	mergeEtags(a, b) {
+		return mergeEtags(a, b);
+	}
+
+	/**
+	 * Returns value.
+	 * @template T
+	 * @param {string} identifier the cache identifier
+	 * @param {Etag | null} etag the etag
+	 * @param {CallbackCache} callback signals when the value is retrieved
+	 * @returns {void}
+	 */
+	get(identifier, etag, callback) {
+		this._cache.get(`${this._name}|${identifier}`, etag, callback);
+	}
+
+	/**
+	 * Returns promise with the data.
+	 * @template T
+	 * @param {string} identifier the cache identifier
+	 * @param {Etag | null} etag the etag
+	 * @returns {Promise} promise with the data
+	 */
+	getPromise(identifier, etag) {
+		return new Promise((resolve, reject) => {
+			this._cache.get(`${this._name}|${identifier}`, etag, (err, data) => {
+				if (err) {
+					reject(err);
+				} else {
+					resolve(data);
+				}
+			});
+		});
+	}
+
+	/**
+	 * Processes the provided identifier.
+	 * @template T
+	 * @param {string} identifier the cache identifier
+	 * @param {Etag | null} etag the etag
+	 * @param {T} data the value to store
+	 * @param {CallbackCache} callback signals when the value is stored
+	 * @returns {void}
+	 */
+	store(identifier, etag, data, callback) {
+		this._cache.store(`${this._name}|${identifier}`, etag, data, callback);
+	}
+
+	/**
+	 * Stores the provided identifier.
+	 * @template T
+	 * @param {string} identifier the cache identifier
+	 * @param {Etag | null} etag the etag
+	 * @param {T} data the value to store
+	 * @returns {Promise} promise signals when the value is stored
+	 */
+	storePromise(identifier, etag, data) {
+		return new Promise((resolve, reject) => {
+			this._cache.store(`${this._name}|${identifier}`, etag, data, (err) => {
+				if (err) {
+					reject(err);
+				} else {
+					resolve();
+				}
+			});
+		});
+	}
+
+	/**
+	 * Processes the provided identifier.
+	 * @template T
+	 * @param {string} identifier the cache identifier
+	 * @param {Etag | null} etag the etag
+	 * @param {(callback: CallbackNormalErrorCache) => void} computer function to compute the value if not cached
+	 * @param {CallbackNormalErrorCache} callback signals when the value is retrieved
+	 * @returns {void}
+	 */
+	provide(identifier, etag, computer, callback) {
+		this.get(identifier, etag, (err, cacheEntry) => {
+			if (err) return callback(err);
+			if (cacheEntry !== undefined) return cacheEntry;
+			computer((err, result) => {
+				if (err) return callback(err);
+				this.store(identifier, etag, result, (err) => {
+					if (err) return callback(err);
+					callback(null, result);
+				});
+			});
+		});
+	}
+
+	/**
+	 * Returns promise with the data.
+	 * @template T
+	 * @param {string} identifier the cache identifier
+	 * @param {Etag | null} etag the etag
+	 * @param {() => Promise | T} computer function to compute the value if not cached
+	 * @returns {Promise} promise with the data
+	 */
+	async providePromise(identifier, etag, computer) {
+		const cacheEntry = await this.getPromise(identifier, etag);
+		if (cacheEntry !== undefined) return cacheEntry;
+		const result = await computer();
+		await this.storePromise(identifier, etag, result);
+		return result;
+	}
+}
+
+module.exports = CacheFacade;
+module.exports.ItemCacheFacade = ItemCacheFacade;
+module.exports.MultiItemCache = MultiItemCache;
diff --git a/lib/CachePlugin.js b/lib/CachePlugin.js
deleted file mode 100644
index 0d1650be963..00000000000
--- a/lib/CachePlugin.js
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const asyncLib = require("neo-async");
-
-class CachePlugin {
-	constructor(cache) {
-		this.cache = cache || {};
-		this.FS_ACCURACY = 2000;
-	}
-
-	apply(compiler) {
-		if (Array.isArray(compiler.compilers)) {
-			compiler.compilers.forEach((c, idx) => {
-				new CachePlugin((this.cache[idx] = this.cache[idx] || {})).apply(c);
-			});
-		} else {
-			const registerCacheToCompiler = (compiler, cache) => {
-				compiler.hooks.thisCompilation.tap("CachePlugin", compilation => {
-					compilation.cache = cache;
-					compilation.hooks.childCompiler.tap(
-						"CachePlugin",
-						(childCompiler, compilerName, compilerIndex) => {
-							if (cache) {
-								let childCache;
-								if (!cache.children) {
-									cache.children = {};
-								}
-								if (!cache.children[compilerName]) {
-									cache.children[compilerName] = [];
-								}
-								if (cache.children[compilerName][compilerIndex]) {
-									childCache = cache.children[compilerName][compilerIndex];
-								} else {
-									cache.children[compilerName].push((childCache = {}));
-								}
-								registerCacheToCompiler(childCompiler, childCache);
-							}
-						}
-					);
-				});
-			};
-			registerCacheToCompiler(compiler, this.cache);
-			compiler.hooks.watchRun.tap("CachePlugin", () => {
-				this.watching = true;
-			});
-			compiler.hooks.run.tapAsync("CachePlugin", (compiler, callback) => {
-				if (!compiler._lastCompilationFileDependencies) {
-					return callback();
-				}
-				const fs = compiler.inputFileSystem;
-				const fileTs = (compiler.fileTimestamps = new Map());
-				asyncLib.forEach(
-					compiler._lastCompilationFileDependencies,
-					(file, callback) => {
-						fs.stat(file, (err, stat) => {
-							if (err) {
-								if (err.code === "ENOENT") return callback();
-								return callback(err);
-							}
-
-							if (stat.mtime) this.applyMtime(+stat.mtime);
-
-							fileTs.set(file, +stat.mtime || Infinity);
-
-							callback();
-						});
-					},
-					err => {
-						if (err) return callback(err);
-
-						for (const [file, ts] of fileTs) {
-							fileTs.set(file, ts + this.FS_ACCURACY);
-						}
-
-						callback();
-					}
-				);
-			});
-			compiler.hooks.afterCompile.tap("CachePlugin", compilation => {
-				compilation.compiler._lastCompilationFileDependencies =
-					compilation.fileDependencies;
-				compilation.compiler._lastCompilationContextDependencies =
-					compilation.contextDependencies;
-			});
-		}
-	}
-
-	/* istanbul ignore next */
-	applyMtime(mtime) {
-		if (this.FS_ACCURACY > 1 && mtime % 2 !== 0) this.FS_ACCURACY = 1;
-		else if (this.FS_ACCURACY > 10 && mtime % 20 !== 0) this.FS_ACCURACY = 10;
-		else if (this.FS_ACCURACY > 100 && mtime % 200 !== 0)
-			this.FS_ACCURACY = 100;
-		else if (this.FS_ACCURACY > 1000 && mtime % 2000 !== 0)
-			this.FS_ACCURACY = 1000;
-	}
-}
-module.exports = CachePlugin;
diff --git a/lib/CaseSensitiveModulesWarning.js b/lib/CaseSensitiveModulesWarning.js
deleted file mode 100644
index 6bfeeca83f1..00000000000
--- a/lib/CaseSensitiveModulesWarning.js
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const WebpackError = require("./WebpackError");
-
-/** @typedef {import("./Module")} Module */
-
-/**
- * @param {Module[]} modules the modules to be sorted
- * @returns {Module[]} sorted version of original modules
- */
-const sortModules = modules => {
-	return modules.slice().sort((a, b) => {
-		const aIdent = a.identifier();
-		const bIdent = b.identifier();
-		/* istanbul ignore next */
-		if (aIdent < bIdent) return -1;
-		/* istanbul ignore next */
-		if (aIdent > bIdent) return 1;
-		/* istanbul ignore next */
-		return 0;
-	});
-};
-
-/**
- * @param {Module[]} modules each module from throw
- * @returns {string} each message from provided moduels
- */
-const createModulesListMessage = modules => {
-	return modules
-		.map(m => {
-			let message = `* ${m.identifier()}`;
-			const validReasons = m.reasons.filter(reason => reason.module);
-
-			if (validReasons.length > 0) {
-				message += `\n    Used by ${validReasons.length} module(s), i. e.`;
-				message += `\n    ${validReasons[0].module.identifier()}`;
-			}
-			return message;
-		})
-		.join("\n");
-};
-
-class CaseSensitiveModulesWarning extends WebpackError {
-	/**
-	 * Creates an instance of CaseSensitiveModulesWarning.
-	 * @param {Module[]} modules modules that were detected
-	 */
-	constructor(modules) {
-		const sortedModules = sortModules(modules);
-		const modulesList = createModulesListMessage(sortedModules);
-		super(`There are multiple modules with names that only differ in casing.
-This can lead to unexpected behavior when compiling on a filesystem with other case-semantic.
-Use equal casing. Compare these module identifiers:
-${modulesList}`);
-
-		this.name = "CaseSensitiveModulesWarning";
-		this.origin = this.module = sortedModules[0];
-
-		Error.captureStackTrace(this, this.constructor);
-	}
-}
-
-module.exports = CaseSensitiveModulesWarning;
diff --git a/lib/Chunk.js b/lib/Chunk.js
index 774eb445a95..26d37f887b0 100644
--- a/lib/Chunk.js
+++ b/lib/Chunk.js
@@ -1,95 +1,81 @@
 /*
-MIT License http://www.opensource.org/licenses/mit-license.php
-Author Tobias Koppers @sokra
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
-const util = require("util");
+const ChunkGraph = require("./ChunkGraph");
+const Entrypoint = require("./Entrypoint");
+const { intersect } = require("./util/SetHelpers");
 const SortableSet = require("./util/SortableSet");
-const intersect = require("./util/SetHelpers").intersect;
-const GraphHelpers = require("./GraphHelpers");
-let debugId = 1000;
-const ERR_CHUNK_ENTRY = "Chunk.entry was removed. Use hasRuntime()";
-const ERR_CHUNK_INITIAL =
-	"Chunk.initial was removed. Use canBeInitial/isOnlyInitial()";
-
-/** @typedef {import("./Module.js")} Module */
+const StringXor = require("./util/StringXor");
+const {
+	compareChunkGroupsByIndex,
+	compareModulesById,
+	compareModulesByIdentifier
+} = require("./util/comparators");
+const { createArrayToSetDeprecationSet } = require("./util/deprecation");
+const { mergeRuntime } = require("./util/runtime");
+
+/** @typedef {import("./ChunkGraph").ChunkFilterPredicate} ChunkFilterPredicate */
+/** @typedef {import("./ChunkGraph").ChunkSizeOptions} ChunkSizeOptions */
+/** @typedef {import("./ChunkGraph").ModuleFilterPredicate} ModuleFilterPredicate */
+/** @typedef {import("./ChunkGraph").ModuleId} ModuleId */
 /** @typedef {import("./ChunkGroup")} ChunkGroup */
-/** @typedef {import("./ModuleReason.js")} ModuleReason */
-/** @typedef {import("webpack-sources").Source} Source */
+/** @typedef {import("./ChunkGroup").ChunkGroupOptions} ChunkGroupOptions */
+/** @typedef {import("./Entrypoint").EntryOptions} EntryOptions */
+/** @typedef {import("./Module")} Module */
+/** @typedef {import("./Compilation").PathDataChunk} PathDataChunk */
+/** @typedef {import("./TemplatedPathPlugin").TemplatePathFn} ChunkFilenameTemplateFn */
+/** @typedef {string | ChunkFilenameTemplateFn} ChunkFilenameTemplate */
+/** @typedef {import("./util/Hash")} Hash */
+/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */
 
-/**
- *  @typedef {Object} WithId an object who has an id property *
- *  @property {string | number} id the id of the object
- */
+/** @typedef {string | null} ChunkName */
+/** @typedef {string | number} ChunkId */
+/** @typedef {SortableSet} IdNameHints */
 
-/**
- * Compare two Modules based on their ids for sorting
- * @param {Module} a module
- * @param {Module} b module
- * @returns {-1|0|1} sort value
- */
-const sortModuleById = (a, b) => {
-	if (a.id < b.id) return -1;
-	if (b.id < a.id) return 1;
-	return 0;
-};
+const ChunkFilesSet = createArrayToSetDeprecationSet("chunk.files");
 
 /**
- * Compare two ChunkGroups based on their ids for sorting
- * @param {ChunkGroup} a chunk group
- * @param {ChunkGroup} b chunk group
- * @returns {-1|0|1} sort value
+ * Defines the chunk maps type used by this module.
+ * @deprecated
+ * @typedef {object} ChunkMaps
+ * @property {Record} hash
+ * @property {Record>} contentHash
+ * @property {Record} name
  */
-const sortChunkGroupById = (a, b) => {
-	if (a.id < b.id) return -1;
-	if (b.id < a.id) return 1;
-	return 0;
-};
 
 /**
- * Compare two Identifiables , based on their ids for sorting
- * @param {Module} a first object with ident fn
- * @param {Module} b second object with ident fn
- * @returns {-1|0|1} The order number of the sort
+ * Defines the chunk module id map type used by this module.
+ * @deprecated
+ * @typedef {Record} ChunkModuleIdMap
  */
-const sortByIdentifier = (a, b) => {
-	if (a.identifier() > b.identifier()) return 1;
-	if (a.identifier() < b.identifier()) return -1;
-	return 0;
-};
 
 /**
- * @returns {string} a concatenation of module identifiers sorted
- * @param {SortableSet} set to pull module identifiers from
+ * Defines the chunk module hash map type used by this module.
+ * @deprecated
+ * @typedef {Record} chunkModuleHashMap
  */
-const getModulesIdent = set => {
-	set.sort();
-	let str = "";
-	for (const m of set) {
-		str += m.identifier() + "#";
-	}
-	return str;
-};
 
 /**
- * @template T
- * @param {SortableSet} set the sortable set to convert to array
- * @returns {Array} the array returned from Array.from(set)
+ * Defines the chunk module maps type used by this module.
+ * @deprecated
+ * @typedef {object} ChunkModuleMaps
+ * @property {ChunkModuleIdMap} id
+ * @property {chunkModuleHashMap} hash
  */
-const getArray = set => Array.from(set);
 
-/**
- * @param {SortableSet} set the sortable Set to get the count/size of
- * @returns {number} the size of the modules
- */
-const getModulesSize = set => {
-	let size = 0;
-	for (const module of set) {
-		size += module.size();
-	}
-	return size;
-};
+/** @typedef {Set} Chunks */
+/** @typedef {Set} Entrypoints */
+/** @typedef {Set} Queue */
+/** @typedef {SortableSet} SortableChunkGroups */
+/** @typedef {Record} ChunkChildIdsByOrdersMap */
+/** @typedef {Record} ChunkChildIdsByOrdersMapByData */
+/** @typedef {{ onChunks: Chunk[], chunks: Chunks }} ChunkChildOfTypeInOrder */
+
+let debugId = 1000;
 
 /**
  * A Chunk is a unit of encapsulation for Modules.
@@ -97,304 +83,535 @@ const getModulesSize = set => {
  */
 class Chunk {
 	/**
-	 * @param {string=} name of chunk being created, is optional (for subclasses)
+	 * Creates an instance of Chunk.
+	 * @param {ChunkName=} name of chunk being created, is optional (for subclasses)
+	 * @param {boolean} backCompat enable backward-compatibility
 	 */
-	constructor(name) {
-		/** @type {number | null} */
+	constructor(name, backCompat = true) {
+		/** @type {ChunkId | null} */
 		this.id = null;
-		/** @type {number[] | null} */
+		/** @type {ChunkId[] | null} */
 		this.ids = null;
 		/** @type {number} */
 		this.debugId = debugId++;
-		/** @type {string} */
+		/** @type {ChunkName | undefined} */
 		this.name = name;
+		/** @type {IdNameHints} */
+		this.idNameHints = new SortableSet();
 		/** @type {boolean} */
 		this.preventIntegration = false;
-		/** @type {Module=} */
-		this.entryModule = undefined;
-		/** @private @type {SortableSet} */
-		this._modules = new SortableSet(undefined, sortByIdentifier);
-		/** @private @type {SortableSet} */
-		this._groups = new SortableSet(undefined, sortChunkGroupById);
-		/** @type {Source[]} */
-		this.files = [];
+		/** @type {ChunkFilenameTemplate | undefined} */
+		this.filenameTemplate = undefined;
+		/** @type {ChunkFilenameTemplate | undefined} */
+		this.cssFilenameTemplate = undefined;
+		/**
+		 * @private
+		 * @type {SortableChunkGroups}
+		 */
+		this._groups = new SortableSet(undefined, compareChunkGroupsByIndex);
+		/** @type {RuntimeSpec} */
+		this.runtime = undefined;
+		/** @type {Set} */
+		this.files = backCompat ? new ChunkFilesSet() : new Set();
+		/** @type {Set} */
+		this.auxiliaryFiles = new Set();
 		/** @type {boolean} */
 		this.rendered = false;
 		/** @type {string=} */
 		this.hash = undefined;
-		/** @type {Object} */
+		/** @type {Record} */
 		this.contentHash = Object.create(null);
+		// Full (untruncated) content digests, so `[contenthash:]` re-encodes
+		// from full entropy. Transient: repopulated by the `contentHash` hook each seal.
+		/** @type {Record} */
+		this.contentHashFull = Object.create(null);
 		/** @type {string=} */
 		this.renderedHash = undefined;
 		/** @type {string=} */
 		this.chunkReason = undefined;
 		/** @type {boolean} */
 		this.extraAsync = false;
-		this.removedModules = undefined;
 	}
 
+	// TODO remove in webpack 6
+	// BACKWARD-COMPAT START
 	/**
-	 * @deprecated Chunk.entry has been deprecated. Please use .hasRuntime() instead
-	 * @returns {never} Throws an error trying to access this property
+	 * Returns entry module.
+	 * @deprecated
+	 * @returns {Module | undefined} entry module
 	 */
-	get entry() {
-		throw new Error(ERR_CHUNK_ENTRY);
+	get entryModule() {
+		const entryModules = [
+			...ChunkGraph.getChunkGraphForChunk(
+				this,
+				"Chunk.entryModule",
+				"DEP_WEBPACK_CHUNK_ENTRY_MODULE"
+			).getChunkEntryModulesIterable(this)
+		];
+		if (entryModules.length === 0) {
+			return undefined;
+		} else if (entryModules.length === 1) {
+			return entryModules[0];
+		}
+
+		throw new Error(
+			"Module.entryModule: Multiple entry modules are not supported by the deprecated API (Use the new ChunkGroup API)"
+		);
 	}
 
 	/**
-	 * @deprecated .entry has been deprecated. Please use .hasRuntime() instead
-	 * @param {never} data The data that was attempting to be set
-	 * @returns {never} Throws an error trying to access this property
+	 * Checks whether this chunk has an entry module.
+	 * @deprecated
+	 * @returns {boolean} true, if the chunk contains an entry module
 	 */
-	set entry(data) {
-		throw new Error(ERR_CHUNK_ENTRY);
+	hasEntryModule() {
+		return (
+			ChunkGraph.getChunkGraphForChunk(
+				this,
+				"Chunk.hasEntryModule",
+				"DEP_WEBPACK_CHUNK_HAS_ENTRY_MODULE"
+			).getNumberOfEntryModules(this) > 0
+		);
 	}
 
 	/**
-	 * @deprecated Chunk.initial was removed. Use canBeInitial/isOnlyInitial()
-	 * @returns {never} Throws an error trying to access this property
+	 * Adds the provided module to the chunk.
+	 * @deprecated
+	 * @param {Module} module the module
+	 * @returns {boolean} true, if the chunk could be added
 	 */
-	get initial() {
-		throw new Error(ERR_CHUNK_INITIAL);
+	addModule(module) {
+		const chunkGraph = ChunkGraph.getChunkGraphForChunk(
+			this,
+			"Chunk.addModule",
+			"DEP_WEBPACK_CHUNK_ADD_MODULE"
+		);
+		if (chunkGraph.isModuleInChunk(module, this)) return false;
+		chunkGraph.connectChunkAndModule(this, module);
+		return true;
 	}
 
 	/**
-	 * @deprecated Chunk.initial was removed. Use canBeInitial/isOnlyInitial()
-	 * @param {never} data The data attempting to be set
-	 * @returns {never} Throws an error trying to access this property
+	 * Removes the provided module from the chunk.
+	 * @deprecated
+	 * @param {Module} module the module
+	 * @returns {void}
 	 */
-	set initial(data) {
-		throw new Error(ERR_CHUNK_INITIAL);
+	removeModule(module) {
+		ChunkGraph.getChunkGraphForChunk(
+			this,
+			"Chunk.removeModule",
+			"DEP_WEBPACK_CHUNK_REMOVE_MODULE"
+		).disconnectChunkAndModule(this, module);
 	}
 
 	/**
-	 * @returns {boolean} whether or not the Chunk will have a runtime
+	 * Gets the number of modules in this chunk.
+	 * @deprecated
+	 * @returns {number} the number of module which are contained in this chunk
 	 */
-	hasRuntime() {
-		for (const chunkGroup of this._groups) {
-			// We only need to check the first one
-			return chunkGroup.isInitial() && chunkGroup.getRuntimeChunk() === this;
-		}
-		return false;
+	getNumberOfModules() {
+		return ChunkGraph.getChunkGraphForChunk(
+			this,
+			"Chunk.getNumberOfModules",
+			"DEP_WEBPACK_CHUNK_GET_NUMBER_OF_MODULES"
+		).getNumberOfChunkModules(this);
 	}
 
 	/**
-	 * @returns {boolean} whether or not this chunk can be an initial chunk
+	 * @deprecated
+	 * @returns {Iterable} modules
 	 */
-	canBeInitial() {
-		for (const chunkGroup of this._groups) {
-			if (chunkGroup.isInitial()) return true;
-		}
-		return false;
+	get modulesIterable() {
+		const chunkGraph = ChunkGraph.getChunkGraphForChunk(
+			this,
+			"Chunk.modulesIterable",
+			"DEP_WEBPACK_CHUNK_MODULES_ITERABLE"
+		);
+		return chunkGraph.getOrderedChunkModulesIterable(
+			this,
+			compareModulesByIdentifier
+		);
 	}
 
 	/**
-	 * @returns {boolean} whether this chunk can only be an initial chunk
+	 * Compares this chunk with another chunk.
+	 * @deprecated
+	 * @param {Chunk} otherChunk the chunk to compare with
+	 * @returns {-1 | 0 | 1} the comparison result
 	 */
-	isOnlyInitial() {
-		if (this._groups.size <= 0) return false;
-		for (const chunkGroup of this._groups) {
-			if (!chunkGroup.isInitial()) return false;
-		}
-		return true;
+	compareTo(otherChunk) {
+		const chunkGraph = ChunkGraph.getChunkGraphForChunk(
+			this,
+			"Chunk.compareTo",
+			"DEP_WEBPACK_CHUNK_COMPARE_TO"
+		);
+		return chunkGraph.compareChunks(this, otherChunk);
 	}
 
 	/**
-	 * @returns {boolean} if this chunk contains the entry module
+	 * Checks whether this chunk contains the module.
+	 * @deprecated
+	 * @param {Module} module the module
+	 * @returns {boolean} true, if the chunk contains the module
 	 */
-	hasEntryModule() {
-		return !!this.entryModule;
+	containsModule(module) {
+		return ChunkGraph.getChunkGraphForChunk(
+			this,
+			"Chunk.containsModule",
+			"DEP_WEBPACK_CHUNK_CONTAINS_MODULE"
+		).isModuleInChunk(module, this);
 	}
 
 	/**
-	 * @param {Module} module the module that will be added to this chunk.
-	 * @returns {boolean} returns true if the chunk doesn't have the module and it was added
+	 * Returns the modules for this chunk.
+	 * @deprecated
+	 * @returns {Module[]} the modules for this chunk
 	 */
-	addModule(module) {
-		if (!this._modules.has(module)) {
-			this._modules.add(module);
-			return true;
-		}
-		return false;
+	getModules() {
+		return ChunkGraph.getChunkGraphForChunk(
+			this,
+			"Chunk.getModules",
+			"DEP_WEBPACK_CHUNK_GET_MODULES"
+		).getChunkModules(this);
 	}
 
 	/**
-	 * @param {Module} module the module that will be removed from this chunk
-	 * @returns {boolean} returns true if chunk exists and is successfully deleted
+	 * Removes this chunk from the chunk graph and chunk groups.
+	 * @deprecated
+	 * @returns {void}
 	 */
-	removeModule(module) {
-		if (this._modules.delete(module)) {
-			module.removeChunk(this);
+	remove() {
+		const chunkGraph = ChunkGraph.getChunkGraphForChunk(
+			this,
+			"Chunk.remove",
+			"DEP_WEBPACK_CHUNK_REMOVE"
+		);
+		chunkGraph.disconnectChunk(this);
+		this.disconnectFromGroups();
+	}
+
+	/**
+	 * Moves a module from this chunk to another chunk.
+	 * @deprecated
+	 * @param {Module} module the module
+	 * @param {Chunk} otherChunk the target chunk
+	 * @returns {void}
+	 */
+	moveModule(module, otherChunk) {
+		const chunkGraph = ChunkGraph.getChunkGraphForChunk(
+			this,
+			"Chunk.moveModule",
+			"DEP_WEBPACK_CHUNK_MOVE_MODULE"
+		);
+		chunkGraph.disconnectChunkAndModule(this, module);
+		chunkGraph.connectChunkAndModule(otherChunk, module);
+	}
+
+	/**
+	 * Integrates another chunk into this chunk when possible.
+	 * @deprecated
+	 * @param {Chunk} otherChunk the other chunk
+	 * @returns {boolean} true, if the specified chunk has been integrated
+	 */
+	integrate(otherChunk) {
+		const chunkGraph = ChunkGraph.getChunkGraphForChunk(
+			this,
+			"Chunk.integrate",
+			"DEP_WEBPACK_CHUNK_INTEGRATE"
+		);
+		if (chunkGraph.canChunksBeIntegrated(this, otherChunk)) {
+			chunkGraph.integrateChunks(this, otherChunk);
 			return true;
 		}
+
 		return false;
 	}
 
 	/**
-	 * @param {Module[]} modules the new modules to be set
-	 * @returns {void} set new modules to this chunk and return nothing
+	 * Checks whether this chunk can be integrated with another chunk.
+	 * @deprecated
+	 * @param {Chunk} otherChunk the other chunk
+	 * @returns {boolean} true, if chunks could be integrated
 	 */
-	setModules(modules) {
-		this._modules = new SortableSet(modules, sortByIdentifier);
+	canBeIntegrated(otherChunk) {
+		const chunkGraph = ChunkGraph.getChunkGraphForChunk(
+			this,
+			"Chunk.canBeIntegrated",
+			"DEP_WEBPACK_CHUNK_CAN_BE_INTEGRATED"
+		);
+		return chunkGraph.canChunksBeIntegrated(this, otherChunk);
 	}
 
 	/**
-	 * @returns {number} the amount of modules in chunk
+	 * Checks whether this chunk is empty.
+	 * @deprecated
+	 * @returns {boolean} true, if this chunk contains no module
 	 */
-	getNumberOfModules() {
-		return this._modules.size;
+	isEmpty() {
+		const chunkGraph = ChunkGraph.getChunkGraphForChunk(
+			this,
+			"Chunk.isEmpty",
+			"DEP_WEBPACK_CHUNK_IS_EMPTY"
+		);
+		return chunkGraph.getNumberOfChunkModules(this) === 0;
 	}
 
 	/**
-	 * @returns {SortableSet} return the modules SortableSet for this chunk
+	 * Returns the total size of all modules in this chunk.
+	 * @deprecated
+	 * @returns {number} total size of all modules in this chunk
 	 */
-	get modulesIterable() {
-		return this._modules;
+	modulesSize() {
+		const chunkGraph = ChunkGraph.getChunkGraphForChunk(
+			this,
+			"Chunk.modulesSize",
+			"DEP_WEBPACK_CHUNK_MODULES_SIZE"
+		);
+		return chunkGraph.getChunkModulesSize(this);
 	}
 
 	/**
-	 * @param {ChunkGroup} chunkGroup the chunkGroup the chunk is being added
-	 * @returns {boolean} returns true if chunk is not apart of chunkGroup and is added successfully
+	 * Returns the estimated size for the requested source type.
+	 * @deprecated
+	 * @param {ChunkSizeOptions} options options object
+	 * @returns {number} total size of this chunk
 	 */
-	addGroup(chunkGroup) {
-		if (this._groups.has(chunkGroup)) return false;
-		this._groups.add(chunkGroup);
-		return true;
+	size(options = {}) {
+		const chunkGraph = ChunkGraph.getChunkGraphForChunk(
+			this,
+			"Chunk.size",
+			"DEP_WEBPACK_CHUNK_SIZE"
+		);
+		return chunkGraph.getChunkSize(this, options);
 	}
 
 	/**
-	 * @param {ChunkGroup} chunkGroup the chunkGroup the chunk is being removed from
-	 * @returns {boolean} returns true if chunk does exist in chunkGroup and is removed
+	 * Returns the integrated size with another chunk.
+	 * @deprecated
+	 * @param {Chunk} otherChunk the other chunk
+	 * @param {ChunkSizeOptions} options options object
+	 * @returns {number} total size of the chunk or false if the chunk can't be integrated
 	 */
-	removeGroup(chunkGroup) {
-		if (!this._groups.has(chunkGroup)) return false;
-		this._groups.delete(chunkGroup);
-		return true;
+	integratedSize(otherChunk, options) {
+		const chunkGraph = ChunkGraph.getChunkGraphForChunk(
+			this,
+			"Chunk.integratedSize",
+			"DEP_WEBPACK_CHUNK_INTEGRATED_SIZE"
+		);
+		return chunkGraph.getIntegratedChunksSize(this, otherChunk, options);
 	}
 
 	/**
-	 * @param {ChunkGroup} chunkGroup the chunkGroup to check
-	 * @returns {boolean} returns true if chunk has chunkGroup reference and exists in chunkGroup
+	 * Gets chunk module maps.
+	 * @deprecated
+	 * @param {ModuleFilterPredicate} filterFn function used to filter modules
+	 * @returns {ChunkModuleMaps} module map information
 	 */
-	isInGroup(chunkGroup) {
-		return this._groups.has(chunkGroup);
+	getChunkModuleMaps(filterFn) {
+		const chunkGraph = ChunkGraph.getChunkGraphForChunk(
+			this,
+			"Chunk.getChunkModuleMaps",
+			"DEP_WEBPACK_CHUNK_GET_CHUNK_MODULE_MAPS"
+		);
+		/** @type {ChunkModuleIdMap} */
+		const chunkModuleIdMap = Object.create(null);
+		/** @type {chunkModuleHashMap} */
+		const chunkModuleHashMap = Object.create(null);
+
+		for (const asyncChunk of this.getAllAsyncChunks()) {
+			/** @type {ChunkId[] | undefined} */
+			let array;
+			for (const module of chunkGraph.getOrderedChunkModulesIterable(
+				asyncChunk,
+				compareModulesById(chunkGraph)
+			)) {
+				if (filterFn(module)) {
+					if (array === undefined) {
+						array = [];
+						chunkModuleIdMap[/** @type {ChunkId} */ (asyncChunk.id)] = array;
+					}
+					const moduleId =
+						/** @type {ModuleId} */
+						(chunkGraph.getModuleId(module));
+					array.push(moduleId);
+					chunkModuleHashMap[moduleId] = chunkGraph.getRenderedModuleHash(
+						module,
+						undefined
+					);
+				}
+			}
+		}
+
+		return {
+			id: chunkModuleIdMap,
+			hash: chunkModuleHashMap
+		};
 	}
 
 	/**
-	 * @returns {number} the amount of groups said chunk is in
+	 * Checks whether this chunk contains a matching module in the graph.
+	 * @deprecated
+	 * @param {ModuleFilterPredicate} filterFn predicate function used to filter modules
+	 * @param {ChunkFilterPredicate=} filterChunkFn predicate function used to filter chunks
+	 * @returns {boolean} return true if module exists in graph
 	 */
-	getNumberOfGroups() {
-		return this._groups.size;
+	hasModuleInGraph(filterFn, filterChunkFn) {
+		const chunkGraph = ChunkGraph.getChunkGraphForChunk(
+			this,
+			"Chunk.hasModuleInGraph",
+			"DEP_WEBPACK_CHUNK_HAS_MODULE_IN_GRAPH"
+		);
+		return chunkGraph.hasModuleInGraph(this, filterFn, filterChunkFn);
 	}
 
 	/**
-	 * @returns {SortableSet} the chunkGroups that said chunk is referenced in
+	 * Returns the chunk map information.
+	 * @deprecated
+	 * @param {boolean} realHash whether the full hash or the rendered hash is to be used
+	 * @returns {ChunkMaps} the chunk map information
 	 */
-	get groupsIterable() {
-		return this._groups;
+	getChunkMaps(realHash) {
+		/** @type {Record} */
+		const chunkHashMap = Object.create(null);
+		/** @type {Record>} */
+		const chunkContentHashMap = Object.create(null);
+		/** @type {Record} */
+		const chunkNameMap = Object.create(null);
+
+		for (const chunk of this.getAllAsyncChunks()) {
+			const id = /** @type {ChunkId} */ (chunk.id);
+			chunkHashMap[id] =
+				/** @type {string} */
+				(realHash ? chunk.hash : chunk.renderedHash);
+			for (const key of Object.keys(chunk.contentHash)) {
+				if (!chunkContentHashMap[key]) {
+					chunkContentHashMap[key] = Object.create(null);
+				}
+				chunkContentHashMap[key][id] = chunk.contentHash[key];
+			}
+			if (chunk.name) {
+				chunkNameMap[id] = chunk.name;
+			}
+		}
+
+		return {
+			hash: chunkHashMap,
+			contentHash: chunkContentHashMap,
+			name: chunkNameMap
+		};
 	}
+	// BACKWARD-COMPAT END
 
 	/**
-	 * @param {Chunk} otherChunk the chunk to compare itself with
-	 * @returns {-1|0|1} this is a comparitor function like sort and returns -1, 0, or 1 based on sort order
+	 * Checks whether this chunk has runtime.
+	 * @returns {boolean} whether or not the Chunk will have a runtime
 	 */
-	compareTo(otherChunk) {
-		this._modules.sort();
-		otherChunk._modules.sort();
-		if (this._modules.size > otherChunk._modules.size) return -1;
-		if (this._modules.size < otherChunk._modules.size) return 1;
-		const a = this._modules[Symbol.iterator]();
-		const b = otherChunk._modules[Symbol.iterator]();
-		// eslint-disable-next-line
-		while (true) {
-			const aItem = a.next();
-			const bItem = b.next();
-			if (aItem.done) return 0;
-			const aModuleIdentifier = aItem.value.identifier();
-			const bModuleIdentifier = bItem.value.identifier();
-			if (aModuleIdentifier < bModuleIdentifier) return -1;
-			if (aModuleIdentifier > bModuleIdentifier) return 1;
+	hasRuntime() {
+		for (const chunkGroup of this._groups) {
+			if (
+				chunkGroup instanceof Entrypoint &&
+				chunkGroup.getRuntimeChunk() === this
+			) {
+				return true;
+			}
 		}
+		return false;
 	}
 
 	/**
-	 * @param {Module} module Module to check
-	 * @returns {boolean} returns true if module does exist in this chunk
+	 * Checks whether it can be initial.
+	 * @returns {boolean} whether or not this chunk can be an initial chunk
 	 */
-	containsModule(module) {
-		return this._modules.has(module);
-	}
-
-	getModules() {
-		return this._modules.getFromCache(getArray);
+	canBeInitial() {
+		for (const chunkGroup of this._groups) {
+			if (chunkGroup.isInitial()) return true;
+		}
+		return false;
 	}
 
-	getModulesIdent() {
-		return this._modules.getFromUnorderedCache(getModulesIdent);
+	/**
+	 * Checks whether this chunk is only initial.
+	 * @returns {boolean} whether this chunk can only be an initial chunk
+	 */
+	isOnlyInitial() {
+		if (this._groups.size <= 0) return false;
+		for (const chunkGroup of this._groups) {
+			if (!chunkGroup.isInitial()) return false;
+		}
+		return true;
 	}
 
-	remove() {
-		// cleanup modules
-		// Array.from is used here to create a clone, because removeChunk modifies this._modules
-		for (const module of Array.from(this._modules)) {
-			module.removeChunk(this);
-		}
+	/**
+	 * Gets entry options.
+	 * @returns {EntryOptions | undefined} the entry options for this chunk
+	 */
+	getEntryOptions() {
 		for (const chunkGroup of this._groups) {
-			chunkGroup.removeChunk(this);
+			if (chunkGroup instanceof Entrypoint) {
+				return chunkGroup.options;
+			}
 		}
+		return undefined;
 	}
 
 	/**
-	 *
-	 * @param {Module} module module to move
-	 * @param {Chunk} otherChunk other chunk to move it to
+	 * Adds the provided chunk group to the chunk.
+	 * @param {ChunkGroup} chunkGroup the chunkGroup the chunk is being added
 	 * @returns {void}
 	 */
-	moveModule(module, otherChunk) {
-		GraphHelpers.disconnectChunkAndModule(this, module);
-		GraphHelpers.connectChunkAndModule(otherChunk, module);
-		module.rewriteChunkInReasons(this, [otherChunk]);
+	addGroup(chunkGroup) {
+		this._groups.add(chunkGroup);
 	}
 
 	/**
-	 *
-	 * @param {Chunk} otherChunk the chunk to integrate with
-	 * @param {ModuleReason} reason reason why the module is being integrated
-	 * @returns {boolean} returns true or false if integration succeeds or fails
+	 * Removes the provided chunk group from the chunk.
+	 * @param {ChunkGroup} chunkGroup the chunkGroup the chunk is being removed from
+	 * @returns {void}
 	 */
-	integrate(otherChunk, reason) {
-		if (!this.canBeIntegrated(otherChunk)) {
-			return false;
-		}
+	removeGroup(chunkGroup) {
+		this._groups.delete(chunkGroup);
+	}
 
-		// Array.from is used here to create a clone, because moveModule modifies otherChunk._modules
-		for (const module of Array.from(otherChunk._modules)) {
-			otherChunk.moveModule(module, this);
-		}
-		otherChunk._modules.clear();
+	/**
+	 * Checks whether this chunk is in group.
+	 * @param {ChunkGroup} chunkGroup the chunkGroup to check
+	 * @returns {boolean} returns true if chunk has chunkGroup reference and exists in chunkGroup
+	 */
+	isInGroup(chunkGroup) {
+		return this._groups.has(chunkGroup);
+	}
 
-		for (const chunkGroup of otherChunk._groups) {
-			chunkGroup.replaceChunk(otherChunk, this);
-			this.addGroup(chunkGroup);
-		}
-		otherChunk._groups.clear();
-
-		if (this.name && otherChunk.name) {
-			if (this.name.length !== otherChunk.name.length) {
-				this.name =
-					this.name.length < otherChunk.name.length
-						? this.name
-						: otherChunk.name;
-			} else {
-				this.name = this.name < otherChunk.name ? this.name : otherChunk.name;
-			}
-		}
+	/**
+	 * Gets number of groups.
+	 * @returns {number} the amount of groups that the said chunk is in
+	 */
+	getNumberOfGroups() {
+		return this._groups.size;
+	}
 
-		return true;
+	/**
+	 * Gets groups iterable.
+	 * @returns {SortableChunkGroups} the chunkGroups that the said chunk is referenced in
+	 */
+	get groupsIterable() {
+		this._groups.sort();
+		return this._groups;
+	}
+
+	/**
+	 * Disconnects from groups.
+	 * @returns {void}
+	 */
+	disconnectFromGroups() {
+		for (const chunkGroup of this._groups) {
+			chunkGroup.removeChunk(this);
+		}
 	}
 
 	/**
-	 * @param {Chunk} newChunk the new chunk that will be split out of, and then chunk raphi twil=
+	 * Processes the provided new chunk.
+	 * @param {Chunk} newChunk the new chunk that will be split out of
 	 * @returns {void}
 	 */
 	split(newChunk) {
@@ -402,121 +619,151 @@ class Chunk {
 			chunkGroup.insertChunk(newChunk, this);
 			newChunk.addGroup(chunkGroup);
 		}
+		for (const idHint of this.idNameHints) {
+			newChunk.idNameHints.add(idHint);
+		}
+		newChunk.runtime = mergeRuntime(newChunk.runtime, this.runtime);
 	}
 
-	isEmpty() {
-		return this._modules.size === 0;
-	}
-
-	updateHash(hash) {
-		hash.update(`${this.id} `);
-		hash.update(this.ids ? this.ids.join(",") : "");
-		hash.update(`${this.name || ""} `);
-		for (const m of this._modules) {
-			hash.update(m.hash);
+	/**
+	 * Updates the hash with the data contributed by this instance.
+	 * @param {Hash} hash hash (will be modified)
+	 * @param {ChunkGraph} chunkGraph the chunk graph
+	 * @returns {void}
+	 */
+	updateHash(hash, chunkGraph) {
+		hash.update(
+			`${this.id} ${this.ids ? this.ids.join() : ""} ${this.name || ""} `
+		);
+		const xor = new StringXor();
+		for (const m of chunkGraph.getChunkModulesIterable(this)) {
+			xor.add(chunkGraph.getModuleHash(m, this.runtime));
+		}
+		xor.updateHash(hash);
+		const entryModules =
+			chunkGraph.getChunkEntryModulesWithChunkGroupIterable(this);
+		for (const [m, chunkGroup] of entryModules) {
+			hash.update(
+				`entry${chunkGraph.getModuleId(m)}${
+					/** @type {ChunkGroup} */ (chunkGroup).id
+				}`
+			);
 		}
 	}
 
-	canBeIntegrated(otherChunk) {
-		const isAvailable = (a, b) => {
-			const queue = new Set(b.groupsIterable);
-			for (const chunkGroup of queue) {
-				if (a.isInGroup(chunkGroup)) continue;
-				if (chunkGroup.isInitial()) return false;
-				for (const parent of chunkGroup.parentsIterable) {
-					queue.add(parent);
-				}
-			}
-			return true;
-		};
+	/**
+	 * Gets all async chunks.
+	 * @returns {Chunks} a set of all the async chunks
+	 */
+	getAllAsyncChunks() {
+		/** @type {Queue} */
+		const queue = new Set();
+		/** @type {Chunks} */
+		const chunks = new Set();
 
-		if (this.preventIntegration || otherChunk.preventIntegration) {
-			return false;
-		}
+		const initialChunks = intersect(
+			Array.from(this.groupsIterable, (g) => new Set(g.chunks))
+		);
 
-		if (this.hasRuntime() !== otherChunk.hasRuntime()) {
-			if (this.hasRuntime()) {
-				return isAvailable(this, otherChunk);
-			} else if (otherChunk.hasRuntime()) {
-				return isAvailable(otherChunk, this);
-			} else {
-				return false;
+		/** @type {Queue} */
+		const initialQueue = new Set(this.groupsIterable);
+
+		for (const chunkGroup of initialQueue) {
+			for (const child of chunkGroup.childrenIterable) {
+				if (child instanceof Entrypoint) {
+					initialQueue.add(child);
+				} else {
+					queue.add(child);
+				}
 			}
 		}
 
-		if (this.hasEntryModule() || otherChunk.hasEntryModule()) {
-			return false;
+		for (const chunkGroup of queue) {
+			for (const chunk of chunkGroup.chunks) {
+				if (!initialChunks.has(chunk)) {
+					chunks.add(chunk);
+				}
+			}
+			for (const child of chunkGroup.childrenIterable) {
+				queue.add(child);
+			}
 		}
 
-		return true;
+		return chunks;
 	}
 
 	/**
-	 *
-	 * @param {number} size the size
-	 * @param {Object} options the options passed in
-	 * @returns {number} the multiplier returned
+	 * Gets all initial chunks.
+	 * @returns {Chunks} a set of all the initial chunks (including itself)
 	 */
-	addMultiplierAndOverhead(size, options) {
-		const overhead =
-			typeof options.chunkOverhead === "number" ? options.chunkOverhead : 10000;
-		const multiplicator = this.canBeInitial()
-			? options.entryChunkMultiplicator || 10
-			: 1;
-
-		return size * multiplicator + overhead;
+	getAllInitialChunks() {
+		/** @type {Chunks} */
+		const chunks = new Set();
+		/** @type {Queue} */
+		const queue = new Set(this.groupsIterable);
+		for (const group of queue) {
+			if (group.isInitial()) {
+				for (const c of group.chunks) chunks.add(c);
+				for (const g of group.childrenIterable) queue.add(g);
+			}
+		}
+		return chunks;
 	}
 
 	/**
-	 * @returns {number} the size of all modules
+	 * Gets all referenced chunks.
+	 * @returns {Chunks} a set of all the referenced chunks (including itself)
 	 */
-	modulesSize() {
-		return this._modules.getFromUnorderedCache(getModulesSize);
+	getAllReferencedChunks() {
+		/** @type {Queue} */
+		const queue = new Set(this.groupsIterable);
+		/** @type {Chunks} */
+		const chunks = new Set();
+
+		for (const chunkGroup of queue) {
+			for (const chunk of chunkGroup.chunks) {
+				chunks.add(chunk);
+			}
+			for (const child of chunkGroup.childrenIterable) {
+				queue.add(child);
+			}
+		}
+
+		return chunks;
 	}
 
 	/**
-	 * @param {Object} options the size display options
-	 * @returns {number} the chunk size
+	 * Gets all referenced async entrypoints.
+	 * @returns {Entrypoints} a set of all the referenced entrypoints
 	 */
-	size(options) {
-		return this.addMultiplierAndOverhead(this.modulesSize(), options);
-	}
-
-	integratedSize(otherChunk, options) {
-		// Chunk if it's possible to integrate this chunk
-		if (!this.canBeIntegrated(otherChunk)) {
-			return false;
-		}
+	getAllReferencedAsyncEntrypoints() {
+		/** @type {Queue} */
+		const queue = new Set(this.groupsIterable);
+		/** @type {Entrypoints} */
+		const entrypoints = new Set();
 
-		let integratedModulesSize = this.modulesSize();
-		// only count modules that do not exist in this chunk!
-		for (const otherModule of otherChunk._modules) {
-			if (!this._modules.has(otherModule)) {
-				integratedModulesSize += otherModule.size();
+		for (const chunkGroup of queue) {
+			for (const entrypoint of chunkGroup.asyncEntrypointsIterable) {
+				entrypoints.add(/** @type {Entrypoint} */ (entrypoint));
+			}
+			for (const child of chunkGroup.childrenIterable) {
+				queue.add(child);
 			}
 		}
 
-		return this.addMultiplierAndOverhead(integratedModulesSize, options);
+		return entrypoints;
 	}
 
 	/**
-	 * @param {function(Module, Module): -1|0|1=} sortByFn a predicate function used to sort modules
-	 * @returns {void}
+	 * Checks whether this chunk has async chunks.
+	 * @returns {boolean} true, if the chunk references async chunks
 	 */
-	sortModules(sortByFn) {
-		this._modules.sortWith(sortByFn || sortModuleById);
-	}
-
-	sortItems() {
-		this.sortModules();
-	}
-
-	getAllAsyncChunks() {
+	hasAsyncChunks() {
+		/** @type {Queue} */
 		const queue = new Set();
-		const chunks = new Set();
 
 		const initialChunks = intersect(
-			Array.from(this.groupsIterable, g => new Set(g.chunks))
+			Array.from(this.groupsIterable, (g) => new Set(g.chunks))
 		);
 
 		for (const chunkGroup of this.groupsIterable) {
@@ -528,7 +775,7 @@ class Chunk {
 		for (const chunkGroup of queue) {
 			for (const chunk of chunkGroup.chunks) {
 				if (!initialChunks.has(chunk)) {
-					chunks.add(chunk);
+					return true;
 				}
 			}
 			for (const child of chunkGroup.childrenIterable) {
@@ -536,95 +783,149 @@ class Chunk {
 			}
 		}
 
-		return chunks;
-	}
-
-	getChunkMaps(realHash) {
-		const chunkHashMap = Object.create(null);
-		const chunkContentHashMap = Object.create(null);
-		const chunkNameMap = Object.create(null);
-
-		for (const chunk of this.getAllAsyncChunks()) {
-			chunkHashMap[chunk.id] = realHash ? chunk.hash : chunk.renderedHash;
-			for (const key of Object.keys(chunk.contentHash)) {
-				if (!chunkContentHashMap[key]) {
-					chunkContentHashMap[key] = Object.create(null);
-				}
-				chunkContentHashMap[key][chunk.id] = chunk.contentHash[key];
-			}
-			if (chunk.name) {
-				chunkNameMap[chunk.id] = chunk.name;
-			}
-		}
-
-		return {
-			hash: chunkHashMap,
-			contentHash: chunkContentHashMap,
-			name: chunkNameMap
-		};
+		return false;
 	}
 
-	getChildIdsByOrders() {
+	/**
+	 * Gets child ids by orders.
+	 * @param {ChunkGraph} chunkGraph the chunk graph
+	 * @param {ChunkFilterPredicate=} filterFn function used to filter chunks
+	 * @returns {Record} a record object of names to lists of child ids(?)
+	 */
+	getChildIdsByOrders(chunkGraph, filterFn) {
+		/** @type {Map} */
 		const lists = new Map();
 		for (const group of this.groupsIterable) {
 			if (group.chunks[group.chunks.length - 1] === this) {
 				for (const childGroup of group.childrenIterable) {
-					// TODO webpack 5 remove this check for options
-					if (typeof childGroup.options === "object") {
-						for (const key of Object.keys(childGroup.options)) {
-							if (key.endsWith("Order")) {
-								const name = key.substr(0, key.length - "Order".length);
-								let list = lists.get(name);
-								if (list === undefined) lists.set(name, (list = []));
-								list.push({
-									order: childGroup.options[key],
-									group: childGroup
-								});
-							}
+					const edgeOptions = group.getChildOrderOptions(
+						childGroup,
+						chunkGraph
+					);
+					for (const key of Object.keys(edgeOptions)) {
+						const name = key.slice(0, key.length - "Order".length);
+						let list = lists.get(name);
+						if (list === undefined) {
+							list = [];
+							lists.set(name, list);
 						}
+						list.push({
+							order: edgeOptions[key],
+							group: childGroup
+						});
 					}
 				}
 			}
 		}
+		/** @type {Record} */
 		const result = Object.create(null);
 		for (const [name, list] of lists) {
 			list.sort((a, b) => {
 				const cmp = b.order - a.order;
 				if (cmp !== 0) return cmp;
-				// TOOD webpack 5 remove this check of compareTo
-				if (a.group.compareTo) {
-					return a.group.compareTo(b.group);
-				}
-				return 0;
+				return a.group.compareTo(chunkGraph, b.group);
 			});
-			result[name] = Array.from(
-				list.reduce((set, item) => {
-					for (const chunk of item.group.chunks) {
-						set.add(chunk.id);
-					}
-					return set;
-				}, new Set())
-			);
+			/** @type {Set} */
+			const chunkIdSet = new Set();
+			for (const item of list) {
+				for (const chunk of item.group.chunks) {
+					if (filterFn && !filterFn(chunk, chunkGraph)) continue;
+					chunkIdSet.add(/** @type {ChunkId} */ (chunk.id));
+				}
+			}
+			if (chunkIdSet.size > 0) {
+				result[name] = [...chunkIdSet];
+			}
 		}
 		return result;
 	}
 
-	getChildIdsByOrdersMap(includeDirectChildren) {
+	/**
+	 * Gets children of type in order.
+	 * @param {ChunkGraph} chunkGraph the chunk graph
+	 * @param {string} type option name
+	 * @returns {ChunkChildOfTypeInOrder[] | undefined} referenced chunks for a specific type
+	 */
+	getChildrenOfTypeInOrder(chunkGraph, type) {
+		/** @type {{ order: number, group: ChunkGroup, childGroup: ChunkGroup }[]} */
+		const list = [];
+		for (const group of this.groupsIterable) {
+			for (const childGroup of group.childrenIterable) {
+				const edgeOptions = group.getChildOrderOptions(childGroup, chunkGraph);
+				const order = edgeOptions[type];
+				if (order === undefined) continue;
+				list.push({
+					order,
+					group,
+					childGroup
+				});
+			}
+		}
+		if (list.length === 0) return;
+		list.sort((a, b) => {
+			const cmp = b.order - a.order;
+			if (cmp !== 0) return cmp;
+			return a.group.compareTo(chunkGraph, b.group);
+		});
+		/** @type {ChunkChildOfTypeInOrder[]} */
+		const result = [];
+		/** @type {undefined | ChunkChildOfTypeInOrder} */
+		let lastEntry;
+		for (const { group, childGroup } of list) {
+			if (lastEntry && lastEntry.onChunks === group.chunks) {
+				for (const chunk of childGroup.chunks) {
+					lastEntry.chunks.add(chunk);
+				}
+			} else {
+				result.push(
+					(lastEntry = {
+						onChunks: group.chunks,
+						chunks: new Set(childGroup.chunks)
+					})
+				);
+			}
+		}
+		return result;
+	}
+
+	/**
+	 * Gets child ids by orders map.
+	 * @param {ChunkGraph} chunkGraph the chunk graph
+	 * @param {boolean=} includeDirectChildren include direct children (by default only children of async children are included)
+	 * @param {ChunkFilterPredicate=} filterFn function used to filter chunks
+	 * @returns {ChunkChildIdsByOrdersMapByData} a record object of names to lists of child ids(?) by chunk id
+	 */
+	getChildIdsByOrdersMap(chunkGraph, includeDirectChildren, filterFn) {
+		/** @type {ChunkChildIdsByOrdersMapByData} */
 		const chunkMaps = Object.create(null);
 
-		const addChildIdsByOrdersToMap = chunk => {
-			const data = chunk.getChildIdsByOrders();
+		/**
+		 * Adds child ids by orders to map.
+		 * @param {Chunk} chunk a chunk
+		 * @returns {void}
+		 */
+		const addChildIdsByOrdersToMap = (chunk) => {
+			const data = chunk.getChildIdsByOrders(chunkGraph, filterFn);
 			for (const key of Object.keys(data)) {
 				let chunkMap = chunkMaps[key];
 				if (chunkMap === undefined) {
 					chunkMaps[key] = chunkMap = Object.create(null);
 				}
-				chunkMap[chunk.id] = data[key];
+				chunkMap[/** @type {ChunkId} */ (chunk.id)] = data[key];
 			}
 		};
 
 		if (includeDirectChildren) {
-			addChildIdsByOrdersToMap(this);
+			/** @type {Chunks} */
+			const chunks = new Set();
+			for (const chunkGroup of this.groupsIterable) {
+				for (const chunk of chunkGroup.chunks) {
+					chunks.add(chunk);
+				}
+			}
+			for (const chunk of chunks) {
+				addChildIdsByOrdersToMap(chunk);
+			}
 		}
 
 		for (const chunk of this.getAllAsyncChunks()) {
@@ -634,128 +935,36 @@ class Chunk {
 		return chunkMaps;
 	}
 
-	getChunkModuleMaps(filterFn) {
-		const chunkModuleIdMap = Object.create(null);
-		const chunkModuleHashMap = Object.create(null);
-
-		for (const chunk of this.getAllAsyncChunks()) {
-			let array;
-			for (const module of chunk.modulesIterable) {
-				if (filterFn(module)) {
-					if (array === undefined) {
-						array = [];
-						chunkModuleIdMap[chunk.id] = array;
-					}
-					array.push(module.id);
-					chunkModuleHashMap[module.id] = module.renderedHash;
-				}
-			}
-			if (array !== undefined) {
-				array.sort();
-			}
-		}
-
-		return {
-			id: chunkModuleIdMap,
-			hash: chunkModuleHashMap
-		};
-	}
-
 	/**
-	 *
-	 * @param {function(Module): boolean} filterFn predicate function used to filter modules
-	 * @param {function(Chunk): boolean} filterChunkFn predicate function used to filter chunks
-	 * @returns {boolean} return true if module exists in graph
+	 * Checks whether this chunk contains the chunk graph.
+	 * @param {ChunkGraph} chunkGraph the chunk graph
+	 * @param {string} type option name
+	 * @param {boolean=} includeDirectChildren include direct children (by default only children of async children are included)
+	 * @param {ChunkFilterPredicate=} filterFn function used to filter chunks
+	 * @returns {boolean} true when the child is of type order, otherwise false
 	 */
-	hasModuleInGraph(filterFn, filterChunkFn) {
-		const queue = new Set(this.groupsIterable);
-		const chunksProcessed = new Set();
-
-		for (const chunkGroup of queue) {
-			for (const chunk of chunkGroup.chunks) {
-				if (!chunksProcessed.has(chunk)) {
-					chunksProcessed.add(chunk);
-					if (!filterChunkFn || filterChunkFn(chunk)) {
-						for (const module of chunk.modulesIterable) {
-							if (filterFn(module)) {
-								return true;
-							}
-						}
-					}
+	hasChildByOrder(chunkGraph, type, includeDirectChildren, filterFn) {
+		if (includeDirectChildren) {
+			/** @type {Chunks} */
+			const chunks = new Set();
+			for (const chunkGroup of this.groupsIterable) {
+				for (const chunk of chunkGroup.chunks) {
+					chunks.add(chunk);
 				}
 			}
-			for (const child of chunkGroup.childrenIterable) {
-				queue.add(child);
+			for (const chunk of chunks) {
+				const data = chunk.getChildIdsByOrders(chunkGraph, filterFn);
+				if (data[type] !== undefined) return true;
 			}
 		}
-		return false;
-	}
 
-	toString() {
-		return `Chunk[${Array.from(this._modules).join()}]`;
-	}
-}
+		for (const chunk of this.getAllAsyncChunks()) {
+			const data = chunk.getChildIdsByOrders(chunkGraph, filterFn);
+			if (data[type] !== undefined) return true;
+		}
 
-// TODO remove in webpack 5
-Object.defineProperty(Chunk.prototype, "forEachModule", {
-	configurable: false,
-	value: util.deprecate(function(fn) {
-		this._modules.forEach(fn);
-	}, "Chunk.forEachModule: Use for(const module of chunk.modulesIterable) instead")
-});
-
-// TODO remove in webpack 5
-Object.defineProperty(Chunk.prototype, "mapModules", {
-	configurable: false,
-	value: util.deprecate(function(fn) {
-		return Array.from(this._modules, fn);
-	}, "Chunk.mapModules: Use Array.from(chunk.modulesIterable, fn) instead")
-});
-
-// TODO remove in webpack 5
-Object.defineProperty(Chunk.prototype, "chunks", {
-	configurable: false,
-	get() {
-		throw new Error("Chunk.chunks: Use ChunkGroup.getChildren() instead");
-	},
-	set() {
-		throw new Error("Chunk.chunks: Use ChunkGroup.add/removeChild() instead");
-	}
-});
-
-// TODO remove in webpack 5
-Object.defineProperty(Chunk.prototype, "parents", {
-	configurable: false,
-	get() {
-		throw new Error("Chunk.parents: Use ChunkGroup.getParents() instead");
-	},
-	set() {
-		throw new Error("Chunk.parents: Use ChunkGroup.add/removeParent() instead");
-	}
-});
-
-// TODO remove in webpack 5
-Object.defineProperty(Chunk.prototype, "blocks", {
-	configurable: false,
-	get() {
-		throw new Error("Chunk.blocks: Use ChunkGroup.getBlocks() instead");
-	},
-	set() {
-		throw new Error("Chunk.blocks: Use ChunkGroup.add/removeBlock() instead");
-	}
-});
-
-// TODO remove in webpack 5
-Object.defineProperty(Chunk.prototype, "entrypoints", {
-	configurable: false,
-	get() {
-		throw new Error(
-			"Chunk.entrypoints: Use Chunks.groupsIterable and filter by instanceof Entrypoint instead"
-		);
-	},
-	set() {
-		throw new Error("Chunk.entrypoints: Use Chunks.addGroup instead");
+		return false;
 	}
-});
+}
 
 module.exports = Chunk;
diff --git a/lib/ChunkGraph.js b/lib/ChunkGraph.js
new file mode 100644
index 00000000000..826f7ffebeb
--- /dev/null
+++ b/lib/ChunkGraph.js
@@ -0,0 +1,2111 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const util = require("util");
+const Entrypoint = require("./Entrypoint");
+const ModuleGraphConnection = require("./ModuleGraphConnection");
+const { DEFAULTS } = require("./config/defaults");
+const { first } = require("./util/SetHelpers");
+const SortableSet = require("./util/SortableSet");
+const {
+	compareIds,
+	compareIterables,
+	compareModulesById,
+	compareModulesByIdentifier,
+	compareSelect,
+	concatComparators
+} = require("./util/comparators");
+const createHash = require("./util/createHash");
+const findGraphRoots = require("./util/findGraphRoots");
+const {
+	RuntimeSpecMap,
+	RuntimeSpecSet,
+	forEachRuntime,
+	mergeRuntime,
+	runtimeToString
+} = require("./util/runtime");
+
+/** @typedef {import("./AsyncDependenciesBlock")} AsyncDependenciesBlock */
+/** @typedef {import("./Chunk")} Chunk */
+/** @typedef {import("./Chunk").Chunks} Chunks */
+/** @typedef {import("./Chunk").Entrypoints} Entrypoints */
+/** @typedef {import("./Chunk").ChunkId} ChunkId */
+/** @typedef {import("./ChunkGroup")} ChunkGroup */
+/** @typedef {import("./Module")} Module */
+/** @typedef {import("./Module").SourceType} SourceType */
+/** @typedef {import("./Module").SourceTypes} SourceTypes */
+/** @typedef {import("./Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */
+/** @typedef {import("./Module").RuntimeRequirements} RuntimeRequirements */
+/** @typedef {import("./ModuleGraph")} ModuleGraph */
+/** @typedef {import("./ModuleGraphConnection").ConnectionState} ConnectionState */
+/** @typedef {import("./RuntimeModule")} RuntimeModule */
+/** @typedef {import("./util/Hash").HashFunction} HashFunction */
+/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */
+/** @typedef {import("./javascript/JavascriptModule").JavascriptModuleBuildMeta} JavascriptModuleBuildMeta */
+
+/** @type {ReadonlySet} */
+const EMPTY_SET = new Set();
+
+const ZERO_BIG_INT = BigInt(0);
+
+/**
+ * Active state to string. Module-level (depends only on ModuleGraphConnection)
+ * so it isn't re-allocated on every getModuleGraphHash call, incl. cache hits.
+ * @param {ConnectionState} state state
+ * @returns {"F" | "T" | "O"} result
+ */
+const activeStateToString = (state) => {
+	if (state === false) return "F";
+	if (state === true) return "T";
+	if (state === ModuleGraphConnection.TRANSITIVE_ONLY) return "O";
+	throw new Error("Not implemented active state");
+};
+
+const compareModuleIterables = compareIterables(compareModulesByIdentifier);
+
+/** @typedef {(c: Chunk, chunkGraph: ChunkGraph) => boolean} ChunkFilterPredicate */
+/** @typedef {(m: Module) => boolean} ModuleFilterPredicate */
+/** @typedef {[Module, Entrypoint | undefined]} EntryModuleWithChunkGroup */
+
+/**
+ * Represents the module hash info runtime component.
+ * @typedef {object} ChunkSizeOptions
+ * @property {number=} chunkOverhead constant overhead for a chunk
+ * @property {number=} entryChunkMultiplicator multiplicator for initial chunks
+ */
+
+class ModuleHashInfo {
+	/**
+	 * Creates an instance of ModuleHashInfo.
+	 * @param {string} hash hash
+	 * @param {string} renderedHash rendered hash
+	 */
+	constructor(hash, renderedHash) {
+		/** @type {string} */
+		this.hash = hash;
+		/** @type {string} */
+		this.renderedHash = renderedHash;
+	}
+}
+
+/**
+ * Returns set as array.
+ * @template T
+ * @param {SortableSet} set the set
+ * @returns {T[]} set as array
+ */
+const getArray = (set) => [...set];
+
+/**
+ * Gets module runtimes.
+ * @param {SortableChunks} chunks the chunks
+ * @returns {RuntimeSpecSet} runtimes
+ */
+const getModuleRuntimes = (chunks) => {
+	const runtimes = new RuntimeSpecSet();
+	for (const chunk of chunks) {
+		runtimes.add(chunk.runtime);
+	}
+	return runtimes;
+};
+
+/**
+ * Modules by source type.
+ * @param {SourceTypesByModule | undefined} sourceTypesByModule sourceTypesByModule
+ * @returns {ModulesBySourceType} modules by source type
+ */
+const modulesBySourceType = (sourceTypesByModule) => (set) => {
+	/** @typedef {SortableSet} ModuleSortableSet */
+	/** @type {Map} */
+	const map = new Map();
+	for (const module of set) {
+		const sourceTypes =
+			(sourceTypesByModule && sourceTypesByModule.get(module)) ||
+			module.getSourceTypes();
+		for (const sourceType of sourceTypes) {
+			let innerSet = map.get(sourceType);
+			if (innerSet === undefined) {
+				/** @type {ModuleSortableSet} */
+				innerSet = new SortableSet();
+				map.set(sourceType, innerSet);
+			}
+			innerSet.add(module);
+		}
+	}
+	for (const [key, innerSet] of map) {
+		// When all modules have the source type, we reuse the original SortableSet
+		// to benefit from the shared cache (especially for sorting)
+		if (innerSet.size === set.size) {
+			map.set(key, set);
+		}
+	}
+	return map;
+};
+
+/** @typedef {(set: SortableSet) => Map>} ModulesBySourceType */
+
+/** @type {ModulesBySourceType} */
+const defaultModulesBySourceType = modulesBySourceType(undefined);
+
+/**
+ * Defines the module set to array function type used by this module.
+ * @typedef {(set: SortableSet) => Module[]} ModuleSetToArrayFunction
+ */
+
+/**
+ * @template T
+ * @type {WeakMap}
+ */
+const createOrderedArrayFunctionMap = new WeakMap();
+
+/**
+ * Creates an ordered array function.
+ * @template T
+ * @param {ModuleComparator} comparator comparator function
+ * @returns {ModuleSetToArrayFunction} set as ordered array
+ */
+const createOrderedArrayFunction = (comparator) => {
+	let fn = createOrderedArrayFunctionMap.get(comparator);
+	if (fn !== undefined) return fn;
+	fn = (set) => {
+		set.sortWith(comparator);
+		return [...set];
+	};
+	createOrderedArrayFunctionMap.set(comparator, fn);
+	return fn;
+};
+
+/**
+ * Returns the size of the modules.
+ * @param {Iterable} modules the modules to get the count/size of
+ * @returns {number} the size of the modules
+ */
+const getModulesSize = (modules) => {
+	let size = 0;
+	for (const module of modules) {
+		for (const type of module.getSourceTypes()) {
+			size += module.size(type);
+		}
+	}
+	return size;
+};
+
+/** @typedef {Record} SizesOfModules */
+
+/**
+ * Gets modules sizes.
+ * @param {Iterable} modules the sortable Set to get the size of
+ * @returns {SizesOfModules} the sizes of the modules
+ */
+const getModulesSizes = (modules) => {
+	/** @type {SizesOfModules} */
+	const sizes = Object.create(null);
+	for (const module of modules) {
+		for (const type of module.getSourceTypes()) {
+			sizes[type] = (sizes[type] || 0) + module.size(type);
+		}
+	}
+	return sizes;
+};
+
+/**
+ * Checks whether this module hash info is available chunk.
+ * @param {Chunk} a chunk
+ * @param {Chunk} b chunk
+ * @returns {boolean} true, if a is always a parent of b
+ */
+const isAvailableChunk = (a, b) => {
+	const queue = new Set(b.groupsIterable);
+	for (const chunkGroup of queue) {
+		if (a.isInGroup(chunkGroup)) continue;
+		if (chunkGroup.isInitial()) return false;
+		for (const parent of chunkGroup.parentsIterable) {
+			queue.add(parent);
+		}
+	}
+	return true;
+};
+
+/** @typedef {SortableSet} SortableChunks */
+/** @typedef {Set} EntryInChunks */
+/** @typedef {Set} RuntimeInChunks */
+/** @typedef {string | number} ModuleId */
+/** @typedef {RuntimeSpecMap, RuntimeRequirements>} ChunkGraphRuntimeRequirements */
+
+class ChunkGraphModule {
+	constructor() {
+		/** @type {SortableChunks} */
+		this.chunks = new SortableSet();
+		/** @type {EntryInChunks | undefined} */
+		this.entryInChunks = undefined;
+		/** @type {RuntimeInChunks | undefined} */
+		this.runtimeInChunks = undefined;
+		/** @type {RuntimeSpecMap | undefined} */
+		this.hashes = undefined;
+		/** @type {ModuleId | null} */
+		this.id = null;
+		/** @type {ChunkGraphRuntimeRequirements | undefined} */
+		this.runtimeRequirements = undefined;
+		/** @type {RuntimeSpecMap | undefined} */
+		this.graphHashes = undefined;
+		/** @type {RuntimeSpecMap | undefined} */
+		this.graphHashesWithConnections = undefined;
+	}
+}
+
+/** @typedef {WeakMap} SourceTypesByModule */
+/** @typedef {Map} EntryModules */
+
+class ChunkGraphChunk {
+	constructor() {
+		/** @type {SortableSet} */
+		this.modules = new SortableSet();
+		/** @type {SourceTypesByModule | undefined} */
+		this.sourceTypesByModule = undefined;
+		/** @type {EntryModules} */
+		this.entryModules = new Map();
+		/** @type {SortableSet} */
+		this.runtimeModules = new SortableSet();
+		/** @type {Set | undefined} */
+		this.fullHashModules = undefined;
+		/** @type {Set | undefined} */
+		this.dependentHashModules = undefined;
+		/** @type {RuntimeRequirements | undefined} */
+		this.runtimeRequirements = undefined;
+		/** @type {Set} */
+		this.runtimeRequirementsInTree = new Set();
+		/** @type {ModulesBySourceType} */
+		this._modulesBySourceType = defaultModulesBySourceType;
+	}
+}
+
+/** @typedef {string | number} RuntimeId */
+/** @typedef {Record} IdToHashMap */
+/** @typedef {Record} ChunkModuleHashMap */
+/** @typedef {Record} ChunkModuleIdMap */
+/** @typedef {Record} ChunkConditionMap */
+
+/** @typedef {(a: Module, b: Module) => -1 | 0 | 1} ModuleComparator */
+
+class ChunkGraph {
+	/**
+	 * Creates an instance of ChunkGraph.
+	 * @param {ModuleGraph} moduleGraph the module graph
+	 * @param {HashFunction} hashFunction the hash function to use
+	 */
+	constructor(moduleGraph, hashFunction = DEFAULTS.HASH_FUNCTION) {
+		/**
+		 * @private
+		 * @type {WeakMap}
+		 */
+		this._modules = new WeakMap();
+		/**
+		 * @private
+		 * @type {WeakMap}
+		 */
+		this._chunks = new WeakMap();
+		/**
+		 * @private
+		 * @type {WeakMap}
+		 */
+		this._blockChunkGroups = new WeakMap();
+		/**
+		 * @private
+		 * @type {Map}
+		 */
+		this._runtimeIds = new Map();
+		/**
+		 * Module runtime requirement Sets stored without copying (ownership not
+		 * transferred). They must be copied before being mutated.
+		 * @private
+		 * @type {WeakSet}
+		 */
+		this._sharedModuleRuntimeRequirements = new WeakSet();
+		/** @type {ModuleGraph} */
+		this.moduleGraph = moduleGraph;
+
+		// build-time execution (`Compilation.executeModule`) runs modules in a
+		// CommonJS-style vm wrapper, so templates must not emit ESM-only syntax
+		/** @type {boolean} */
+		this.buildTimeExecution = false;
+
+		/** @type {HashFunction} */
+		this._hashFunction = hashFunction;
+
+		this._getGraphRoots = this._getGraphRoots.bind(this);
+	}
+
+	/**
+	 * Get chunk graph module.
+	 * @private
+	 * @param {Module} module the module
+	 * @returns {ChunkGraphModule} internal module
+	 */
+	_getChunkGraphModule(module) {
+		let cgm = this._modules.get(module);
+		if (cgm === undefined) {
+			cgm = new ChunkGraphModule();
+			this._modules.set(module, cgm);
+		}
+		return cgm;
+	}
+
+	/**
+	 * Get chunk graph chunk.
+	 * @private
+	 * @param {Chunk} chunk the chunk
+	 * @returns {ChunkGraphChunk} internal chunk
+	 */
+	_getChunkGraphChunk(chunk) {
+		let cgc = this._chunks.get(chunk);
+		if (cgc === undefined) {
+			cgc = new ChunkGraphChunk();
+			this._chunks.set(chunk, cgc);
+		}
+		return cgc;
+	}
+
+	/**
+	 * Returns the graph roots.
+	 * @param {SortableSet} set the sortable Set to get the roots of
+	 * @returns {Module[]} the graph roots
+	 */
+	_getGraphRoots(set) {
+		const { moduleGraph } = this;
+		return [
+			...findGraphRoots(set, (module) => {
+				/** @type {Set} */
+				const set = new Set();
+				/**
+				 * Adds the provided module to the chunk graph.
+				 * @param {Module} module module
+				 */
+				const addDependencies = (module) => {
+					for (const connection of moduleGraph.getOutgoingConnections(module)) {
+						if (!connection.module) continue;
+						const activeState = connection.getActiveState(undefined);
+						if (activeState === false) continue;
+						if (activeState === ModuleGraphConnection.TRANSITIVE_ONLY) {
+							addDependencies(connection.module);
+							continue;
+						}
+						set.add(connection.module);
+					}
+				};
+				addDependencies(module);
+				return set;
+			})
+		].sort(compareModulesByIdentifier);
+	}
+
+	/**
+	 * Connects chunk and module.
+	 * @param {Chunk} chunk the new chunk
+	 * @param {Module} module the module
+	 * @returns {void}
+	 */
+	connectChunkAndModule(chunk, module) {
+		const cgm = this._getChunkGraphModule(module);
+		const cgc = this._getChunkGraphChunk(chunk);
+		cgm.chunks.add(chunk);
+		cgc.modules.add(module);
+	}
+
+	/**
+	 * Disconnects chunk and module.
+	 * @param {Chunk} chunk the chunk
+	 * @param {Module} module the module
+	 * @returns {void}
+	 */
+	disconnectChunkAndModule(chunk, module) {
+		const cgm = this._getChunkGraphModule(module);
+		const cgc = this._getChunkGraphChunk(chunk);
+		cgc.modules.delete(module);
+		// No need to invalidate cgc._modulesBySourceType because we modified cgc.modules anyway
+		if (cgc.sourceTypesByModule) cgc.sourceTypesByModule.delete(module);
+		cgm.chunks.delete(chunk);
+	}
+
+	/**
+	 * Processes the provided chunk.
+	 * @param {Chunk} chunk the chunk which will be disconnected
+	 * @returns {void}
+	 */
+	disconnectChunk(chunk) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		for (const module of cgc.modules) {
+			const cgm = this._getChunkGraphModule(module);
+			cgm.chunks.delete(chunk);
+		}
+		cgc.modules.clear();
+		chunk.disconnectFromGroups();
+		ChunkGraph.clearChunkGraphForChunk(chunk);
+	}
+
+	/**
+	 * Processes the provided chunk.
+	 * @param {Chunk} chunk the chunk
+	 * @param {Iterable} modules the modules
+	 * @returns {void}
+	 */
+	attachModules(chunk, modules) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		for (const module of modules) {
+			cgc.modules.add(module);
+		}
+	}
+
+	/**
+	 * Attach runtime modules.
+	 * @param {Chunk} chunk the chunk
+	 * @param {Iterable} modules the runtime modules
+	 * @returns {void}
+	 */
+	attachRuntimeModules(chunk, modules) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		for (const module of modules) {
+			cgc.runtimeModules.add(module);
+		}
+	}
+
+	/**
+	 * Attach full hash modules.
+	 * @param {Chunk} chunk the chunk
+	 * @param {Iterable} modules the modules that require a full hash
+	 * @returns {void}
+	 */
+	attachFullHashModules(chunk, modules) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		if (cgc.fullHashModules === undefined) cgc.fullHashModules = new Set();
+		for (const module of modules) {
+			cgc.fullHashModules.add(module);
+		}
+	}
+
+	/**
+	 * Attach dependent hash modules.
+	 * @param {Chunk} chunk the chunk
+	 * @param {Iterable} modules the modules that require a full hash
+	 * @returns {void}
+	 */
+	attachDependentHashModules(chunk, modules) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		if (cgc.dependentHashModules === undefined) {
+			cgc.dependentHashModules = new Set();
+		}
+		for (const module of modules) {
+			cgc.dependentHashModules.add(module);
+		}
+	}
+
+	/**
+	 * Processes the provided old module.
+	 * @param {Module} oldModule the replaced module
+	 * @param {Module} newModule the replacing module
+	 * @returns {void}
+	 */
+	replaceModule(oldModule, newModule) {
+		const oldCgm = this._getChunkGraphModule(oldModule);
+		const newCgm = this._getChunkGraphModule(newModule);
+
+		for (const chunk of oldCgm.chunks) {
+			const cgc = this._getChunkGraphChunk(chunk);
+			cgc.modules.delete(oldModule);
+			cgc.modules.add(newModule);
+			newCgm.chunks.add(chunk);
+		}
+		oldCgm.chunks.clear();
+
+		if (oldCgm.entryInChunks !== undefined) {
+			if (newCgm.entryInChunks === undefined) {
+				newCgm.entryInChunks = new Set();
+			}
+			for (const chunk of oldCgm.entryInChunks) {
+				const cgc = this._getChunkGraphChunk(chunk);
+				const old = /** @type {Entrypoint} */ (cgc.entryModules.get(oldModule));
+				/** @type {EntryModules} */
+				const newEntryModules = new Map();
+				for (const [m, cg] of cgc.entryModules) {
+					if (m === oldModule) {
+						newEntryModules.set(newModule, old);
+					} else {
+						newEntryModules.set(m, cg);
+					}
+				}
+				cgc.entryModules = newEntryModules;
+				newCgm.entryInChunks.add(chunk);
+			}
+			oldCgm.entryInChunks = undefined;
+		}
+
+		if (oldCgm.runtimeInChunks !== undefined) {
+			if (newCgm.runtimeInChunks === undefined) {
+				newCgm.runtimeInChunks = new Set();
+			}
+			for (const chunk of oldCgm.runtimeInChunks) {
+				const cgc = this._getChunkGraphChunk(chunk);
+				cgc.runtimeModules.delete(/** @type {RuntimeModule} */ (oldModule));
+				cgc.runtimeModules.add(/** @type {RuntimeModule} */ (newModule));
+				newCgm.runtimeInChunks.add(chunk);
+				if (
+					cgc.fullHashModules !== undefined &&
+					cgc.fullHashModules.has(/** @type {RuntimeModule} */ (oldModule))
+				) {
+					cgc.fullHashModules.delete(/** @type {RuntimeModule} */ (oldModule));
+					cgc.fullHashModules.add(/** @type {RuntimeModule} */ (newModule));
+				}
+				if (
+					cgc.dependentHashModules !== undefined &&
+					cgc.dependentHashModules.has(/** @type {RuntimeModule} */ (oldModule))
+				) {
+					cgc.dependentHashModules.delete(
+						/** @type {RuntimeModule} */ (oldModule)
+					);
+					cgc.dependentHashModules.add(
+						/** @type {RuntimeModule} */ (newModule)
+					);
+				}
+			}
+			oldCgm.runtimeInChunks = undefined;
+		}
+	}
+
+	/**
+	 * Checks whether this chunk graph is module in chunk.
+	 * @param {Module} module the checked module
+	 * @param {Chunk} chunk the checked chunk
+	 * @returns {boolean} true, if the chunk contains the module
+	 */
+	isModuleInChunk(module, chunk) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		return cgc.modules.has(module);
+	}
+
+	/**
+	 * Checks whether this chunk graph is module in chunk group.
+	 * @param {Module} module the checked module
+	 * @param {ChunkGroup} chunkGroup the checked chunk group
+	 * @returns {boolean} true, if the chunk contains the module
+	 */
+	isModuleInChunkGroup(module, chunkGroup) {
+		for (const chunk of chunkGroup.chunks) {
+			if (this.isModuleInChunk(module, chunk)) return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Checks whether this chunk graph is entry module.
+	 * @param {Module} module the checked module
+	 * @returns {boolean} true, if the module is entry of any chunk
+	 */
+	isEntryModule(module) {
+		const cgm = this._getChunkGraphModule(module);
+		return cgm.entryInChunks !== undefined;
+	}
+
+	/**
+	 * Gets module chunks iterable.
+	 * @param {Module} module the module
+	 * @returns {Iterable} iterable of chunks (do not modify)
+	 */
+	getModuleChunksIterable(module) {
+		const cgm = this._getChunkGraphModule(module);
+		return cgm.chunks;
+	}
+
+	/**
+	 * Gets ordered module chunks iterable.
+	 * @param {Module} module the module
+	 * @param {(a: Chunk, b: Chunk) => -1 | 0 | 1} sortFn sort function
+	 * @returns {Iterable} iterable of chunks (do not modify)
+	 */
+	getOrderedModuleChunksIterable(module, sortFn) {
+		const cgm = this._getChunkGraphModule(module);
+		cgm.chunks.sortWith(sortFn);
+		return cgm.chunks;
+	}
+
+	/**
+	 * Gets module chunks.
+	 * @param {Module} module the module
+	 * @returns {Chunk[]} array of chunks (cached, do not modify)
+	 */
+	getModuleChunks(module) {
+		const cgm = this._getChunkGraphModule(module);
+		return cgm.chunks.getFromCache(getArray);
+	}
+
+	/**
+	 * Gets number of module chunks.
+	 * @param {Module} module the module
+	 * @returns {number} the number of chunk which contain the module
+	 */
+	getNumberOfModuleChunks(module) {
+		const cgm = this._getChunkGraphModule(module);
+		return cgm.chunks.size;
+	}
+
+	/**
+	 * Gets module runtimes.
+	 * @param {Module} module the module
+	 * @returns {RuntimeSpecSet} runtimes
+	 */
+	getModuleRuntimes(module) {
+		const cgm = this._getChunkGraphModule(module);
+		return cgm.chunks.getFromUnorderedCache(getModuleRuntimes);
+	}
+
+	/**
+	 * Gets number of chunk modules.
+	 * @param {Chunk} chunk the chunk
+	 * @returns {number} the number of modules which are contained in this chunk
+	 */
+	getNumberOfChunkModules(chunk) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		return cgc.modules.size;
+	}
+
+	/**
+	 * Gets number of chunk full hash modules.
+	 * @param {Chunk} chunk the chunk
+	 * @returns {number} the number of full hash modules which are contained in this chunk
+	 */
+	getNumberOfChunkFullHashModules(chunk) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		return cgc.fullHashModules === undefined ? 0 : cgc.fullHashModules.size;
+	}
+
+	/**
+	 * Gets chunk modules iterable.
+	 * @param {Chunk} chunk the chunk
+	 * @returns {Iterable} return the modules for this chunk
+	 */
+	getChunkModulesIterable(chunk) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		return cgc.modules;
+	}
+
+	/**
+	 * Gets chunk modules iterable by source type.
+	 * @param {Chunk} chunk the chunk
+	 * @param {string} sourceType source type
+	 * @returns {Iterable | undefined} return the modules for this chunk
+	 */
+	getChunkModulesIterableBySourceType(chunk, sourceType) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		const modulesWithSourceType = cgc.modules
+			.getFromUnorderedCache(cgc._modulesBySourceType)
+			.get(sourceType);
+		return modulesWithSourceType;
+	}
+
+	/**
+	 * Sets chunk module source types.
+	 * @param {Chunk} chunk chunk
+	 * @param {Module} module chunk module
+	 * @param {SourceTypes} sourceTypes source types
+	 */
+	setChunkModuleSourceTypes(chunk, module, sourceTypes) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		if (cgc.sourceTypesByModule === undefined) {
+			cgc.sourceTypesByModule = new WeakMap();
+		}
+		cgc.sourceTypesByModule.set(module, sourceTypes);
+		// Update cgc._modulesBySourceType to invalidate the cache
+		cgc._modulesBySourceType = modulesBySourceType(cgc.sourceTypesByModule);
+	}
+
+	/**
+	 * Gets chunk module source types.
+	 * @param {Chunk} chunk chunk
+	 * @param {Module} module chunk module
+	 * @returns {SourceTypes} source types
+	 */
+	getChunkModuleSourceTypes(chunk, module) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		if (cgc.sourceTypesByModule === undefined) {
+			return module.getSourceTypes();
+		}
+		return cgc.sourceTypesByModule.get(module) || module.getSourceTypes();
+	}
+
+	/**
+	 * Gets module source types.
+	 * @param {Module} module module
+	 * @returns {SourceTypes} source types
+	 */
+	getModuleSourceTypes(module) {
+		return (
+			this._getOverwrittenModuleSourceTypes(module) || module.getSourceTypes()
+		);
+	}
+
+	/**
+	 * Get overwritten module source types.
+	 * @param {Module} module module
+	 * @returns {SourceTypes | undefined} source types
+	 */
+	_getOverwrittenModuleSourceTypes(module) {
+		let newSet = false;
+		/** @type {Set | undefined} */
+		let sourceTypes;
+		for (const chunk of this.getModuleChunksIterable(module)) {
+			const cgc = this._getChunkGraphChunk(chunk);
+			if (cgc.sourceTypesByModule === undefined) return;
+			const st = cgc.sourceTypesByModule.get(module);
+			if (st === undefined) return;
+			if (!sourceTypes) {
+				sourceTypes = /** @type {Set} */ (st);
+			} else if (!newSet) {
+				for (const type of st) {
+					if (!newSet) {
+						if (!sourceTypes.has(type)) {
+							newSet = true;
+							sourceTypes = new Set(sourceTypes);
+							sourceTypes.add(type);
+						}
+					} else {
+						sourceTypes.add(type);
+					}
+				}
+			} else {
+				for (const type of st) sourceTypes.add(type);
+			}
+		}
+
+		return sourceTypes;
+	}
+
+	/**
+	 * Gets ordered chunk modules iterable.
+	 * @param {Chunk} chunk the chunk
+	 * @param {ModuleComparator} comparator comparator function
+	 * @returns {Iterable} return the modules for this chunk
+	 */
+	getOrderedChunkModulesIterable(chunk, comparator) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		cgc.modules.sortWith(comparator);
+		return cgc.modules;
+	}
+
+	/**
+	 * Gets ordered chunk modules iterable by source type.
+	 * @param {Chunk} chunk the chunk
+	 * @param {string} sourceType source type
+	 * @param {ModuleComparator} comparator comparator function
+	 * @returns {Iterable | undefined} return the modules for this chunk
+	 */
+	getOrderedChunkModulesIterableBySourceType(chunk, sourceType, comparator) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		const modulesWithSourceType = cgc.modules
+			.getFromUnorderedCache(cgc._modulesBySourceType)
+			.get(sourceType);
+		if (modulesWithSourceType === undefined) return;
+		modulesWithSourceType.sortWith(comparator);
+		return modulesWithSourceType;
+	}
+
+	/**
+	 * Gets chunk modules.
+	 * @param {Chunk} chunk the chunk
+	 * @returns {Module[]} return the modules for this chunk (cached, do not modify)
+	 */
+	getChunkModules(chunk) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		return cgc.modules.getFromUnorderedCache(getArray);
+	}
+
+	/**
+	 * Gets ordered chunk modules.
+	 * @param {Chunk} chunk the chunk
+	 * @param {ModuleComparator} comparator comparator function
+	 * @returns {Module[]} return the modules for this chunk (cached, do not modify)
+	 */
+	getOrderedChunkModules(chunk, comparator) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		const arrayFunction = createOrderedArrayFunction(comparator);
+		return cgc.modules.getFromUnorderedCache(arrayFunction);
+	}
+
+	/**
+	 * Gets chunk module id map.
+	 * @param {Chunk} chunk the chunk
+	 * @param {ModuleFilterPredicate} filterFn function used to filter modules
+	 * @param {boolean} includeAllChunks all chunks or only async chunks
+	 * @returns {ChunkModuleIdMap} chunk to module ids object
+	 */
+	getChunkModuleIdMap(chunk, filterFn, includeAllChunks = false) {
+		/** @type {ChunkModuleIdMap} */
+		const chunkModuleIdMap = Object.create(null);
+
+		for (const asyncChunk of includeAllChunks
+			? chunk.getAllReferencedChunks()
+			: chunk.getAllAsyncChunks()) {
+			/** @type {ModuleId[] | undefined} */
+			let array;
+			for (const module of this.getOrderedChunkModulesIterable(
+				asyncChunk,
+				compareModulesById(this)
+			)) {
+				if (filterFn(module)) {
+					if (array === undefined) {
+						array = [];
+						chunkModuleIdMap[/** @type {ChunkId} */ (asyncChunk.id)] = array;
+					}
+					const moduleId = /** @type {ModuleId} */ (this.getModuleId(module));
+					array.push(moduleId);
+				}
+			}
+		}
+
+		return chunkModuleIdMap;
+	}
+
+	/**
+	 * Gets chunk module rendered hash map.
+	 * @param {Chunk} chunk the chunk
+	 * @param {ModuleFilterPredicate} filterFn function used to filter modules
+	 * @param {number} hashLength length of the hash
+	 * @param {boolean} includeAllChunks all chunks or only async chunks
+	 * @returns {ChunkModuleHashMap} chunk to module id to module hash object
+	 */
+	getChunkModuleRenderedHashMap(
+		chunk,
+		filterFn,
+		hashLength = 0,
+		includeAllChunks = false
+	) {
+		/** @type {ChunkModuleHashMap} */
+		const chunkModuleHashMap = Object.create(null);
+
+		for (const asyncChunk of includeAllChunks
+			? chunk.getAllReferencedChunks()
+			: chunk.getAllAsyncChunks()) {
+			/** @type {IdToHashMap | undefined} */
+			let idToHashMap;
+			for (const module of this.getOrderedChunkModulesIterable(
+				asyncChunk,
+				compareModulesById(this)
+			)) {
+				if (filterFn(module)) {
+					if (idToHashMap === undefined) {
+						/** @type {IdToHashMap} */
+						idToHashMap = Object.create(null);
+						chunkModuleHashMap[/** @type {ChunkId} */ (asyncChunk.id)] =
+							/** @type {IdToHashMap} */
+							(idToHashMap);
+					}
+					const moduleId = this.getModuleId(module);
+					const hash = this.getRenderedModuleHash(module, asyncChunk.runtime);
+					/** @type {IdToHashMap} */
+					(idToHashMap)[/** @type {ModuleId} */ (moduleId)] = hashLength
+						? hash.slice(0, hashLength)
+						: hash;
+				}
+			}
+		}
+
+		return chunkModuleHashMap;
+	}
+
+	/**
+	 * Gets chunk condition map.
+	 * @param {Chunk} chunk the chunk
+	 * @param {ChunkFilterPredicate} filterFn function used to filter chunks
+	 * @returns {ChunkConditionMap} chunk condition map
+	 */
+	getChunkConditionMap(chunk, filterFn) {
+		/** @type {ChunkConditionMap} */
+		const map = Object.create(null);
+		for (const c of chunk.getAllReferencedChunks()) {
+			map[/** @type {ChunkId} */ (c.id)] = filterFn(c, this);
+		}
+		return map;
+	}
+
+	/**
+	 * Checks whether this chunk graph contains the chunk.
+	 * @param {Chunk} chunk the chunk
+	 * @param {ModuleFilterPredicate} filterFn predicate function used to filter modules
+	 * @param {ChunkFilterPredicate=} filterChunkFn predicate function used to filter chunks
+	 * @returns {boolean} return true if module exists in graph
+	 */
+	hasModuleInGraph(chunk, filterFn, filterChunkFn) {
+		const queue = new Set(chunk.groupsIterable);
+		/** @type {Set} */
+		const chunksProcessed = new Set();
+
+		for (const chunkGroup of queue) {
+			for (const innerChunk of chunkGroup.chunks) {
+				if (!chunksProcessed.has(innerChunk)) {
+					chunksProcessed.add(innerChunk);
+					if (!filterChunkFn || filterChunkFn(innerChunk, this)) {
+						for (const module of this.getChunkModulesIterable(innerChunk)) {
+							if (filterFn(module)) {
+								return true;
+							}
+						}
+					}
+				}
+			}
+			for (const child of chunkGroup.childrenIterable) {
+				queue.add(child);
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Compares the provided values and returns their ordering.
+	 * @param {Chunk} chunkA first chunk
+	 * @param {Chunk} chunkB second chunk
+	 * @returns {-1 | 0 | 1} this is a comparator function like sort and returns -1, 0, or 1 based on sort order
+	 */
+	compareChunks(chunkA, chunkB) {
+		const cgcA = this._getChunkGraphChunk(chunkA);
+		const cgcB = this._getChunkGraphChunk(chunkB);
+		if (cgcA.modules.size > cgcB.modules.size) return -1;
+		if (cgcA.modules.size < cgcB.modules.size) return 1;
+		cgcA.modules.sortWith(compareModulesByIdentifier);
+		cgcB.modules.sortWith(compareModulesByIdentifier);
+		return compareModuleIterables(cgcA.modules, cgcB.modules);
+	}
+
+	/**
+	 * Gets chunk modules size.
+	 * @param {Chunk} chunk the chunk
+	 * @returns {number} total size of all modules in the chunk
+	 */
+	getChunkModulesSize(chunk) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		return cgc.modules.getFromUnorderedCache(getModulesSize);
+	}
+
+	/**
+	 * Gets chunk modules sizes.
+	 * @param {Chunk} chunk the chunk
+	 * @returns {Record} total sizes of all modules in the chunk by source type
+	 */
+	getChunkModulesSizes(chunk) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		return cgc.modules.getFromUnorderedCache(getModulesSizes);
+	}
+
+	/**
+	 * Gets chunk root modules.
+	 * @param {Chunk} chunk the chunk
+	 * @returns {Module[]} root modules of the chunks (ordered by identifier)
+	 */
+	getChunkRootModules(chunk) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		return cgc.modules.getFromUnorderedCache(this._getGraphRoots);
+	}
+
+	/**
+	 * Returns total size of the chunk.
+	 * @param {Chunk} chunk the chunk
+	 * @param {ChunkSizeOptions} options options object
+	 * @returns {number} total size of the chunk
+	 */
+	getChunkSize(chunk, options = {}) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		const modulesSize = cgc.modules.getFromUnorderedCache(getModulesSize);
+		const chunkOverhead =
+			typeof options.chunkOverhead === "number" ? options.chunkOverhead : 10000;
+		const entryChunkMultiplicator =
+			typeof options.entryChunkMultiplicator === "number"
+				? options.entryChunkMultiplicator
+				: 10;
+		return (
+			chunkOverhead +
+			modulesSize * (chunk.canBeInitial() ? entryChunkMultiplicator : 1)
+		);
+	}
+
+	/**
+	 * Gets integrated chunks size.
+	 * @param {Chunk} chunkA chunk
+	 * @param {Chunk} chunkB chunk
+	 * @param {ChunkSizeOptions} options options object
+	 * @returns {number} total size of the chunk or false if chunks can't be integrated
+	 */
+	getIntegratedChunksSize(chunkA, chunkB, options = {}) {
+		const cgcA = this._getChunkGraphChunk(chunkA);
+		const cgcB = this._getChunkGraphChunk(chunkB);
+		const allModules = new Set(cgcA.modules);
+		for (const m of cgcB.modules) allModules.add(m);
+		const modulesSize = getModulesSize(allModules);
+		const chunkOverhead =
+			typeof options.chunkOverhead === "number" ? options.chunkOverhead : 10000;
+		const entryChunkMultiplicator =
+			typeof options.entryChunkMultiplicator === "number"
+				? options.entryChunkMultiplicator
+				: 10;
+		return (
+			chunkOverhead +
+			modulesSize *
+				(chunkA.canBeInitial() || chunkB.canBeInitial()
+					? entryChunkMultiplicator
+					: 1)
+		);
+	}
+
+	/**
+	 * Checks whether it can chunks be integrated.
+	 * @param {Chunk} chunkA chunk
+	 * @param {Chunk} chunkB chunk
+	 * @returns {boolean} true, if chunks could be integrated
+	 */
+	canChunksBeIntegrated(chunkA, chunkB) {
+		if (chunkA.preventIntegration || chunkB.preventIntegration) {
+			return false;
+		}
+
+		const hasRuntimeA = chunkA.hasRuntime();
+		const hasRuntimeB = chunkB.hasRuntime();
+
+		if (hasRuntimeA !== hasRuntimeB) {
+			if (hasRuntimeA) {
+				return isAvailableChunk(chunkA, chunkB);
+			} else if (hasRuntimeB) {
+				return isAvailableChunk(chunkB, chunkA);
+			}
+
+			return false;
+		}
+
+		if (
+			this.getNumberOfEntryModules(chunkA) > 0 ||
+			this.getNumberOfEntryModules(chunkB) > 0
+		) {
+			return false;
+		}
+
+		return true;
+	}
+
+	/**
+	 * Processes the provided chunk a.
+	 * @param {Chunk} chunkA the target chunk
+	 * @param {Chunk} chunkB the chunk to integrate
+	 * @returns {void}
+	 */
+	integrateChunks(chunkA, chunkB) {
+		// Decide for one name (deterministic)
+		if (chunkA.name && chunkB.name) {
+			if (
+				this.getNumberOfEntryModules(chunkA) > 0 ===
+				this.getNumberOfEntryModules(chunkB) > 0
+			) {
+				// When both chunks have entry modules or none have one, use
+				// shortest name
+				if (chunkA.name.length !== chunkB.name.length) {
+					chunkA.name =
+						chunkA.name.length < chunkB.name.length ? chunkA.name : chunkB.name;
+				} else {
+					chunkA.name = chunkA.name < chunkB.name ? chunkA.name : chunkB.name;
+				}
+			} else if (this.getNumberOfEntryModules(chunkB) > 0) {
+				// Pick the name of the chunk with the entry module
+				chunkA.name = chunkB.name;
+			}
+		} else if (chunkB.name) {
+			chunkA.name = chunkB.name;
+		}
+
+		// Merge id name hints
+		for (const hint of chunkB.idNameHints) {
+			chunkA.idNameHints.add(hint);
+		}
+
+		// Merge runtime
+		chunkA.runtime = mergeRuntime(chunkA.runtime, chunkB.runtime);
+
+		// getChunkModules is used here to create a clone, because disconnectChunkAndModule modifies
+		for (const module of this.getChunkModules(chunkB)) {
+			this.disconnectChunkAndModule(chunkB, module);
+			this.connectChunkAndModule(chunkA, module);
+		}
+
+		for (const [
+			module,
+			chunkGroup
+		] of this.getChunkEntryModulesWithChunkGroupIterable(chunkB)) {
+			this.disconnectChunkAndEntryModule(chunkB, module);
+			this.connectChunkAndEntryModule(
+				chunkA,
+				module,
+				/** @type {Entrypoint} */
+				(chunkGroup)
+			);
+		}
+
+		for (const chunkGroup of chunkB.groupsIterable) {
+			chunkGroup.replaceChunk(chunkB, chunkA);
+			chunkA.addGroup(chunkGroup);
+			chunkB.removeGroup(chunkGroup);
+		}
+		ChunkGraph.clearChunkGraphForChunk(chunkB);
+	}
+
+	/**
+	 * Upgrade dependent to full hash modules.
+	 * @param {Chunk} chunk the chunk to upgrade
+	 * @returns {void}
+	 */
+	upgradeDependentToFullHashModules(chunk) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		if (cgc.dependentHashModules === undefined) return;
+		if (cgc.fullHashModules === undefined) {
+			cgc.fullHashModules = cgc.dependentHashModules;
+		} else {
+			for (const m of cgc.dependentHashModules) {
+				cgc.fullHashModules.add(m);
+			}
+			cgc.dependentHashModules = undefined;
+		}
+	}
+
+	/**
+	 * Checks whether this chunk graph is entry module in chunk.
+	 * @param {Module} module the checked module
+	 * @param {Chunk} chunk the checked chunk
+	 * @returns {boolean} true, if the chunk contains the module as entry
+	 */
+	isEntryModuleInChunk(module, chunk) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		return cgc.entryModules.has(module);
+	}
+
+	/**
+	 * Connects chunk and entry module.
+	 * @param {Chunk} chunk the new chunk
+	 * @param {Module} module the entry module
+	 * @param {Entrypoint} entrypoint the chunk group which must be loaded before the module is executed
+	 * @returns {void}
+	 */
+	connectChunkAndEntryModule(chunk, module, entrypoint) {
+		const cgm = this._getChunkGraphModule(module);
+		const cgc = this._getChunkGraphChunk(chunk);
+		if (cgm.entryInChunks === undefined) {
+			cgm.entryInChunks = new Set();
+		}
+		cgm.entryInChunks.add(chunk);
+		cgc.entryModules.set(module, entrypoint);
+	}
+
+	/**
+	 * Connects chunk and runtime module.
+	 * @param {Chunk} chunk the new chunk
+	 * @param {RuntimeModule} module the runtime module
+	 * @returns {void}
+	 */
+	connectChunkAndRuntimeModule(chunk, module) {
+		const cgm = this._getChunkGraphModule(module);
+		const cgc = this._getChunkGraphChunk(chunk);
+		if (cgm.runtimeInChunks === undefined) {
+			cgm.runtimeInChunks = new Set();
+		}
+		cgm.runtimeInChunks.add(chunk);
+		cgc.runtimeModules.add(module);
+	}
+
+	/**
+	 * Adds full hash module to chunk.
+	 * @param {Chunk} chunk the new chunk
+	 * @param {RuntimeModule} module the module that require a full hash
+	 * @returns {void}
+	 */
+	addFullHashModuleToChunk(chunk, module) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		if (cgc.fullHashModules === undefined) cgc.fullHashModules = new Set();
+		cgc.fullHashModules.add(module);
+	}
+
+	/**
+	 * Adds dependent hash module to chunk.
+	 * @param {Chunk} chunk the new chunk
+	 * @param {RuntimeModule} module the module that require a full hash
+	 * @returns {void}
+	 */
+	addDependentHashModuleToChunk(chunk, module) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		if (cgc.dependentHashModules === undefined) {
+			cgc.dependentHashModules = new Set();
+		}
+		cgc.dependentHashModules.add(module);
+	}
+
+	/**
+	 * Disconnects chunk and entry module.
+	 * @param {Chunk} chunk the new chunk
+	 * @param {Module} module the entry module
+	 * @returns {void}
+	 */
+	disconnectChunkAndEntryModule(chunk, module) {
+		const cgm = this._getChunkGraphModule(module);
+		const cgc = this._getChunkGraphChunk(chunk);
+		/** @type {EntryInChunks} */
+		(cgm.entryInChunks).delete(chunk);
+		if (/** @type {EntryInChunks} */ (cgm.entryInChunks).size === 0) {
+			cgm.entryInChunks = undefined;
+		}
+		cgc.entryModules.delete(module);
+	}
+
+	/**
+	 * Disconnects chunk and runtime module.
+	 * @param {Chunk} chunk the new chunk
+	 * @param {RuntimeModule} module the runtime module
+	 * @returns {void}
+	 */
+	disconnectChunkAndRuntimeModule(chunk, module) {
+		const cgm = this._getChunkGraphModule(module);
+		const cgc = this._getChunkGraphChunk(chunk);
+		/** @type {RuntimeInChunks} */
+		(cgm.runtimeInChunks).delete(chunk);
+		if (/** @type {RuntimeInChunks} */ (cgm.runtimeInChunks).size === 0) {
+			cgm.runtimeInChunks = undefined;
+		}
+		cgc.runtimeModules.delete(module);
+	}
+
+	/**
+	 * Disconnects entry module.
+	 * @param {Module} module the entry module, it will no longer be entry
+	 * @returns {void}
+	 */
+	disconnectEntryModule(module) {
+		const cgm = this._getChunkGraphModule(module);
+		for (const chunk of /** @type {EntryInChunks} */ (cgm.entryInChunks)) {
+			const cgc = this._getChunkGraphChunk(chunk);
+			cgc.entryModules.delete(module);
+		}
+		cgm.entryInChunks = undefined;
+	}
+
+	/**
+	 * Disconnects entries.
+	 * @param {Chunk} chunk the chunk, for which all entries will be removed
+	 * @returns {void}
+	 */
+	disconnectEntries(chunk) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		for (const module of cgc.entryModules.keys()) {
+			const cgm = this._getChunkGraphModule(module);
+			/** @type {EntryInChunks} */
+			(cgm.entryInChunks).delete(chunk);
+			if (/** @type {EntryInChunks} */ (cgm.entryInChunks).size === 0) {
+				cgm.entryInChunks = undefined;
+			}
+		}
+		cgc.entryModules.clear();
+	}
+
+	/**
+	 * Gets number of entry modules.
+	 * @param {Chunk} chunk the chunk
+	 * @returns {number} the amount of entry modules in chunk
+	 */
+	getNumberOfEntryModules(chunk) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		return cgc.entryModules.size;
+	}
+
+	/**
+	 * Gets number of runtime modules.
+	 * @param {Chunk} chunk the chunk
+	 * @returns {number} the amount of entry modules in chunk
+	 */
+	getNumberOfRuntimeModules(chunk) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		return cgc.runtimeModules.size;
+	}
+
+	/**
+	 * Gets chunk entry modules iterable.
+	 * @param {Chunk} chunk the chunk
+	 * @returns {Iterable} iterable of modules (do not modify)
+	 */
+	getChunkEntryModulesIterable(chunk) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		return cgc.entryModules.keys();
+	}
+
+	/**
+	 * Gets chunk entry dependent chunks iterable.
+	 * @param {Chunk} chunk the chunk
+	 * @returns {Iterable} iterable of chunks
+	 */
+	getChunkEntryDependentChunksIterable(chunk) {
+		/** @type {Chunks} */
+		const set = new Set();
+		for (const chunkGroup of chunk.groupsIterable) {
+			if (chunkGroup instanceof Entrypoint) {
+				const entrypointChunk = chunkGroup.getEntrypointChunk();
+				const cgc = this._getChunkGraphChunk(entrypointChunk);
+				for (const chunkGroup of cgc.entryModules.values()) {
+					for (const c of chunkGroup.chunks) {
+						if (c !== chunk && c !== entrypointChunk && !c.hasRuntime()) {
+							set.add(c);
+						}
+					}
+				}
+			}
+		}
+
+		return set;
+	}
+
+	/**
+	 * Gets runtime chunk dependent chunks iterable.
+	 * @param {Chunk} chunk the chunk
+	 * @returns {Iterable} iterable of chunks and include chunks from children entrypoints
+	 */
+	getRuntimeChunkDependentChunksIterable(chunk) {
+		/** @type {Chunks} */
+		const set = new Set();
+
+		/** @type {Entrypoints} */
+		const entrypoints = new Set();
+
+		for (const chunkGroup of chunk.groupsIterable) {
+			if (chunkGroup instanceof Entrypoint) {
+				const queue = [chunkGroup];
+				while (queue.length > 0) {
+					const current = queue.shift();
+					if (current) {
+						entrypoints.add(current);
+
+						let hasChildrenEntrypoint = false;
+						for (const child of current.childrenIterable) {
+							if (child instanceof Entrypoint && child.dependOn(current)) {
+								hasChildrenEntrypoint = true;
+								queue.push(/** @type {Entrypoint} */ (child));
+							}
+						}
+						// entryChunkB: hasChildrenEntrypoint = true
+						// entryChunkA: dependOn = entryChunkB
+						if (hasChildrenEntrypoint) {
+							const entrypointChunk = current.getEntrypointChunk();
+							if (entrypointChunk !== chunk && !entrypointChunk.hasRuntime()) {
+								// add entryChunkB to set
+								set.add(entrypointChunk);
+							}
+						}
+					}
+				}
+			}
+		}
+
+		for (const entrypoint of entrypoints) {
+			const entrypointChunk = entrypoint.getEntrypointChunk();
+			const cgc = this._getChunkGraphChunk(entrypointChunk);
+			for (const chunkGroup of cgc.entryModules.values()) {
+				for (const c of chunkGroup.chunks) {
+					if (c !== chunk && c !== entrypointChunk && !c.hasRuntime()) {
+						set.add(c);
+					}
+				}
+			}
+		}
+		return set;
+	}
+
+	/**
+	 * Checks whether this chunk graph contains the chunk.
+	 * @param {Chunk} chunk the chunk
+	 * @returns {boolean} true, when it has dependent chunks
+	 */
+	hasChunkEntryDependentChunks(chunk) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		for (const chunkGroup of cgc.entryModules.values()) {
+			for (const c of chunkGroup.chunks) {
+				if (c !== chunk) {
+					return true;
+				}
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Gets chunk runtime modules iterable.
+	 * @param {Chunk} chunk the chunk
+	 * @returns {Iterable} iterable of modules (do not modify)
+	 */
+	getChunkRuntimeModulesIterable(chunk) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		return cgc.runtimeModules;
+	}
+
+	/**
+	 * Gets chunk runtime modules in order.
+	 * @param {Chunk} chunk the chunk
+	 * @returns {RuntimeModule[]} array of modules in order of execution
+	 */
+	getChunkRuntimeModulesInOrder(chunk) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		const array = [...cgc.runtimeModules];
+		array.sort(
+			concatComparators(
+				compareSelect(
+					(r) => /** @type {RuntimeModule} */ (r).stage,
+					compareIds
+				),
+				compareModulesByIdentifier
+			)
+		);
+		return array;
+	}
+
+	/**
+	 * Gets chunk full hash modules iterable.
+	 * @param {Chunk} chunk the chunk
+	 * @returns {Iterable | undefined} iterable of modules (do not modify)
+	 */
+	getChunkFullHashModulesIterable(chunk) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		return cgc.fullHashModules;
+	}
+
+	/**
+	 * Gets chunk full hash modules set.
+	 * @param {Chunk} chunk the chunk
+	 * @returns {ReadonlySet | undefined} set of modules (do not modify)
+	 */
+	getChunkFullHashModulesSet(chunk) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		return cgc.fullHashModules;
+	}
+
+	/**
+	 * Gets chunk dependent hash modules iterable.
+	 * @param {Chunk} chunk the chunk
+	 * @returns {Iterable | undefined} iterable of modules (do not modify)
+	 */
+	getChunkDependentHashModulesIterable(chunk) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		return cgc.dependentHashModules;
+	}
+
+	/**
+	 * Gets chunk entry modules with chunk group iterable.
+	 * @param {Chunk} chunk the chunk
+	 * @returns {Iterable} iterable of modules (do not modify)
+	 */
+	getChunkEntryModulesWithChunkGroupIterable(chunk) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		return cgc.entryModules;
+	}
+
+	/**
+	 * Gets block chunk group.
+	 * @param {AsyncDependenciesBlock} depBlock the async block
+	 * @returns {ChunkGroup | undefined} the chunk group
+	 */
+	getBlockChunkGroup(depBlock) {
+		return this._blockChunkGroups.get(depBlock);
+	}
+
+	/**
+	 * Connects block and chunk group.
+	 * @param {AsyncDependenciesBlock} depBlock the async block
+	 * @param {ChunkGroup} chunkGroup the chunk group
+	 * @returns {void}
+	 */
+	connectBlockAndChunkGroup(depBlock, chunkGroup) {
+		this._blockChunkGroups.set(depBlock, chunkGroup);
+		chunkGroup.addBlock(depBlock);
+	}
+
+	/**
+	 * Disconnects chunk group.
+	 * @param {ChunkGroup} chunkGroup the chunk group
+	 * @returns {void}
+	 */
+	disconnectChunkGroup(chunkGroup) {
+		for (const block of chunkGroup.blocksIterable) {
+			this._blockChunkGroups.delete(block);
+		}
+		// TODO refactor by moving blocks list into ChunkGraph
+		chunkGroup._blocks.clear();
+	}
+
+	/**
+	 * Returns the id of the module.
+	 * @param {Module} module the module
+	 * @returns {ModuleId | null} the id of the module
+	 */
+	getModuleId(module) {
+		const cgm = this._getChunkGraphModule(module);
+		return cgm.id;
+	}
+
+	/**
+	 * Updates module id using the provided module.
+	 * @param {Module} module the module
+	 * @param {ModuleId} id the id of the module
+	 * @returns {void}
+	 */
+	setModuleId(module, id) {
+		const cgm = this._getChunkGraphModule(module);
+		cgm.id = id;
+	}
+
+	/**
+	 * Returns the id of the runtime.
+	 * @param {string} runtime runtime
+	 * @returns {RuntimeId} the id of the runtime
+	 */
+	getRuntimeId(runtime) {
+		return /** @type {RuntimeId} */ (this._runtimeIds.get(runtime));
+	}
+
+	/**
+	 * Updates runtime id using the provided runtime.
+	 * @param {string} runtime runtime
+	 * @param {RuntimeId} id the id of the runtime
+	 * @returns {void}
+	 */
+	setRuntimeId(runtime, id) {
+		this._runtimeIds.set(runtime, id);
+	}
+
+	/**
+	 * Get module hash info.
+	 * @template T
+	 * @param {Module} module the module
+	 * @param {RuntimeSpecMap} hashes hashes data
+	 * @param {RuntimeSpec} runtime the runtime
+	 * @returns {T} hash
+	 */
+	_getModuleHashInfo(module, hashes, runtime) {
+		if (!hashes) {
+			throw new Error(
+				`Module ${module.identifier()} has no hash info for runtime ${runtimeToString(
+					runtime
+				)} (hashes not set at all)`
+			);
+		} else if (runtime === undefined) {
+			const hashInfoItems = new Set(hashes.values());
+			if (hashInfoItems.size !== 1) {
+				throw new Error(
+					`No unique hash info entry for unspecified runtime for ${module.identifier()} (existing runtimes: ${Array.from(
+						hashes.keys(),
+						(r) => runtimeToString(r)
+					).join(", ")}).
+Caller might not support runtime-dependent code generation (opt-out via optimization.usedExports: "global").`
+				);
+			}
+			return /** @type {T} */ (first(hashInfoItems));
+		} else {
+			const hashInfo = hashes.get(runtime);
+			if (!hashInfo) {
+				throw new Error(
+					`Module ${module.identifier()} has no hash info for runtime ${runtimeToString(
+						runtime
+					)} (available runtimes ${Array.from(
+						hashes.keys(),
+						runtimeToString
+					).join(", ")})`
+				);
+			}
+			return hashInfo;
+		}
+	}
+
+	/**
+	 * Checks whether this chunk graph contains the module.
+	 * @param {Module} module the module
+	 * @param {RuntimeSpec} runtime the runtime
+	 * @returns {boolean} true, if the module has hashes for this runtime
+	 */
+	hasModuleHashes(module, runtime) {
+		const cgm = this._getChunkGraphModule(module);
+		const hashes = /** @type {RuntimeSpecMap} */ (cgm.hashes);
+		return hashes && hashes.has(runtime);
+	}
+
+	/**
+	 * Returns hash.
+	 * @param {Module} module the module
+	 * @param {RuntimeSpec} runtime the runtime
+	 * @returns {string} hash
+	 */
+	getModuleHash(module, runtime) {
+		const cgm = this._getChunkGraphModule(module);
+		const hashes = /** @type {RuntimeSpecMap} */ (cgm.hashes);
+		return this._getModuleHashInfo(module, hashes, runtime).hash;
+	}
+
+	/**
+	 * Gets rendered module hash.
+	 * @param {Module} module the module
+	 * @param {RuntimeSpec} runtime the runtime
+	 * @returns {string} hash
+	 */
+	getRenderedModuleHash(module, runtime) {
+		const cgm = this._getChunkGraphModule(module);
+		const hashes = /** @type {RuntimeSpecMap} */ (cgm.hashes);
+		return this._getModuleHashInfo(module, hashes, runtime).renderedHash;
+	}
+
+	/**
+	 * Sets module hashes.
+	 * @param {Module} module the module
+	 * @param {RuntimeSpec} runtime the runtime
+	 * @param {string} hash the full hash
+	 * @param {string} renderedHash the shortened hash for rendering
+	 * @returns {void}
+	 */
+	setModuleHashes(module, runtime, hash, renderedHash) {
+		const cgm = this._getChunkGraphModule(module);
+		if (cgm.hashes === undefined) {
+			cgm.hashes = new RuntimeSpecMap();
+		}
+		cgm.hashes.set(runtime, new ModuleHashInfo(hash, renderedHash));
+	}
+
+	/**
+	 * Adds module runtime requirements.
+	 * @param {Module} module the module
+	 * @param {RuntimeSpec} runtime the runtime
+	 * @param {RuntimeRequirements} items runtime requirements to be added (ownership of this Set is given to ChunkGraph when transferOwnership not false)
+	 * @param {boolean} transferOwnership true: transfer ownership of the items object, false: items is immutable and shared and won't be modified
+	 * @returns {void}
+	 */
+	addModuleRuntimeRequirements(
+		module,
+		runtime,
+		items,
+		transferOwnership = true
+	) {
+		const cgm = this._getChunkGraphModule(module);
+		const runtimeRequirementsMap = cgm.runtimeRequirements;
+		if (!transferOwnership) this._sharedModuleRuntimeRequirements.add(items);
+		if (runtimeRequirementsMap === undefined) {
+			/** @type {ChunkGraphRuntimeRequirements} */
+			const map = new RuntimeSpecMap();
+			map.set(runtime, items);
+			cgm.runtimeRequirements = map;
+			return;
+		}
+		runtimeRequirementsMap.update(runtime, (runtimeRequirements) => {
+			if (runtimeRequirements === undefined) return items;
+			const owned =
+				!this._sharedModuleRuntimeRequirements.has(runtimeRequirements);
+			// Merge into whichever owned Set is larger; otherwise copy-on-write.
+			if (owned && runtimeRequirements.size >= items.size) {
+				for (const item of items) runtimeRequirements.add(item);
+				return runtimeRequirements;
+			} else if (transferOwnership) {
+				for (const item of runtimeRequirements) items.add(item);
+				this._sharedModuleRuntimeRequirements.delete(items);
+				return items;
+			} else if (owned) {
+				for (const item of items) runtimeRequirements.add(item);
+				return runtimeRequirements;
+			}
+			const merged = new Set(runtimeRequirements);
+			for (const item of items) merged.add(item);
+			return merged;
+		});
+	}
+
+	/**
+	 * Adds chunk runtime requirements.
+	 * @param {Chunk} chunk the chunk
+	 * @param {RuntimeRequirements} items runtime requirements to be added (ownership of this Set is given to ChunkGraph)
+	 * @returns {void}
+	 */
+	addChunkRuntimeRequirements(chunk, items) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		const runtimeRequirements = cgc.runtimeRequirements;
+		if (runtimeRequirements === undefined) {
+			cgc.runtimeRequirements = items;
+		} else if (runtimeRequirements.size >= items.size) {
+			for (const item of items) runtimeRequirements.add(item);
+		} else {
+			for (const item of runtimeRequirements) items.add(item);
+			cgc.runtimeRequirements = items;
+		}
+	}
+
+	/**
+	 * Adds tree runtime requirements.
+	 * @param {Chunk} chunk the chunk
+	 * @param {Iterable} items runtime requirements to be added
+	 * @returns {void}
+	 */
+	addTreeRuntimeRequirements(chunk, items) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		const runtimeRequirements = cgc.runtimeRequirementsInTree;
+		for (const item of items) runtimeRequirements.add(item);
+	}
+
+	/**
+	 * Gets module runtime requirements.
+	 * @param {Module} module the module
+	 * @param {RuntimeSpec} runtime the runtime
+	 * @returns {ReadOnlyRuntimeRequirements} runtime requirements
+	 */
+	getModuleRuntimeRequirements(module, runtime) {
+		const cgm = this._getChunkGraphModule(module);
+		const runtimeRequirements =
+			cgm.runtimeRequirements && cgm.runtimeRequirements.get(runtime);
+		return runtimeRequirements === undefined ? EMPTY_SET : runtimeRequirements;
+	}
+
+	/**
+	 * Gets chunk runtime requirements.
+	 * @param {Chunk} chunk the chunk
+	 * @returns {ReadOnlyRuntimeRequirements} runtime requirements
+	 */
+	getChunkRuntimeRequirements(chunk) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		const runtimeRequirements = cgc.runtimeRequirements;
+		return runtimeRequirements === undefined ? EMPTY_SET : runtimeRequirements;
+	}
+
+	/**
+	 * Gets module graph hash.
+	 * @param {Module} module the module
+	 * @param {RuntimeSpec} runtime the runtime
+	 * @param {boolean} withConnections include connections
+	 * @returns {string} hash
+	 */
+	getModuleGraphHash(module, runtime, withConnections = true) {
+		const cgm = this._getChunkGraphModule(module);
+		return withConnections
+			? this._getModuleGraphHashWithConnections(cgm, module, runtime)
+			: this._getModuleGraphHashBigInt(cgm, module, runtime).toString(16);
+	}
+
+	/**
+	 * Gets module graph hash big int.
+	 * @param {Module} module the module
+	 * @param {RuntimeSpec} runtime the runtime
+	 * @param {boolean} withConnections include connections
+	 * @returns {bigint} hash
+	 */
+	getModuleGraphHashBigInt(module, runtime, withConnections = true) {
+		const cgm = this._getChunkGraphModule(module);
+		return withConnections
+			? BigInt(
+					`0x${this._getModuleGraphHashWithConnections(cgm, module, runtime)}`
+				)
+			: this._getModuleGraphHashBigInt(cgm, module, runtime);
+	}
+
+	/**
+	 * Get module graph hash big int.
+	 * @param {ChunkGraphModule} cgm the ChunkGraphModule
+	 * @param {Module} module the module
+	 * @param {RuntimeSpec} runtime the runtime
+	 * @returns {bigint} hash as big int
+	 */
+	_getModuleGraphHashBigInt(cgm, module, runtime) {
+		if (cgm.graphHashes === undefined) {
+			cgm.graphHashes = new RuntimeSpecMap();
+		}
+		const graphHash = cgm.graphHashes.provide(runtime, () => {
+			const hash = createHash(this._hashFunction);
+			hash.update(`${cgm.id}${this.moduleGraph.isAsync(module)}`);
+			const sourceTypes = this._getOverwrittenModuleSourceTypes(module);
+			if (sourceTypes !== undefined) {
+				for (const type of sourceTypes) hash.update(type);
+			}
+			this.moduleGraph.getExportsInfo(module).updateHash(hash, runtime);
+			return BigInt(`0x${hash.digest("hex")}`);
+		});
+		return graphHash;
+	}
+
+	/**
+	 * Get module graph hash with connections.
+	 * @param {ChunkGraphModule} cgm the ChunkGraphModule
+	 * @param {Module} module the module
+	 * @param {RuntimeSpec} runtime the runtime
+	 * @returns {string} hash
+	 */
+	_getModuleGraphHashWithConnections(cgm, module, runtime) {
+		if (cgm.graphHashesWithConnections === undefined) {
+			cgm.graphHashesWithConnections = new RuntimeSpecMap();
+		}
+
+		return cgm.graphHashesWithConnections.provide(runtime, () => {
+			const strict =
+				module.buildMeta &&
+				/** @type {JavascriptModuleBuildMeta} */ (module.buildMeta)
+					.strictHarmonyModule;
+			const graphHash = this._getModuleGraphHashBigInt(
+				cgm,
+				module,
+				runtime
+			).toString(16);
+			const connections = this.moduleGraph.getOutgoingConnections(module);
+			/** @type {Set} */
+			const activeNamespaceModules = new Set();
+			/** @type {Map>} */
+			const connectedModules = new Map();
+			/**
+			 * Process connection.
+			 * @param {ModuleGraphConnection} connection connection
+			 * @param {string} stateInfo state info
+			 */
+			const processConnection = (connection, stateInfo) => {
+				const module = connection.module;
+				stateInfo += module.getExportsType(this.moduleGraph, strict);
+				// cspell:word Tnamespace
+				if (stateInfo === "Tnamespace") {
+					activeNamespaceModules.add(module);
+				} else {
+					const oldModule = connectedModules.get(stateInfo);
+					if (oldModule === undefined) {
+						connectedModules.set(stateInfo, module);
+					} else if (oldModule instanceof Set) {
+						oldModule.add(module);
+					} else if (oldModule !== module) {
+						connectedModules.set(stateInfo, new Set([oldModule, module]));
+					}
+				}
+			};
+			if (runtime === undefined || typeof runtime === "string") {
+				for (const connection of connections) {
+					const state = connection.getActiveState(runtime);
+					if (state === false) continue;
+					processConnection(connection, state === true ? "T" : "O");
+				}
+			} else {
+				// cspell:word Tnamespace
+				for (const connection of connections) {
+					/** @type {Set} */
+					const states = new Set();
+					let stateInfo = "";
+					forEachRuntime(
+						runtime,
+						(runtime) => {
+							const state = connection.getActiveState(runtime);
+							states.add(state);
+							stateInfo += activeStateToString(state) + runtime;
+						},
+						true
+					);
+					if (states.size === 1) {
+						const state = first(states);
+						if (state === false) continue;
+						stateInfo = activeStateToString(
+							/** @type {ConnectionState} */
+							(state)
+						);
+					}
+					processConnection(connection, stateInfo);
+				}
+			}
+			// cspell:word Tnamespace
+			if (activeNamespaceModules.size === 0 && connectedModules.size === 0) {
+				return graphHash;
+			}
+			const connectedModulesInOrder =
+				connectedModules.size > 1
+					? [...connectedModules].sort(([a], [b]) => (a < b ? -1 : 1))
+					: connectedModules;
+			const hash = createHash(this._hashFunction);
+			/**
+			 * Adds module to hash.
+			 * @param {Module} module module
+			 */
+			const addModuleToHash = (module) => {
+				hash.update(
+					this._getModuleGraphHashBigInt(
+						this._getChunkGraphModule(module),
+						module,
+						runtime
+					).toString(16)
+				);
+			};
+			/**
+			 * Adds modules to hash.
+			 * @param {Set} modules modules
+			 */
+			const addModulesToHash = (modules) => {
+				let xor = ZERO_BIG_INT;
+				for (const m of modules) {
+					xor ^= this._getModuleGraphHashBigInt(
+						this._getChunkGraphModule(m),
+						m,
+						runtime
+					);
+				}
+				hash.update(xor.toString(16));
+			};
+			if (activeNamespaceModules.size === 1) {
+				addModuleToHash(
+					/** @type {Module} */ (activeNamespaceModules.values().next().value)
+				);
+			} else if (activeNamespaceModules.size > 1) {
+				addModulesToHash(activeNamespaceModules);
+			}
+			for (const [stateInfo, modules] of connectedModulesInOrder) {
+				hash.update(stateInfo);
+				if (modules instanceof Set) {
+					addModulesToHash(modules);
+				} else {
+					addModuleToHash(modules);
+				}
+			}
+			hash.update(graphHash);
+			return hash.digest("hex");
+		});
+	}
+
+	/**
+	 * Gets tree runtime requirements.
+	 * @param {Chunk} chunk the chunk
+	 * @returns {ReadOnlyRuntimeRequirements} runtime requirements
+	 */
+	getTreeRuntimeRequirements(chunk) {
+		const cgc = this._getChunkGraphChunk(chunk);
+		return cgc.runtimeRequirementsInTree;
+	}
+
+	// TODO remove in webpack 6
+	/**
+	 * Gets chunk graph for module.
+	 * @deprecated
+	 * @param {Module} module the module
+	 * @param {string} deprecateMessage message for the deprecation message
+	 * @param {string} deprecationCode code for the deprecation
+	 * @returns {ChunkGraph} the chunk graph
+	 */
+	static getChunkGraphForModule(module, deprecateMessage, deprecationCode) {
+		const fn = deprecateGetChunkGraphForModuleMap.get(deprecateMessage);
+		if (fn) return fn(module);
+		const newFn = util.deprecate(
+			/**
+			 * Handles the callback logic for this hook.
+			 * @param {Module} module the module
+			 * @returns {ChunkGraph} the chunk graph
+			 */
+			(module) => {
+				const chunkGraph = chunkGraphForModuleMap.get(module);
+				if (!chunkGraph) {
+					throw new Error(
+						`${
+							deprecateMessage
+						}: There was no ChunkGraph assigned to the Module for backward-compat (Use the new API)`
+					);
+				}
+				return chunkGraph;
+			},
+			`${deprecateMessage}: Use new ChunkGraph API`,
+			deprecationCode
+		);
+		deprecateGetChunkGraphForModuleMap.set(deprecateMessage, newFn);
+		return newFn(module);
+	}
+
+	// TODO remove in webpack 6
+	// BACKWARD-COMPAT START
+	/**
+	 * Sets chunk graph for module.
+	 * @deprecated
+	 * @param {Module} module the module
+	 * @param {ChunkGraph} chunkGraph the chunk graph
+	 * @returns {void}
+	 */
+	static setChunkGraphForModule(module, chunkGraph) {
+		chunkGraphForModuleMap.set(module, chunkGraph);
+	}
+
+	/**
+	 * Clear chunk graph for module.
+	 * @deprecated
+	 * @param {Module} module the module
+	 * @returns {void}
+	 */
+	static clearChunkGraphForModule(module) {
+		chunkGraphForModuleMap.delete(module);
+	}
+
+	/**
+	 * Gets chunk graph for chunk.
+	 * @deprecated
+	 * @param {Chunk} chunk the chunk
+	 * @param {string} deprecateMessage message for the deprecation message
+	 * @param {string} deprecationCode code for the deprecation
+	 * @returns {ChunkGraph} the chunk graph
+	 */
+	static getChunkGraphForChunk(chunk, deprecateMessage, deprecationCode) {
+		const fn = deprecateGetChunkGraphForChunkMap.get(deprecateMessage);
+		if (fn) return fn(chunk);
+		const newFn = util.deprecate(
+			/**
+			 * Handles the callback logic for this hook.
+			 * @param {Chunk} chunk the chunk
+			 * @returns {ChunkGraph} the chunk graph
+			 */
+			(chunk) => {
+				const chunkGraph = chunkGraphForChunkMap.get(chunk);
+				if (!chunkGraph) {
+					throw new Error(
+						`${
+							deprecateMessage
+						}There was no ChunkGraph assigned to the Chunk for backward-compat (Use the new API)`
+					);
+				}
+				return chunkGraph;
+			},
+			`${deprecateMessage}: Use new ChunkGraph API`,
+			deprecationCode
+		);
+		deprecateGetChunkGraphForChunkMap.set(deprecateMessage, newFn);
+		return newFn(chunk);
+	}
+
+	/**
+	 * Sets chunk graph for chunk.
+	 * @deprecated
+	 * @param {Chunk} chunk the chunk
+	 * @param {ChunkGraph} chunkGraph the chunk graph
+	 * @returns {void}
+	 */
+	static setChunkGraphForChunk(chunk, chunkGraph) {
+		chunkGraphForChunkMap.set(chunk, chunkGraph);
+	}
+
+	/**
+	 * Clear chunk graph for chunk.
+	 * @deprecated
+	 * @param {Chunk} chunk the chunk
+	 * @returns {void}
+	 */
+	static clearChunkGraphForChunk(chunk) {
+		chunkGraphForChunkMap.delete(chunk);
+	}
+	// BACKWARD-COMPAT END
+}
+
+// TODO remove in webpack 6
+/** @type {WeakMap} */
+const chunkGraphForModuleMap = new WeakMap();
+
+// TODO remove in webpack 6
+/** @type {WeakMap} */
+const chunkGraphForChunkMap = new WeakMap();
+
+// TODO remove in webpack 6
+/** @type {Map ChunkGraph>} */
+const deprecateGetChunkGraphForModuleMap = new Map();
+
+// TODO remove in webpack 6
+/** @type {Map ChunkGraph>} */
+const deprecateGetChunkGraphForChunkMap = new Map();
+
+module.exports = ChunkGraph;
diff --git a/lib/ChunkGroup.js b/lib/ChunkGroup.js
index 482800b7ed4..950d2872ab6 100644
--- a/lib/ChunkGroup.js
+++ b/lib/ChunkGroup.js
@@ -2,33 +2,56 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
+const util = require("util");
 const SortableSet = require("./util/SortableSet");
-const compareLocations = require("./compareLocations");
+const {
+	compareChunks,
+	compareIterables,
+	compareLocations
+} = require("./util/comparators");
 
+/** @typedef {import("./AsyncDependenciesBlock")} AsyncDependenciesBlock */
 /** @typedef {import("./Chunk")} Chunk */
+/** @typedef {import("./ChunkGraph")} ChunkGraph */
+/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */
+/** @typedef {import("./Entrypoint")} Entrypoint */
 /** @typedef {import("./Module")} Module */
-/** @typedef {import("./ModuleReason")} ModuleReason */
+/** @typedef {import("./ModuleGraph")} ModuleGraph */
+
+/** @typedef {{ module: Module | null, loc: DependencyLocation, request: string }} OriginRecord */
+
+/**
+ * Describes the scheduling hints that can be attached to a chunk group.
+ * These values influence how child groups are ordered for preload/prefetch
+ * and how their fetch priority is exposed to runtime code.
+ * @typedef {object} RawChunkGroupOptions
+ * @property {number=} preloadOrder
+ * @property {number=} prefetchOrder
+ * @property {number=} cssPreloadOrder preload only the chunk's CSS (`as="style"`), not its JS
+ * @property {("low" | "high" | "auto")=} fetchPriority
+ */
 
-/** @typedef {{id: number}} HasId */
-/** @typedef {{module: Module, loc: TODO, request: string}} OriginRecord */
-/** @typedef {string|{name: string}} ChunkGroupOptions */
+/** @typedef {RawChunkGroupOptions & { name?: string | null }} ChunkGroupOptions */
 
 let debugId = 5000;
 
 /**
+ * Materializes a sortable set as an array without changing its current order.
+ * Used with `SortableSet` caches that expect a stable array result.
  * @template T
  * @param {SortableSet} set set to convert to array.
  * @returns {T[]} the array format of existing set
  */
-const getArray = set => Array.from(set);
+const getArray = (set) => [...set];
 
 /**
  * A convenience method used to sort chunks based on their id's
- * @param {HasId} a first sorting comparator
- * @param {HasId} b second sorting comparator
- * @returns {1|0|-1} a sorting index to determine order
+ * @param {ChunkGroup} a first sorting comparator
+ * @param {ChunkGroup} b second sorting comparator
+ * @returns {1 | 0 | -1} a sorting index to determine order
  */
 const sortById = (a, b) => {
 	if (a.id < b.id) return -1;
@@ -37,9 +60,11 @@ const sortById = (a, b) => {
 };
 
 /**
+ * Orders origin records by referencing module and then by source location.
+ * This keeps origin metadata deterministic for hashing and diagnostics.
  * @param {OriginRecord} a the first comparator in sort
  * @param {OriginRecord} b the second comparator in sort
- * @returns {1|-1|0} returns sorting order as index
+ * @returns {1 | -1 | 0} returns sorting order as index
  */
 const sortOrigin = (a, b) => {
 	const aIdent = a.module ? a.module.identifier() : "";
@@ -49,10 +74,15 @@ const sortOrigin = (a, b) => {
 	return compareLocations(a.loc, b.loc);
 };
 
+/**
+ * Represents a connected group of chunks along with the parent/child
+ * relationships, async blocks, and traversal metadata webpack tracks for it.
+ */
 class ChunkGroup {
 	/**
-	 * Creates an instance of ChunkGroup.
-	 * @param {ChunkGroupOptions=} options chunk group options passed to chunkGroup
+	 * Creates a chunk group and initializes the relationship sets and ordering
+	 * metadata used while building and optimizing the chunk graph.
+	 * @param {string | ChunkGroupOptions=} options chunk group options passed to chunkGroup
 	 */
 	constructor(options) {
 		if (typeof options === "string") {
@@ -62,28 +92,65 @@ class ChunkGroup {
 		}
 		/** @type {number} */
 		this.groupDebugId = debugId++;
+		/** @type {ChunkGroupOptions} */
 		this.options = options;
+		/** @type {SortableSet} */
 		this._children = new SortableSet(undefined, sortById);
+		/** @type {SortableSet} */
 		this._parents = new SortableSet(undefined, sortById);
+		/** @type {SortableSet} */
+		this._asyncEntrypoints = new SortableSet(undefined, sortById);
+		/** @type {SortableSet} */
 		this._blocks = new SortableSet();
 		/** @type {Chunk[]} */
 		this.chunks = [];
 		/** @type {OriginRecord[]} */
 		this.origins = [];
+
+		/** @typedef {Map} OrderIndices */
+
+		/** Indices in top-down order */
+		/**
+		 * @private
+		 * @type {OrderIndices}
+		 */
+		this._modulePreOrderIndices = new Map();
+		/** Indices in bottom-up order */
+		/**
+		 * @private
+		 * @type {OrderIndices}
+		 */
+		this._modulePostOrderIndices = new Map();
+		/** @type {number | undefined} */
+		this.index = undefined;
 	}
 
 	/**
-	 * when a new chunk is added to a chunkGroup, addingOptions will occur.
+	 * Merges additional options into the chunk group.
+	 * Order-based options are combined by taking the higher priority, while
+	 * unsupported conflicts surface as an explicit error.
 	 * @param {ChunkGroupOptions} options the chunkGroup options passed to addOptions
 	 * @returns {void}
 	 */
 	addOptions(options) {
-		for (const key of Object.keys(options)) {
+		for (const key of /** @type {(keyof ChunkGroupOptions)[]} */ (
+			Object.keys(options)
+		)) {
 			if (this.options[key] === undefined) {
-				this.options[key] = options[key];
+				/** @type {ChunkGroupOptions[keyof ChunkGroupOptions]} */
+				(this.options[key]) = options[key];
 			} else if (this.options[key] !== options[key]) {
 				if (key.endsWith("Order")) {
-					this.options[key] = Math.max(this.options[key], options[key]);
+					const orderKey =
+						/** @type {Exclude} */
+						(key);
+
+					this.options[orderKey] = Math.max(
+						/** @type {number} */
+						(this.options[orderKey]),
+						/** @type {number} */
+						(options[orderKey])
+					);
 				} else {
 					throw new Error(
 						`ChunkGroup.addOptions: No option merge strategy for ${key}`
@@ -94,40 +161,44 @@ class ChunkGroup {
 	}
 
 	/**
-	 * returns the name of current ChunkGroup
-	 * @returns {string|undefined} returns the ChunkGroup name
+	 * Returns the configured name of the chunk group, if one was assigned.
+	 * @returns {ChunkGroupOptions["name"]} returns the ChunkGroup name
 	 */
 	get name() {
 		return this.options.name;
 	}
 
 	/**
-	 * sets a new name for current ChunkGroup
-	 * @param {string} value the new name for ChunkGroup
+	 * Updates the configured name of the chunk group.
+	 * @param {string | undefined} value the new name for ChunkGroup
 	 * @returns {void}
 	 */
 	set name(value) {
 		this.options.name = value;
 	}
 
+	/* istanbul ignore next */
 	/**
-	 * get a uniqueId for ChunkGroup, made up of its member Chunk debugId's
+	 * Returns a debug-only identifier derived from the group's member chunk
+	 * debug ids. This is primarily useful in diagnostics and assertions.
 	 * @returns {string} a unique concatenation of chunk debugId's
 	 */
 	get debugId() {
-		return Array.from(this.chunks, x => x.debugId).join("+");
+		return Array.from(this.chunks, (x) => x.debugId).join("+");
 	}
 
 	/**
-	 * get a unique id for ChunkGroup, made up of its member Chunk id's
+	 * Returns an identifier derived from the ids of the chunks currently in
+	 * the group.
 	 * @returns {string} a unique concatenation of chunk ids
 	 */
 	get id() {
-		return Array.from(this.chunks, x => x.id).join("+");
+		return Array.from(this.chunks, (x) => x.id).join("+");
 	}
 
 	/**
-	 * Performs an unshift of a specific chunk
+	 * Moves a chunk to the front of the group or inserts it when it is not
+	 * already present.
 	 * @param {Chunk} chunk chunk being unshifted
 	 * @returns {boolean} returns true if attempted chunk shift is accepted
 	 */
@@ -144,7 +215,8 @@ class ChunkGroup {
 	}
 
 	/**
-	 * inserts a chunk before another existing chunk in group
+	 * Inserts a chunk directly before another chunk that already belongs to the
+	 * group, preserving the rest of the ordering.
 	 * @param {Chunk} chunk Chunk being inserted
 	 * @param {Chunk} before Placeholder/target chunk marking new chunk insertion point
 	 * @returns {boolean} return true if insertion was successful
@@ -166,9 +238,9 @@ class ChunkGroup {
 	}
 
 	/**
-	 * add a chunk into ChunkGroup. Is pushed on or prepended
+	 * Appends a chunk to the group when it is not already a member.
 	 * @param {Chunk} chunk chunk being pushed into ChunkGroupS
-	 * @returns {boolean} returns true if chunk addition was ssuccesful.
+	 * @returns {boolean} returns true if chunk addition was successful.
 	 */
 	pushChunk(chunk) {
 		const oldIdx = this.chunks.indexOf(chunk);
@@ -180,9 +252,11 @@ class ChunkGroup {
 	}
 
 	/**
+	 * Replaces one member chunk with another while preserving the group's
+	 * ordering and avoiding duplicates.
 	 * @param {Chunk} oldChunk chunk to be replaced
-	 * @param {Chunk} newChunk New chunkt that will be replaced
-	 * @returns {boolean} rerturns true for
+	 * @param {Chunk} newChunk New chunk that will be replaced with
+	 * @returns {boolean | undefined} returns true if the replacement was successful
 	 */
 	replaceChunk(oldChunk, newChunk) {
 		const oldIdx = this.chunks.indexOf(oldChunk);
@@ -202,6 +276,11 @@ class ChunkGroup {
 		}
 	}
 
+	/**
+	 * Removes a chunk from this group.
+	 * @param {Chunk} chunk chunk to remove
+	 * @returns {boolean} returns true if chunk was removed
+	 */
 	removeChunk(chunk) {
 		const idx = this.chunks.indexOf(chunk);
 		if (idx >= 0) {
@@ -211,18 +290,30 @@ class ChunkGroup {
 		return false;
 	}
 
+	/**
+	 * Indicates whether this chunk group is loaded as part of the initial page
+	 * load instead of being created lazily.
+	 * @returns {boolean} true, when this chunk group will be loaded on initial page load
+	 */
 	isInitial() {
 		return false;
 	}
 
-	addChild(chunk) {
-		if (this._children.has(chunk)) {
-			return false;
-		}
-		this._children.add(chunk);
-		return true;
+	/**
+	 * Adds a child chunk group to the current group.
+	 * @param {ChunkGroup} group chunk group to add
+	 * @returns {boolean} returns true if chunk group was added
+	 */
+	addChild(group) {
+		const size = this._children.size;
+		this._children.add(group);
+		return size !== this._children.size;
 	}
 
+	/**
+	 * Returns the child chunk groups reachable from this group.
+	 * @returns {ChunkGroup[]} returns the children of this group
+	 */
 	getChildren() {
 		return this._children.getFromCache(getArray);
 	}
@@ -235,16 +326,27 @@ class ChunkGroup {
 		return this._children;
 	}
 
-	removeChild(chunk) {
-		if (!this._children.has(chunk)) {
+	/**
+	 * Removes a child chunk group and clears the corresponding parent link on
+	 * the removed child.
+	 * @param {ChunkGroup} group the chunk group to remove
+	 * @returns {boolean} returns true if the chunk group was removed
+	 */
+	removeChild(group) {
+		if (!this._children.has(group)) {
 			return false;
 		}
 
-		this._children.delete(chunk);
-		chunk.removeParent(this);
+		this._children.delete(group);
+		group.removeParent(this);
 		return true;
 	}
 
+	/**
+	 * Records a parent chunk group relationship.
+	 * @param {ChunkGroup} parentChunk the parent group to be added into
+	 * @returns {boolean} returns true if this chunk group was added to the parent group
+	 */
 	addParent(parentChunk) {
 		if (!this._parents.has(parentChunk)) {
 			this._parents.add(parentChunk);
@@ -253,21 +355,23 @@ class ChunkGroup {
 		return false;
 	}
 
+	/**
+	 * Returns the parent chunk groups that can lead to this group.
+	 * @returns {ChunkGroup[]} returns the parents of this group
+	 */
 	getParents() {
 		return this._parents.getFromCache(getArray);
 	}
 
-	setParents(newParents) {
-		this._parents.clear();
-		for (const p of newParents) {
-			this._parents.add(p);
-		}
-	}
-
 	getNumberOfParents() {
 		return this._parents.size;
 	}
 
+	/**
+	 * Checks whether the provided group is registered as a parent.
+	 * @param {ChunkGroup} parent the parent group
+	 * @returns {boolean} returns true if the parent group contains this group
+	 */
 	hasParent(parent) {
 		return this._parents.has(parent);
 	}
@@ -276,16 +380,37 @@ class ChunkGroup {
 		return this._parents;
 	}
 
-	removeParent(chunk) {
-		if (this._parents.delete(chunk)) {
-			chunk.removeChunk(this);
+	/**
+	 * Removes a parent chunk group and clears the reverse child relationship.
+	 * @param {ChunkGroup} chunkGroup the parent group
+	 * @returns {boolean} returns true if this group has been removed from the parent
+	 */
+	removeParent(chunkGroup) {
+		if (this._parents.delete(chunkGroup)) {
+			chunkGroup.removeChild(this);
 			return true;
 		}
 		return false;
 	}
 
 	/**
-	 * @returns {Array} - an array containing the blocks
+	 * Registers an async entrypoint that is rooted in this chunk group.
+	 * @param {Entrypoint} entrypoint entrypoint to add
+	 * @returns {boolean} returns true if entrypoint was added
+	 */
+	addAsyncEntrypoint(entrypoint) {
+		const size = this._asyncEntrypoints.size;
+		this._asyncEntrypoints.add(entrypoint);
+		return size !== this._asyncEntrypoints.size;
+	}
+
+	get asyncEntrypointsIterable() {
+		return this._asyncEntrypoints;
+	}
+
+	/**
+	 * Returns the async dependency blocks that create or reference this group.
+	 * @returns {AsyncDependenciesBlock[]} an array containing the blocks
 	 */
 	getBlocks() {
 		return this._blocks.getFromCache(getArray);
@@ -295,14 +420,28 @@ class ChunkGroup {
 		return this._blocks.size;
 	}
 
+	/**
+	 * Checks whether an async dependency block is associated with this group.
+	 * @param {AsyncDependenciesBlock} block block
+	 * @returns {boolean} true, if block exists
+	 */
 	hasBlock(block) {
 		return this._blocks.has(block);
 	}
 
+	/**
+	 * Exposes the group's async dependency blocks as an iterable.
+	 * @returns {Iterable} blocks
+	 */
 	get blocksIterable() {
 		return this._blocks;
 	}
 
+	/**
+	 * Associates an async dependency block with this chunk group.
+	 * @param {AsyncDependenciesBlock} block a block
+	 * @returns {boolean} false, if block was already added
+	 */
 	addBlock(block) {
 		if (!this._blocks.has(block)) {
 			this._blocks.add(block);
@@ -311,6 +450,14 @@ class ChunkGroup {
 		return false;
 	}
 
+	/**
+	 * Records where this chunk group originated from in user code.
+	 * The origin is used for diagnostics, ordering, and reporting.
+	 * @param {Module | null} module origin module
+	 * @param {DependencyLocation} loc location of the reference in the origin module
+	 * @param {string} request request name of the reference
+	 * @returns {void}
+	 */
 	addOrigin(module, loc, request) {
 		this.origins.push({
 			module,
@@ -319,14 +466,12 @@ class ChunkGroup {
 		});
 	}
 
-	containsModule(module) {
-		for (const chunk of this.chunks) {
-			if (chunk.containsModule(module)) return true;
-		}
-		return false;
-	}
-
+	/**
+	 * Collects the emitted files produced by every chunk in the group.
+	 * @returns {string[]} the files contained this chunk group
+	 */
 	getFiles() {
+		/** @type {Set} */
 		const files = new Set();
 
 		for (const chunk of this.chunks) {
@@ -335,14 +480,16 @@ class ChunkGroup {
 			}
 		}
 
-		return Array.from(files);
+		return [...files];
 	}
 
 	/**
-	 * @param {ModuleReason} reason reason for removing ChunkGroup
+	 * Disconnects this group from its parents, children, and chunks.
+	 * Child groups are reconnected to this group's parents so the surrounding
+	 * graph remains intact after removal.
 	 * @returns {void}
 	 */
-	remove(reason) {
+	remove() {
 		// cleanup parents
 		for (const parentChunkGroup of this._parents) {
 			// remove this chunk from its parents
@@ -363,7 +510,7 @@ class ChunkGroup {
 
 		/**
 		 * we need to iterate again over the children
-		 * to remove this from the childs parents.
+		 * to remove this from the child's parents.
 		 * This can not be done in the above loop
 		 * as it is not guaranteed that `this._parents` contains anything.
 		 */
@@ -372,11 +519,6 @@ class ChunkGroup {
 			chunkGroup._parents.delete(this);
 		}
 
-		// cleanup blocks
-		for (const block of this._blocks) {
-			block.chunkGroup = null;
-		}
-
 		// remove chunks
 		for (const chunk of this.chunks) {
 			chunk.removeGroup(this);
@@ -385,89 +527,176 @@ class ChunkGroup {
 
 	sortItems() {
 		this.origins.sort(sortOrigin);
-		this._parents.sort();
-		this._children.sort();
 	}
 
 	/**
 	 * Sorting predicate which allows current ChunkGroup to be compared against another.
 	 * Sorting values are based off of number of chunks in ChunkGroup.
-	 *
+	 * @param {ChunkGraph} chunkGraph the chunk graph
 	 * @param {ChunkGroup} otherGroup the chunkGroup to compare this against
-	 * @returns {-1|0|1} sort position for comparison
+	 * @returns {-1 | 0 | 1} sort position for comparison
 	 */
-	compareTo(otherGroup) {
+	compareTo(chunkGraph, otherGroup) {
 		if (this.chunks.length > otherGroup.chunks.length) return -1;
 		if (this.chunks.length < otherGroup.chunks.length) return 1;
-		const a = this.chunks[Symbol.iterator]();
-		const b = otherGroup.chunks[Symbol.iterator]();
-		// eslint-disable-next-line
-		while (true) {
-			const aItem = a.next();
-			const bItem = b.next();
-			if (aItem.done) return 0;
-			const cmp = aItem.value.compareTo(bItem.value);
-			if (cmp !== 0) return cmp;
+		return compareIterables(compareChunks(chunkGraph))(
+			this.chunks,
+			otherGroup.chunks
+		);
+	}
+
+	/**
+	 * Aggregates per-block `*Order` options for the blocks that bridge this
+	 * chunk group to the given child chunk group. `*Order` options are tied to
+	 * the originating `import()` call and must not be sourced from the child's
+	 * shared options, otherwise a webpackPrefetch/Preload directive from one
+	 * parent would leak into other parents that share the child by name.
+	 * @param {ChunkGroup} childGroup the child chunk group
+	 * @param {ChunkGraph} chunkGraph the chunk graph
+	 * @returns {Record} merged `*Order` options for the edge from this group to `childGroup`
+	 */
+	getChildOrderOptions(childGroup, chunkGraph) {
+		/** @type {Record} */
+		const result = Object.create(null);
+		let bridged = false;
+		for (const block of childGroup.blocksIterable) {
+			const rootModule = /** @type {Module} */ (block.getRootBlock());
+			if (!chunkGraph.isModuleInChunkGroup(rootModule, this)) continue;
+			bridged = true;
+			const opts = block.groupOptions;
+			if (!opts) continue;
+			for (const key of Object.keys(opts)) {
+				if (!key.endsWith("Order")) continue;
+				const value =
+					/** @type {number} */
+					(opts[/** @type {keyof ChunkGroupOptions} */ (key)]);
+				if (typeof value !== "number") continue;
+				if (result[key] === undefined || value > result[key]) {
+					result[key] = value;
+				}
+			}
+		}
+		// Fall back to the child's own options only when no block bridges
+		// this edge (e.g. a chunk group created by APIs that don't go through
+		// an AsyncDependenciesBlock). Otherwise we'd reintroduce the leak.
+		if (!bridged) {
+			for (const key of Object.keys(childGroup.options)) {
+				if (!key.endsWith("Order")) continue;
+				const value =
+					childGroup.options[/** @type {keyof ChunkGroupOptions} */ (key)];
+				if (typeof value === "number") {
+					result[key] = value;
+				}
+			}
 		}
+		return result;
 	}
 
-	getChildrenByOrders() {
+	/**
+	 * Groups child chunk groups by their `*Order` options and sorts each group
+	 * by descending order and deterministic chunk-group comparison.
+	 * @param {ModuleGraph} moduleGraph the module graph
+	 * @param {ChunkGraph} chunkGraph the chunk graph
+	 * @returns {Record} mapping from children type to ordered list of ChunkGroups
+	 */
+	getChildrenByOrders(moduleGraph, chunkGraph) {
+		/** @type {Map} */
 		const lists = new Map();
 		for (const childGroup of this._children) {
-			// TODO webpack 5 remove this check for options
-			if (typeof childGroup.options === "object") {
-				for (const key of Object.keys(childGroup.options)) {
-					if (key.endsWith("Order")) {
-						const name = key.substr(0, key.length - "Order".length);
-						let list = lists.get(name);
-						if (list === undefined) {
-							lists.set(name, (list = []));
-						}
-						list.push({
-							order: childGroup.options[key],
-							group: childGroup
-						});
-					}
+			const edgeOptions = this.getChildOrderOptions(childGroup, chunkGraph);
+			for (const key of Object.keys(edgeOptions)) {
+				const name = key.slice(0, key.length - "Order".length);
+				let list = lists.get(name);
+				if (list === undefined) {
+					lists.set(name, (list = []));
 				}
+				list.push({
+					order: edgeOptions[key],
+					group: childGroup
+				});
 			}
 		}
+		/** @type {Record} */
 		const result = Object.create(null);
 		for (const [name, list] of lists) {
 			list.sort((a, b) => {
 				const cmp = b.order - a.order;
 				if (cmp !== 0) return cmp;
-				// TOOD webpack 5 remove this check of compareTo
-				if (a.group.compareTo) {
-					return a.group.compareTo(b.group);
-				}
-				return 0;
+				return a.group.compareTo(chunkGraph, b.group);
 			});
-			result[name] = list.map(i => i.group);
+			result[name] = list.map((i) => i.group);
 		}
 		return result;
 	}
 
+	/**
+	 * Stores the module's top-down traversal index within this group.
+	 * @param {Module} module module for which the index should be set
+	 * @param {number} index the index of the module
+	 * @returns {void}
+	 */
+	setModulePreOrderIndex(module, index) {
+		this._modulePreOrderIndices.set(module, index);
+	}
+
+	/**
+	 * Returns the module's top-down traversal index within this group.
+	 * @param {Module} module the module
+	 * @returns {number | undefined} index
+	 */
+	getModulePreOrderIndex(module) {
+		return this._modulePreOrderIndices.get(module);
+	}
+
+	/**
+	 * Stores the module's bottom-up traversal index within this group.
+	 * @param {Module} module module for which the index should be set
+	 * @param {number} index the index of the module
+	 * @returns {void}
+	 */
+	setModulePostOrderIndex(module, index) {
+		this._modulePostOrderIndices.set(module, index);
+	}
+
+	/**
+	 * Returns the module's bottom-up traversal index within this group.
+	 * @param {Module} module the module
+	 * @returns {number | undefined} index
+	 */
+	getModulePostOrderIndex(module) {
+		return this._modulePostOrderIndices.get(module);
+	}
+
+	/* istanbul ignore next */
 	checkConstraints() {
 		const chunk = this;
 		for (const child of chunk._children) {
 			if (!child._parents.has(chunk)) {
 				throw new Error(
-					`checkConstraints: child missing parent ${chunk.debugId} -> ${
-						child.debugId
-					}`
+					`checkConstraints: child missing parent ${chunk.debugId} -> ${child.debugId}`
 				);
 			}
 		}
 		for (const parentChunk of chunk._parents) {
 			if (!parentChunk._children.has(chunk)) {
 				throw new Error(
-					`checkConstraints: parent missing child ${parentChunk.debugId} <- ${
-						chunk.debugId
-					}`
+					`checkConstraints: parent missing child ${parentChunk.debugId} <- ${chunk.debugId}`
 				);
 			}
 		}
 	}
 }
 
+ChunkGroup.prototype.getModuleIndex = util.deprecate(
+	ChunkGroup.prototype.getModulePreOrderIndex,
+	"ChunkGroup.getModuleIndex was renamed to getModulePreOrderIndex",
+	"DEP_WEBPACK_CHUNK_GROUP_GET_MODULE_INDEX"
+);
+
+ChunkGroup.prototype.getModuleIndex2 = util.deprecate(
+	ChunkGroup.prototype.getModulePostOrderIndex,
+	"ChunkGroup.getModuleIndex2 was renamed to getModulePostOrderIndex",
+	"DEP_WEBPACK_CHUNK_GROUP_GET_MODULE_INDEX_2"
+);
+
 module.exports = ChunkGroup;
diff --git a/lib/ChunkRenderError.js b/lib/ChunkRenderError.js
deleted file mode 100644
index 0d0eb2cbc53..00000000000
--- a/lib/ChunkRenderError.js
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const WebpackError = require("./WebpackError");
-
-/** @typedef {import("./Chunk")} Chunk */
-
-class ChunkRenderError extends WebpackError {
-	/**
-	 * Create a new ChunkRenderError
-	 * @param {Chunk} chunk A chunk
-	 * @param {string} file Related file
-	 * @param {Error} error Original error
-	 */
-	constructor(chunk, file, error) {
-		super();
-
-		this.name = "ChunkRenderError";
-		this.error = error;
-		this.message = error.message;
-		this.details = error.stack;
-		this.file = file;
-		this.chunk = chunk;
-
-		Error.captureStackTrace(this, this.constructor);
-	}
-}
-
-module.exports = ChunkRenderError;
diff --git a/lib/ChunkTemplate.js b/lib/ChunkTemplate.js
index c22586f1fcc..cecdca1fca9 100644
--- a/lib/ChunkTemplate.js
+++ b/lib/ChunkTemplate.js
@@ -2,70 +2,178 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
-const { Tapable, SyncWaterfallHook, SyncHook } = require("tapable");
+const util = require("util");
+const JavascriptModulesPlugin = require("./javascript/JavascriptModulesPlugin");
 
-/** @typedef {import("./ModuleTemplate")} ModuleTemplate */
+/** @typedef {import("tapable").Tap} Tap */
+/** @typedef {import("./config/defaults").OutputNormalizedWithDefaults} OutputOptions */
 /** @typedef {import("./Chunk")} Chunk */
-/** @typedef {import("./Module")} Module} */
-/** @typedef {import("crypto").Hash} Hash */
-
+/** @typedef {import("./Compilation")} Compilation */
+/** @typedef {import("./Compilation").ChunkHashContext} ChunkHashContext */
+/** @typedef {import("./Compilation").Hash} Hash */
+/** @typedef {import("./Compilation").RenderManifestEntry} RenderManifestEntry */
+/** @typedef {import("./Compilation").RenderManifestOptions} RenderManifestOptions */
+/** @typedef {import("./Compilation").Source} Source */
+/** @typedef {import("./ModuleTemplate")} ModuleTemplate */
+/** @typedef {import("./javascript/JavascriptModulesPlugin").RenderContext} RenderContext */
 /**
- * @typedef {Object} RenderManifestOptions
- * @property {Chunk} chunk the chunk used to render
- * @property {Hash} hash
- * @property {string} fullHash
- * @property {TODO} outputOptions
- * @property {{javascript: ModuleTemplate, webassembly: ModuleTemplate}} moduleTemplates
- * @property {Map} dependencyTemplates
+ * Defines the if set type used by this module.
+ * @template T
+ * @typedef {import("tapable").IfSet} IfSet
  */
 
-module.exports = class ChunkTemplate extends Tapable {
-	constructor(outputOptions) {
-		super();
-		this.outputOptions = outputOptions || {};
-		this.hooks = {
-			renderManifest: new SyncWaterfallHook(["result", "options"]),
-			modules: new SyncWaterfallHook([
-				"source",
-				"chunk",
-				"moduleTemplate",
-				"dependencyTemplates"
-			]),
-			render: new SyncWaterfallHook([
-				"source",
-				"chunk",
-				"moduleTemplate",
-				"dependencyTemplates"
-			]),
-			renderWithEntry: new SyncWaterfallHook(["source", "chunk"]),
-			hash: new SyncHook(["hash"]),
-			hashForChunk: new SyncHook(["hash", "chunk"])
-		};
-	}
-
+// TODO webpack 6 remove this class
+class ChunkTemplate {
 	/**
-	 *
-	 * @param {RenderManifestOptions} options render manifest options
-	 * @returns {TODO[]} returns render manifest
+	 * Creates an instance of ChunkTemplate.
+	 * @param {OutputOptions} outputOptions output options
+	 * @param {Compilation} compilation the compilation
 	 */
-	getRenderManifest(options) {
-		const result = [];
-
-		this.hooks.renderManifest.call(result, options);
-
-		return result;
+	constructor(outputOptions, compilation) {
+		this._outputOptions = outputOptions || {};
+		this.hooks = Object.freeze({
+			renderManifest: {
+				tap: util.deprecate(
+					/**
+					 * Handles the callback logic for this hook.
+					 * @template AdditionalOptions
+					 * @param {string | Tap & IfSet} options options
+					 * @param {(renderManifestEntries: RenderManifestEntry[], renderManifestOptions: RenderManifestOptions) => RenderManifestEntry[]} fn function
+					 */
+					(options, fn) => {
+						compilation.hooks.renderManifest.tap(
+							options,
+							(entries, options) => {
+								if (options.chunk.hasRuntime()) return entries;
+								return fn(entries, options);
+							}
+						);
+					},
+					"ChunkTemplate.hooks.renderManifest is deprecated (use Compilation.hooks.renderManifest instead)",
+					"DEP_WEBPACK_CHUNK_TEMPLATE_RENDER_MANIFEST"
+				)
+			},
+			modules: {
+				tap: util.deprecate(
+					/**
+					 * Handles the callback logic for this hook.
+					 * @template AdditionalOptions
+					 * @param {string | Tap & IfSet} options options
+					 * @param {(source: Source, moduleTemplate: ModuleTemplate, renderContext: RenderContext) => Source} fn function
+					 */
+					(options, fn) => {
+						JavascriptModulesPlugin.getCompilationHooks(
+							compilation
+						).renderChunk.tap(options, (source, renderContext) =>
+							fn(source, compilation.moduleTemplates.javascript, renderContext)
+						);
+					},
+					"ChunkTemplate.hooks.modules is deprecated (use JavascriptModulesPlugin.getCompilationHooks().renderChunk instead)",
+					"DEP_WEBPACK_CHUNK_TEMPLATE_MODULES"
+				)
+			},
+			render: {
+				tap: util.deprecate(
+					/**
+					 * Handles the callback logic for this hook.
+					 * @template AdditionalOptions
+					 * @param {string | Tap & IfSet} options options
+					 * @param {(source: Source, moduleTemplate: ModuleTemplate, renderContext: RenderContext) => Source} fn function
+					 */
+					(options, fn) => {
+						JavascriptModulesPlugin.getCompilationHooks(
+							compilation
+						).renderChunk.tap(options, (source, renderContext) =>
+							fn(source, compilation.moduleTemplates.javascript, renderContext)
+						);
+					},
+					"ChunkTemplate.hooks.render is deprecated (use JavascriptModulesPlugin.getCompilationHooks().renderChunk instead)",
+					"DEP_WEBPACK_CHUNK_TEMPLATE_RENDER"
+				)
+			},
+			renderWithEntry: {
+				tap: util.deprecate(
+					/**
+					 * Handles the callback logic for this hook.
+					 * @template AdditionalOptions
+					 * @param {string | Tap & IfSet} options options
+					 * @param {(source: Source, chunk: Chunk) => Source} fn function
+					 */
+					(options, fn) => {
+						JavascriptModulesPlugin.getCompilationHooks(compilation).render.tap(
+							options,
+							(source, renderContext) => {
+								if (
+									renderContext.chunkGraph.getNumberOfEntryModules(
+										renderContext.chunk
+									) === 0 ||
+									renderContext.chunk.hasRuntime()
+								) {
+									return source;
+								}
+								return fn(source, renderContext.chunk);
+							}
+						);
+					},
+					"ChunkTemplate.hooks.renderWithEntry is deprecated (use JavascriptModulesPlugin.getCompilationHooks().render instead)",
+					"DEP_WEBPACK_CHUNK_TEMPLATE_RENDER_WITH_ENTRY"
+				)
+			},
+			hash: {
+				tap: util.deprecate(
+					/**
+					 * Handles the callback logic for this hook.
+					 * @template AdditionalOptions
+					 * @param {string | Tap & IfSet} options options
+					 * @param {(hash: Hash) => void} fn function
+					 */
+					(options, fn) => {
+						compilation.hooks.fullHash.tap(options, fn);
+					},
+					"ChunkTemplate.hooks.hash is deprecated (use Compilation.hooks.fullHash instead)",
+					"DEP_WEBPACK_CHUNK_TEMPLATE_HASH"
+				)
+			},
+			hashForChunk: {
+				tap: util.deprecate(
+					/**
+					 * Handles the callback logic for this hook.
+					 * @template AdditionalOptions
+					 * @param {string | Tap & IfSet} options options
+					 * @param {(hash: Hash, chunk: Chunk, chunkHashContext: ChunkHashContext) => void} fn function
+					 */
+					(options, fn) => {
+						JavascriptModulesPlugin.getCompilationHooks(
+							compilation
+						).chunkHash.tap(options, (chunk, hash, context) => {
+							if (chunk.hasRuntime()) return;
+							fn(hash, chunk, context);
+						});
+					},
+					"ChunkTemplate.hooks.hashForChunk is deprecated (use JavascriptModulesPlugin.getCompilationHooks().chunkHash instead)",
+					"DEP_WEBPACK_CHUNK_TEMPLATE_HASH_FOR_CHUNK"
+				)
+			}
+		});
 	}
+}
 
-	updateHash(hash) {
-		hash.update("ChunkTemplate");
-		hash.update("2");
-		this.hooks.hash.call(hash);
-	}
+Object.defineProperty(ChunkTemplate.prototype, "outputOptions", {
+	get: util.deprecate(
+		/**
+		 * Returns output options.
+		 * @this {ChunkTemplate}
+		 * @returns {OutputOptions} output options
+		 */
+		function outputOptions() {
+			return this._outputOptions;
+		},
+		"ChunkTemplate.outputOptions is deprecated (use Compilation.outputOptions instead)",
+		"DEP_WEBPACK_CHUNK_TEMPLATE_OUTPUT_OPTIONS"
+	)
+});
 
-	updateHashForChunk(hash, chunk) {
-		this.updateHash(hash);
-		this.hooks.hashForChunk.call(hash, chunk);
-	}
-};
+module.exports = ChunkTemplate;
diff --git a/lib/CircularModulesPlugin.js b/lib/CircularModulesPlugin.js
new file mode 100644
index 00000000000..8b85b4353cb
--- /dev/null
+++ b/lib/CircularModulesPlugin.js
@@ -0,0 +1,356 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Natsu @xiaoxiaojx
+*/
+
+"use strict";
+
+const CircularDependenciesWarning = require("./errors/CircularDependenciesWarning");
+const { compareModulesByIdentifier } = require("./util/comparators");
+
+/** @typedef {import("../declarations/WebpackOptions").PerformanceOptions} PerformanceOptions */
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./ModuleGraph")} ModuleGraph */
+/** @typedef {import("./ModuleGraphConnection")} ModuleGraphConnection */
+/** @typedef {import("./Module")} Module */
+/** @typedef {import("./Module").BuildInfo} BuildInfo */
+/** @typedef {import("./RequestShortener")} RequestShortener */
+
+const PLUGIN_NAME = "CircularModulesPlugin";
+
+// Enough to name the worst tangles without printing the module graph.
+const MAX_REPORTED_CYCLES = 5;
+
+/**
+ * The module a connection makes its origin evaluate before it can run, or
+ * `null` when it does not: a weak reference never loads its target, and an
+ * async edge lives in an `AsyncDependenciesBlock`, so a synchronous
+ * dependency's parent block is the module itself. A self-reference is
+ * returned — how CommonJS reads its own `module.exports` — and is the
+ * caller's to interpret.
+ * @param {ModuleGraphConnection} connection an outgoing connection
+ * @param {Module} module the module the connection starts at
+ * @param {ModuleGraph} moduleGraph the module graph
+ * @returns {Module | null} the module evaluated first, or `null`
+ */
+const getSynchronousTarget = (connection, module, moduleGraph) => {
+	const dependency = connection.dependency;
+
+	if (!dependency) return null;
+
+	const target = connection.module;
+
+	if (!target || connection.weak) return null;
+
+	if (moduleGraph.getParentBlock(dependency) !== module) return null;
+
+	return target;
+};
+
+/**
+ * The member a group is reported from. Traversal order is not stable across
+ * runs, so the lowest identifier is picked rather than the one found first.
+ * @param {Module[]} members one group of modules that can all reach each other
+ * @returns {Module} the module the report starts at
+ */
+const getCanonicalMember = (members) => {
+	let canonical = members[0];
+
+	for (const member of members) {
+		if (compareModulesByIdentifier(member, canonical) < 0) {
+			canonical = member;
+		}
+	}
+
+	return canonical;
+};
+
+/**
+ * The shortest cycle through the canonical module of a group, as a readable
+ * path ending back where it started.
+ * @param {Module[]} members one group of modules that can all reach each other
+ * @param {ModuleGraph} moduleGraph the module graph
+ * @param {RequestShortener} requestShortener the request shortener
+ * @returns {string} the cycle path
+ */
+const formatShortestCycle = (members, moduleGraph, requestShortener) => {
+	const group = new Set(members);
+	const start = getCanonicalMember(members);
+	/** @type {Map} */
+	const previous = new Map();
+	const queue = [start];
+	/** @type {Module | undefined} */
+	let last;
+
+	for (let i = 0; i < queue.length && last === undefined; i++) {
+		const module = queue[i];
+
+		for (const connection of moduleGraph.getOutgoingConnections(module)) {
+			const target = getSynchronousTarget(connection, module, moduleGraph);
+
+			// A module reading its own exports would otherwise collapse the path.
+			if (target === null || target === module || !group.has(target)) continue;
+
+			if (target === start) {
+				last = module;
+				break;
+			}
+
+			if (previous.has(target)) continue;
+
+			previous.set(target, module);
+			queue.push(target);
+		}
+	}
+
+	const path = [start];
+
+	for (
+		let module = last;
+		module !== undefined && module !== start;
+		module = previous.get(module)
+	) {
+		path.push(module);
+	}
+
+	path.push(start);
+
+	return path
+		.reverse()
+		.map((module) => module.readableIdentifier(requestShortener))
+		.join(" -> ");
+};
+
+/**
+ * Detects circular dependencies among synchronous module imports.
+ *
+ * Builds an adjacency layout from each module's synchronous outgoing
+ * connections (skipping weak and async-block edges), then runs an iterative
+ * SCC algorithm to find circular modules.
+ *
+ * Use the static `build()` method to create an instance. All intermediate data
+ * (adjacency layout, index mappings) is local to `build()` and released on
+ * return. The instance only holds the result.
+ */
+class CycleGraph {
+	/**
+	 * @param {Set} circularModules modules in a multi-module SCC or with a self-loop (for isCircular)
+	 * @param {Module[][]} circularGroups multi-module SCC groups only (for reporting; self-loops omitted)
+	 */
+	constructor(circularModules, circularGroups) {
+		/** @type {Set} */
+		this.circularModules = circularModules;
+		/** @type {Module[][]} */
+		this.circularGroups = circularGroups;
+	}
+
+	/**
+	 * Builds a CycleGraph by constructing the synchronous outgoing-connection
+	 * adjacency list and running iterative SCC to detect circular modules.
+	 * @param {Iterable} modules the set of modules
+	 * @param {ModuleGraph} moduleGraph the module graph
+	 * @returns {CycleGraph} the result
+	 */
+	static build(modules, moduleGraph) {
+		/** @type {Module[]} */
+		const moduleList = [];
+		/** @type {Map} */
+		const moduleToIndex = new Map();
+		for (const module of modules) {
+			moduleToIndex.set(module, moduleList.length);
+			moduleList.push(module);
+		}
+
+		const size = moduleList.length;
+		if (size === 0) return new CycleGraph(new Set(), []);
+
+		/** @type {number[][]} */
+		const edges = Array.from({ length: size });
+		/** @type {boolean[]} */
+		const selfLoops = Array.from({ length: size }, () => false);
+
+		for (let i = 0; i < size; i++) {
+			const module = moduleList[i];
+			/** @type {number[]} */
+			const deps = [];
+			for (const connection of moduleGraph.getOutgoingConnections(module)) {
+				const target = getSynchronousTarget(connection, module, moduleGraph);
+				if (target === null) continue;
+				if (target === module) {
+					selfLoops[i] = true;
+					continue;
+				}
+				const targetIdx = moduleToIndex.get(target);
+				if (targetIdx !== undefined) {
+					deps.push(targetIdx);
+				}
+			}
+			edges[i] = deps;
+		}
+
+		// Iterative SCC algorithm
+		/** @type {Set} */
+		const circularModules = new Set();
+		/** @type {Module[][]} */
+		const circularGroups = [];
+		let nextIndex = 0;
+		const nodeIndex = new Int32Array(size).fill(-1);
+		const nodeLowLink = new Int32Array(size);
+		const nodeOnStack = new Uint8Array(size);
+		/** @type {number[]} */
+		const sccStack = [];
+
+		/**
+		 * @typedef {object} Frame
+		 * @property {number} node
+		 * @property {number} edgeIdx
+		 * @property {number} parent
+		 */
+
+		for (let root = 0; root < size; root++) {
+			if (nodeIndex[root] !== -1) continue;
+
+			nodeIndex[root] = nextIndex;
+			nodeLowLink[root] = nextIndex;
+			nextIndex++;
+			nodeOnStack[root] = 1;
+			sccStack.push(root);
+
+			/** @type {Frame[]} */
+			const callStack = [{ node: root, edgeIdx: 0, parent: -1 }];
+
+			while (callStack.length > 0) {
+				const frame = /** @type {Frame} */ (callStack[callStack.length - 1]);
+				const v = frame.node;
+				const vEdges = edges[v];
+
+				if (frame.edgeIdx < vEdges.length) {
+					const w = vEdges[frame.edgeIdx++];
+					if (nodeIndex[w] === -1) {
+						nodeIndex[w] = nextIndex;
+						nodeLowLink[w] = nextIndex;
+						nextIndex++;
+						nodeOnStack[w] = 1;
+						sccStack.push(w);
+						callStack.push({ node: w, edgeIdx: 0, parent: v });
+					} else if (nodeOnStack[w] && nodeIndex[w] < nodeLowLink[v]) {
+						nodeLowLink[v] = nodeIndex[w];
+					}
+				} else {
+					if (nodeLowLink[v] === nodeIndex[v]) {
+						/** @type {number[]} */
+						const group = [];
+						let w;
+						do {
+							w = /** @type {number} */ (sccStack.pop());
+							nodeOnStack[w] = 0;
+							group.push(w);
+						} while (w !== v);
+
+						if (group.length > 1 || selfLoops[v]) {
+							for (const idx of group) {
+								circularModules.add(moduleList[idx]);
+							}
+						}
+
+						// A module that only references itself is no cycle, so it is
+						// circular for inlining but never a group to report.
+						if (group.length > 1) {
+							circularGroups.push(group.map((idx) => moduleList[idx]));
+						}
+					}
+
+					callStack.pop();
+					if (
+						frame.parent !== -1 &&
+						nodeLowLink[v] < nodeLowLink[frame.parent]
+					) {
+						nodeLowLink[frame.parent] = nodeLowLink[v];
+					}
+				}
+			}
+		}
+
+		return new CycleGraph(circularModules, circularGroups);
+	}
+}
+
+/**
+ * @typedef {object} CircularModulesPluginOptions
+ * @property {PerformanceOptions["hints"]=} hints when set, report SCC groups
+ */
+
+/**
+ * One SCC scan: marks isCircular on every module, and reports groups when hints are set.
+ */
+class CircularModulesPlugin {
+	/**
+	 * @param {CircularModulesPluginOptions=} options options
+	 */
+	constructor(options) {
+		/** @type {PerformanceOptions["hints"] | undefined} */
+		this.hints = options && options.hints;
+	}
+
+	/**
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
+	apply(compiler) {
+		const hints = this.hints;
+
+		compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
+			compilation.hooks.optimizeModules.tap(PLUGIN_NAME, (modules) => {
+				const { circularModules, circularGroups } = CycleGraph.build(
+					modules,
+					compilation.moduleGraph
+				);
+
+				// Must be an explicit boolean: ConstExportsPlugin checks `=== false`.
+				for (const m of modules) {
+					/** @type {BuildInfo} */
+					(m.buildInfo).isCircular = circularModules.has(m);
+				}
+
+				if (!hints) return;
+				if (circularGroups.length === 0) return;
+
+				const { moduleGraph, requestShortener } = compilation;
+
+				// Largest tangle first; ties break by name, as the order groups are
+				// discovered in is not stable.
+				circularGroups.sort(
+					(a, b) =>
+						b.length - a.length ||
+						compareModulesByIdentifier(
+							getCanonicalMember(a),
+							getCanonicalMember(b)
+						)
+				);
+
+				// The shortest cycle of a large group names two of its modules, so the
+				// size travels with it — otherwise the group reads as a pair.
+				const groups = circularGroups
+					.slice(0, MAX_REPORTED_CYCLES)
+					.map((members) => ({
+						size: members.length,
+						cycle: formatShortestCycle(members, moduleGraph, requestShortener)
+					}));
+				const warning = new CircularDependenciesWarning(
+					groups,
+					circularGroups.length
+				);
+
+				if (hints === "error") {
+					compilation.errors.push(warning);
+				} else if (hints === "stats") {
+					compilation.hints.push(warning);
+				} else {
+					compilation.warnings.push(warning);
+				}
+			});
+		});
+	}
+}
+
+module.exports = CircularModulesPlugin;
diff --git a/lib/CleanPlugin.js b/lib/CleanPlugin.js
new file mode 100644
index 00000000000..e5cf0ff9d24
--- /dev/null
+++ b/lib/CleanPlugin.js
@@ -0,0 +1,496 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Sergey Melyukov @smelukov
+*/
+
+"use strict";
+
+const path = require("path");
+const asyncLib = require("neo-async");
+const { SyncBailHook } = require("tapable");
+const createHooksRegistry = require("./util/createHooksRegistry");
+const { join } = require("./util/fs");
+const { ABSOLUTE_PATH_REGEXP } = require("./util/identifier");
+const processAsyncTree = require("./util/processAsyncTree");
+
+/** @typedef {import("../declarations/WebpackOptions").CleanOptions} CleanOptions */
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./logging/Logger").Logger} Logger */
+/** @typedef {import("./util/fs").IStats} IStats */
+/** @typedef {import("./util/fs").OutputFileSystem} OutputFileSystem */
+/** @typedef {import("./util/fs").StatsCallback} StatsCallback */
+
+/** @typedef {Map} Assets */
+
+const createCompilationHooks = () => ({
+	/**
+	 * When returning true the file/directory will be kept during cleaning, returning false will clean it and ignore the following plugins and config.
+	 * @type {SyncBailHook<[string], boolean | void>}
+	 * @since 5.20.0
+	 */
+	keep: new SyncBailHook(["ignore"])
+});
+
+/**
+ * @typedef {ReturnType} CleanPluginCompilationHooks
+ */
+
+/**
+ * Defines the keep fn callback.
+ * @callback KeepFn
+ * @param {string} path path
+ * @returns {boolean | undefined} true, if the path should be kept
+ */
+
+const _10sec = 10 * 1000;
+
+/**
+ * merge assets map 2 into map 1
+ * @param {Assets} as1 assets
+ * @param {Assets} as2 assets
+ * @returns {void}
+ */
+const mergeAssets = (as1, as2) => {
+	for (const [key, value1] of as2) {
+		const value2 = as1.get(key);
+		if (!value2 || value1 > value2) as1.set(key, value1);
+	}
+};
+
+/** @typedef {Map} CurrentAssets */
+
+/**
+ * Returns set of directory paths.
+ * @param {CurrentAssets} assets current assets
+ * @returns {Set} Set of directory paths
+ */
+function getDirectories(assets) {
+	/** @type {Set} */
+	const directories = new Set();
+	/**
+	 * Adds the provided filename to this object.
+	 * @param {string} filename asset filename
+	 */
+	const addDirectory = (filename) => {
+		directories.add(path.dirname(filename));
+	};
+
+	// get directories of assets
+	for (const [asset] of assets) {
+		addDirectory(asset);
+	}
+	// and all parent directories
+	for (const directory of directories) {
+		addDirectory(directory);
+	}
+	return directories;
+}
+
+/** @typedef {Set} Diff */
+
+/**
+ * Returns diff to fs.
+ * @param {OutputFileSystem} fs filesystem
+ * @param {string} outputPath output path
+ * @param {CurrentAssets} currentAssets filename of the current assets (must not start with .. or ., must only use / as path separator)
+ * @param {(err?: Error | null, set?: Diff) => void} callback returns the filenames of the assets that shouldn't be there
+ * @returns {void}
+ */
+const getDiffToFs = (fs, outputPath, currentAssets, callback) => {
+	const directories = getDirectories(currentAssets);
+	/** @type {Diff} */
+	const diff = new Set();
+	asyncLib.forEachLimit(
+		directories,
+		10,
+		(directory, callback) => {
+			/** @type {NonNullable} */
+			(fs.readdir)(join(fs, outputPath, directory), (err, entries) => {
+				if (err) {
+					if (err.code === "ENOENT") return callback();
+					if (err.code === "ENOTDIR") {
+						diff.add(directory);
+						return callback();
+					}
+					return callback(err);
+				}
+				for (const entry of /** @type {string[]} */ (entries)) {
+					const file = entry;
+					// Since path.normalize("./file") === path.normalize("file"),
+					// return file directly when directory === "."
+					const filename =
+						directory && directory !== "." ? `${directory}/${file}` : file;
+					if (!directories.has(filename) && !currentAssets.has(filename)) {
+						diff.add(filename);
+					}
+				}
+				callback();
+			});
+		},
+		(err) => {
+			if (err) return callback(err);
+
+			callback(null, diff);
+		}
+	);
+};
+
+/**
+ * Gets diff to old assets.
+ * @param {Assets} currentAssets assets list
+ * @param {Assets} oldAssets old assets list
+ * @returns {Diff} diff
+ */
+const getDiffToOldAssets = (currentAssets, oldAssets) => {
+	/** @type {Diff} */
+	const diff = new Set();
+	const now = Date.now();
+	for (const [asset, ts] of oldAssets) {
+		if (ts >= now) continue;
+		if (!currentAssets.has(asset)) diff.add(asset);
+	}
+	return diff;
+};
+
+/**
+ * Processes the provided f.
+ * @param {OutputFileSystem} fs filesystem
+ * @param {string} filename path to file
+ * @param {StatsCallback} callback callback for provided filename
+ * @returns {void}
+ */
+const doStat = (fs, filename, callback) => {
+	if ("lstat" in fs) {
+		/** @type {NonNullable} */
+		(fs.lstat)(filename, callback);
+	} else {
+		fs.stat(filename, callback);
+	}
+};
+
+/**
+ * Processes the provided f.
+ * @param {OutputFileSystem} fs filesystem
+ * @param {string} outputPath output path
+ * @param {boolean} dry only log instead of fs modification
+ * @param {Logger} logger logger
+ * @param {Diff} diff filenames of the assets that shouldn't be there
+ * @param {KeepFn} isKept check if the entry is ignored
+ * @param {(err?: Error, assets?: Assets) => void} callback callback
+ * @returns {void}
+ */
+const applyDiff = (fs, outputPath, dry, logger, diff, isKept, callback) => {
+	/**
+	 * Processes the provided msg.
+	 * @param {string} msg message
+	 */
+	const log = (msg) => {
+		if (dry) {
+			logger.info(msg);
+		} else {
+			logger.log(msg);
+		}
+	};
+	/** @typedef {{ type: "check" | "unlink" | "rmdir", filename: string, parent: { remaining: number, job: Job } | undefined }} Job */
+	/** @type {Job[]} */
+	const jobs = Array.from(diff.keys(), (filename) => ({
+		type: "check",
+		filename,
+		parent: undefined
+	}));
+	/** @type {Assets} */
+	const keptAssets = new Map();
+	processAsyncTree(
+		jobs,
+		10,
+		({ type, filename, parent }, push, callback) => {
+			const path = join(fs, outputPath, filename);
+			/**
+			 * Describes how this handle error operation behaves.
+			 * @param {Error & { code?: string }} err error
+			 * @returns {void}
+			 */
+			const handleError = (err) => {
+				const isAlreadyRemoved = () =>
+					new Promise((resolve) => {
+						if (err.code === "ENOENT") {
+							resolve(true);
+						} else if (err.code === "EPERM") {
+							// https://github.com/isaacs/rimraf/blob/main/src/fix-eperm.ts#L37
+							// fs.existsSync(path) === false https://github.com/webpack/webpack/actions/runs/15493412975/job/43624272783?pr=19586
+							doStat(fs, path, (err) => {
+								if (err) {
+									resolve(err.code === "ENOENT");
+								} else {
+									resolve(false);
+								}
+							});
+						} else {
+							resolve(false);
+						}
+					});
+
+				isAlreadyRemoved().then((isRemoved) => {
+					if (isRemoved) {
+						log(`${filename} was removed during cleaning by something else`);
+						handleParent();
+						return callback();
+					}
+					return callback(err);
+				});
+			};
+			const handleParent = () => {
+				if (parent && --parent.remaining === 0) push(parent.job);
+			};
+			switch (type) {
+				case "check":
+					if (isKept(filename)) {
+						keptAssets.set(filename, 0);
+						// do not decrement parent entry as we don't want to delete the parent
+						log(`${filename} will be kept`);
+						return process.nextTick(callback);
+					}
+					doStat(fs, path, (err, stats) => {
+						if (err) return handleError(err);
+						if (!(/** @type {IStats} */ (stats).isDirectory())) {
+							push({
+								type: "unlink",
+								filename,
+								parent
+							});
+							return callback();
+						}
+
+						/** @type {NonNullable} */
+						(fs.readdir)(path, (err, _entries) => {
+							if (err) return handleError(err);
+							/** @type {Job} */
+							const deleteJob = {
+								type: "rmdir",
+								filename,
+								parent
+							};
+							const entries = /** @type {string[]} */ (_entries);
+							if (entries.length === 0) {
+								push(deleteJob);
+							} else {
+								const parentToken = {
+									remaining: entries.length,
+									job: deleteJob
+								};
+								for (const entry of entries) {
+									const file = /** @type {string} */ (entry);
+									if (file.startsWith(".")) {
+										log(
+											`${filename} will be kept (dot-files will never be removed)`
+										);
+										continue;
+									}
+									push({
+										type: "check",
+										filename: `${filename}/${file}`,
+										parent: parentToken
+									});
+								}
+							}
+							return callback();
+						});
+					});
+					break;
+				case "rmdir":
+					log(`${filename} will be removed`);
+					if (dry) {
+						handleParent();
+						return process.nextTick(callback);
+					}
+					if (!fs.rmdir) {
+						logger.warn(
+							`${filename} can't be removed because output file system doesn't support removing directories (rmdir)`
+						);
+						return process.nextTick(callback);
+					}
+					fs.rmdir(path, (err) => {
+						if (err) return handleError(err);
+						handleParent();
+						callback();
+					});
+					break;
+				case "unlink":
+					log(`${filename} will be removed`);
+					if (dry) {
+						handleParent();
+						return process.nextTick(callback);
+					}
+					if (!fs.unlink) {
+						logger.warn(
+							`${filename} can't be removed because output file system doesn't support removing files (rmdir)`
+						);
+						return process.nextTick(callback);
+					}
+					fs.unlink(path, (err) => {
+						if (err) return handleError(err);
+						handleParent();
+						callback();
+					});
+					break;
+			}
+		},
+		(err) => {
+			if (err) return callback(err);
+			callback(undefined, keptAssets);
+		}
+	);
+};
+
+const PLUGIN_NAME = "CleanPlugin";
+
+class CleanPlugin {
+	/** @param {CleanOptions} options options */
+	constructor(options = {}) {
+		/** @type {CleanOptions} */
+		this.options = options;
+	}
+
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
+	apply(compiler) {
+		compiler.hooks.validate.tap(PLUGIN_NAME, () => {
+			compiler.validate(
+				() => {
+					const { definitions } = require("../schemas/WebpackOptions.json");
+
+					return {
+						definitions,
+						oneOf: [{ $ref: "#/definitions/CleanOptions" }]
+					};
+				},
+				this.options,
+				{
+					name: "Clean Plugin",
+					baseDataPath: "options"
+				}
+			);
+		});
+
+		const { keep } = this.options;
+
+		/** @type {boolean} */
+		const dry = this.options.dry || false;
+		/** @type {KeepFn} */
+		const keepFn =
+			typeof keep === "function"
+				? keep
+				: typeof keep === "string"
+					? (path) => path.startsWith(keep)
+					: typeof keep === "object" && keep.test
+						? (path) => keep.test(path)
+						: () => false;
+
+		// We assume that no external modification happens while the compiler is active
+		// So we can store the old assets and only diff to them to avoid fs access on
+		// incremental builds
+		/** @type {undefined | Assets} */
+		let oldAssets;
+
+		compiler.hooks.emit.tapAsync(
+			{
+				name: PLUGIN_NAME,
+				stage: 100
+			},
+			(compilation, callback) => {
+				const hooks = CleanPlugin.getCompilationHooks(compilation);
+				const logger = compilation.getLogger(`webpack.${PLUGIN_NAME}`);
+				const fs = /** @type {OutputFileSystem} */ (compiler.outputFileSystem);
+
+				if (!fs.readdir) {
+					return callback(
+						new Error(
+							`${PLUGIN_NAME}: Output filesystem doesn't support listing directories (readdir)`
+						)
+					);
+				}
+
+				/** @type {Assets} */
+				const currentAssets = new Map();
+				const now = Date.now();
+				for (const asset of Object.keys(compilation.assets)) {
+					if (ABSOLUTE_PATH_REGEXP.test(asset)) continue;
+					/** @type {string} */
+					let normalizedAsset;
+					let newNormalizedAsset = asset.replace(/\\/g, "/");
+					do {
+						normalizedAsset = newNormalizedAsset;
+						newNormalizedAsset = normalizedAsset.replace(
+							/(^|\/)(?!\.\.)[^/]+\/\.\.\//g,
+							"$1"
+						);
+					} while (newNormalizedAsset !== normalizedAsset);
+					if (normalizedAsset.startsWith("../")) continue;
+					const assetInfo = compilation.assetsInfo.get(asset);
+					if (assetInfo && assetInfo.hotModuleReplacement) {
+						currentAssets.set(normalizedAsset, now + _10sec);
+					} else {
+						currentAssets.set(normalizedAsset, 0);
+					}
+				}
+
+				const outputPath = compilation.getPath(compiler.outputPath, {});
+
+				/**
+				 * Checks whether this clean plugin is kept.
+				 * @param {string} path path
+				 * @returns {boolean | undefined} true, if needs to be kept
+				 */
+				const isKept = (path) => {
+					const result = hooks.keep.call(path);
+					if (result !== undefined) return result;
+					return keepFn(path);
+				};
+
+				/**
+				 * Processes the provided err.
+				 * @param {(Error | null)=} err err
+				 * @param {Diff=} diff diff
+				 */
+				const diffCallback = (err, diff) => {
+					if (err) {
+						oldAssets = undefined;
+						callback(err);
+						return;
+					}
+					applyDiff(
+						fs,
+						outputPath,
+						dry,
+						logger,
+						/** @type {Diff} */ (diff),
+						isKept,
+						(err, keptAssets) => {
+							if (err) {
+								oldAssets = undefined;
+							} else {
+								if (oldAssets) mergeAssets(currentAssets, oldAssets);
+								oldAssets = currentAssets;
+								if (keptAssets) mergeAssets(oldAssets, keptAssets);
+							}
+							callback(err);
+						}
+					);
+				};
+
+				if (oldAssets) {
+					diffCallback(null, getDiffToOldAssets(currentAssets, oldAssets));
+				} else {
+					getDiffToFs(fs, outputPath, currentAssets, diffCallback);
+				}
+			}
+		);
+	}
+}
+
+CleanPlugin.getCompilationHooks = createHooksRegistry(createCompilationHooks);
+
+module.exports = CleanPlugin;
diff --git a/lib/CodeGenerationResults.js b/lib/CodeGenerationResults.js
new file mode 100644
index 00000000000..cf5c787ce27
--- /dev/null
+++ b/lib/CodeGenerationResults.js
@@ -0,0 +1,186 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const { DEFAULTS } = require("./config/defaults");
+const { getOrInsert } = require("./util/MapHelpers");
+const { first } = require("./util/SetHelpers");
+const createHash = require("./util/createHash");
+const { RuntimeSpecMap, runtimeToString } = require("./util/runtime");
+
+/** @typedef {import("webpack-sources").Source} Source */
+/** @typedef {import("./Module")} Module */
+/** @typedef {import("./Module").SourceType} SourceType */
+/** @typedef {import("./Module").CodeGenerationResult} CodeGenerationResult */
+/** @typedef {import("./Module").CodeGenerationResultData} CodeGenerationResultData */
+/** @typedef {import("./Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */
+/** @typedef {import("./util/Hash").HashFunction} HashFunction */
+/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */
+
+/**
+ * Stores code generation results keyed by module and runtime so later stages
+ * can retrieve emitted sources, metadata, and derived hashes.
+ */
+class CodeGenerationResults {
+	/**
+	 * Initializes an empty result store and remembers which hash function should
+	 * be used when a result hash needs to be derived lazily.
+	 * @param {HashFunction} hashFunction the hash function to use
+	 */
+	constructor(hashFunction = DEFAULTS.HASH_FUNCTION) {
+		/** @type {Map>} */
+		this.map = new Map();
+		/** @type {HashFunction} */
+		this._hashFunction = hashFunction;
+	}
+
+	/**
+	 * Returns the code generation result for a module/runtime pair, rejecting
+	 * ambiguous lookups where no unique runtime-independent result exists.
+	 * @param {Module} module the module
+	 * @param {RuntimeSpec} runtime runtime(s)
+	 * @returns {CodeGenerationResult} the CodeGenerationResult
+	 */
+	get(module, runtime) {
+		const entry = this.map.get(module);
+		if (entry === undefined) {
+			throw new Error(
+				`No code generation entry for ${module.identifier()} (existing entries: ${Array.from(
+					this.map.keys(),
+					(m) => m.identifier()
+				).join(", ")})`
+			);
+		}
+		if (runtime === undefined) {
+			if (entry.size > 1) {
+				const results = new Set(entry.values());
+				if (results.size !== 1) {
+					throw new Error(
+						`No unique code generation entry for unspecified runtime for ${module.identifier()} (existing runtimes: ${Array.from(
+							entry.keys(),
+							(r) => runtimeToString(r)
+						).join(", ")}).
+Caller might not support runtime-dependent code generation (opt-out via optimization.usedExports: "global").`
+					);
+				}
+				return /** @type {CodeGenerationResult} */ (first(results));
+			}
+			return /** @type {CodeGenerationResult} */ (entry.values().next().value);
+		}
+		const result = entry.get(runtime);
+		if (result === undefined) {
+			throw new Error(
+				`No code generation entry for runtime ${runtimeToString(
+					runtime
+				)} for ${module.identifier()} (existing runtimes: ${Array.from(
+					entry.keys(),
+					(r) => runtimeToString(r)
+				).join(", ")})`
+			);
+		}
+		return result;
+	}
+
+	/**
+	 * Reports whether a module has a stored result for the requested runtime, or
+	 * a single unambiguous result when no runtime is specified.
+	 * @param {Module} module the module
+	 * @param {RuntimeSpec} runtime runtime(s)
+	 * @returns {boolean} true, when we have data for this
+	 */
+	has(module, runtime) {
+		const entry = this.map.get(module);
+		if (entry === undefined) {
+			return false;
+		}
+		if (runtime !== undefined) {
+			return entry.has(runtime);
+		} else if (entry.size > 1) {
+			const results = new Set(entry.values());
+			return results.size === 1;
+		}
+		return entry.size === 1;
+	}
+
+	/**
+	 * Returns a generated source of the requested source type from a stored code
+	 * generation result.
+	 * @param {Module} module the module
+	 * @param {RuntimeSpec} runtime runtime(s)
+	 * @param {SourceType} sourceType the source type
+	 * @returns {Source} a source
+	 */
+	getSource(module, runtime, sourceType) {
+		return /** @type {Source} */ (
+			this.get(module, runtime).sources.get(sourceType)
+		);
+	}
+
+	/**
+	 * Returns the runtime requirements captured during code generation for the
+	 * requested module/runtime pair.
+	 * @param {Module} module the module
+	 * @param {RuntimeSpec} runtime runtime(s)
+	 * @returns {ReadOnlyRuntimeRequirements | null} runtime requirements
+	 */
+	getRuntimeRequirements(module, runtime) {
+		return this.get(module, runtime).runtimeRequirements;
+	}
+
+	/**
+	 * Returns an arbitrary metadata entry recorded during code generation.
+	 * @param {Module} module the module
+	 * @param {RuntimeSpec} runtime runtime(s)
+	 * @param {string} key data key
+	 * @returns {ReturnType} data generated by code generation
+	 */
+	getData(module, runtime, key) {
+		const data = this.get(module, runtime).data;
+		return data === undefined ? undefined : data.get(key);
+	}
+
+	/**
+	 * Returns a stable hash for the generated sources and runtime requirements,
+	 * computing and caching it on first access.
+	 * @param {Module} module the module
+	 * @param {RuntimeSpec} runtime runtime(s)
+	 * @returns {string} hash of the code generation
+	 */
+	getHash(module, runtime) {
+		const info = this.get(module, runtime);
+		if (info.hash !== undefined) return info.hash;
+		const hash = createHash(this._hashFunction);
+		for (const [type, source] of info.sources) {
+			hash.update(type);
+			source.updateHash(hash);
+		}
+		if (info.runtimeRequirements) {
+			for (const rr of info.runtimeRequirements) hash.update(rr);
+		}
+		return (info.hash = hash.digest("hex"));
+	}
+
+	/**
+	 * Stores a code generation result for a module/runtime pair, creating the
+	 * per-module runtime map when needed.
+	 * @param {Module} module the module
+	 * @param {RuntimeSpec} runtime runtime(s)
+	 * @param {CodeGenerationResult} result result from module
+	 * @returns {void}
+	 */
+	add(module, runtime, result) {
+		const map = getOrInsert(
+			this.map,
+			module,
+			() =>
+				/** @type {RuntimeSpecMap} */
+				new RuntimeSpecMap()
+		);
+		map.set(runtime, result);
+	}
+}
+
+module.exports = CodeGenerationResults;
diff --git a/lib/CommentCompilationWarning.js b/lib/CommentCompilationWarning.js
deleted file mode 100644
index 4b1e6de5118..00000000000
--- a/lib/CommentCompilationWarning.js
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const WebpackError = require("./WebpackError");
-
-/** @typedef {import("./Module.js")} Module */
-
-/** @typedef {import("./Dependency.js").Loc} Loc */
-
-class CommentCompilationWarning extends WebpackError {
-	/**
-	 *
-	 * @param {string} message warning message
-	 * @param {Module} module affected module
-	 * @param {Loc} loc affected lines of code
-	 */
-	constructor(message, module, loc) {
-		super(message);
-
-		this.name = "CommentCompilationWarning";
-
-		this.module = module;
-		this.loc = loc;
-
-		Error.captureStackTrace(this, this.constructor);
-	}
-}
-
-module.exports = CommentCompilationWarning;
diff --git a/lib/CompatibilityPlugin.js b/lib/CompatibilityPlugin.js
index 5d2680245bb..afe175ea2d6 100644
--- a/lib/CompatibilityPlugin.js
+++ b/lib/CompatibilityPlugin.js
@@ -2,69 +2,287 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
+const {
+	JAVASCRIPT_MODULE_TYPE_AUTO,
+	JAVASCRIPT_MODULE_TYPE_DYNAMIC,
+	JAVASCRIPT_MODULE_TYPE_ESM
+} = require("./ModuleTypeConstants");
+const RuntimeGlobals = require("./RuntimeGlobals");
 const ConstDependency = require("./dependencies/ConstDependency");
 
-const NullFactory = require("./NullFactory");
+/** @typedef {import("estree").CallExpression} CallExpression */
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */
+/** @typedef {import("./dependencies/ContextDependency")} ContextDependency */
+/** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */
+/** @typedef {import("./javascript/JavascriptParser").Range} Range */
+
+/**
+ * Captures the source range of a renamed compatibility binding so it can be
+ * rewritten exactly once.
+ * @typedef {object} CompatibilitySettingsDeclaration
+ * @property {boolean} updated
+ * @property {DependencyLocation} loc
+ * @property {Range} range
+ */
+
+/**
+ * Stores the replacement variable name and the declaration metadata tracked
+ * for a compatibility rewrite.
+ * @typedef {object} CompatibilitySettings
+ * @property {string} name
+ * @property {CompatibilitySettingsDeclaration} declaration
+ */
 
-/** @typedef {import("./Compiler.js")} Compiler */
+const nestedWebpackIdentifierTag = Symbol("nested webpack identifier");
+const PLUGIN_NAME = "CompatibilityPlugin";
 
+/**
+ * Adds parser-time compatibility rewrites for legacy runtime patterns that
+ * webpack still needs to recognize in user and generated code.
+ */
 class CompatibilityPlugin {
 	/**
-	 * Apply the plugin
-	 * @param {Compiler} compiler Webpack Compiler
+	 * Installs parser hooks that preserve compatibility with legacy patterns
+	 * such as nested `__webpack_require__` bindings and hashbang handling.
+	 * @param {Compiler} compiler the compiler instance
 	 * @returns {void}
 	 */
 	apply(compiler) {
 		compiler.hooks.compilation.tap(
-			"CompatibilityPlugin",
+			PLUGIN_NAME,
 			(compilation, { normalModuleFactory }) => {
-				compilation.dependencyFactories.set(ConstDependency, new NullFactory());
 				compilation.dependencyTemplates.set(
 					ConstDependency,
 					new ConstDependency.Template()
 				);
 
 				normalModuleFactory.hooks.parser
-					.for("javascript/auto")
-					.tap("CompatibilityPlugin", (parser, parserOptions) => {
+					.for(JAVASCRIPT_MODULE_TYPE_AUTO)
+					.tap(PLUGIN_NAME, (parser, parserOptions) => {
 						if (
-							typeof parserOptions.browserify !== "undefined" &&
+							parserOptions.browserify !== undefined &&
 							!parserOptions.browserify
-						)
+						) {
 							return;
+						}
 
-						parser.hooks.call
-							.for("require")
-							.tap("CompatibilityPlugin", expr => {
+						parser.hooks.call.for("require").tap(
+							PLUGIN_NAME,
+							/**
+							 * Rewrites browserify-style delegated `require` calls into a
+							 * plain webpack require reference and removes the synthetic
+							 * context dependency created for the delegator pattern.
+							 * @param {CallExpression} expr call expression
+							 * @returns {boolean | void} true when need to handle
+							 */
+							(expr) => {
 								// support for browserify style require delegator: "require(o, !0)"
 								if (expr.arguments.length !== 2) return;
 								const second = parser.evaluateExpression(expr.arguments[1]);
 								if (!second.isBoolean()) return;
 								if (second.asBool() !== true) return;
-								const dep = new ConstDependency("require", expr.callee.range);
-								dep.loc = expr.loc;
-								if (parser.state.current.dependencies.length > 1) {
+								const dep = new ConstDependency(
+									"require",
+									/** @type {Range} */ (expr.callee.range)
+								);
+								dep.loc = parser.getLocation(expr);
+								if (parser.state.current.dependencies.length > 0) {
 									const last =
-										parser.state.current.dependencies[
-											parser.state.current.dependencies.length - 1
-										];
+										/** @type {ContextDependency} */
+										(
+											parser.state.current.dependencies[
+												parser.state.current.dependencies.length - 1
+											]
+										);
 									if (
 										last.critical &&
 										last.options &&
 										last.options.request === "." &&
 										last.userRequest === "." &&
 										last.options.recursive
-									)
+									) {
 										parser.state.current.dependencies.pop();
+									}
 								}
-								parser.state.current.addDependency(dep);
+								parser.state.module.addPresentationalDependency(dep);
+								return true;
+							}
+						);
+					});
+
+				/**
+				 * Attaches the compatibility rewrites for a JavaScript parser
+				 * instance.
+				 * @param {JavascriptParser} parser the parser
+				 * @returns {void}
+				 */
+				const handler = (parser) => {
+					// Handle nested requires
+					parser.hooks.preStatementByType
+						.for("FunctionDeclaration")
+						.tap(PLUGIN_NAME, (statement) => {
+							if (
+								statement.type === "FunctionDeclaration" &&
+								statement.id &&
+								statement.id.name === RuntimeGlobals.require
+							) {
+								const newName = `__nested_webpack_require_${
+									/** @type {Range} */
+									(statement.range)[0]
+								}__`;
+								parser.tagVariable(
+									statement.id.name,
+									nestedWebpackIdentifierTag,
+									{
+										name: newName,
+										declaration: {
+											updated: false,
+											loc: parser.getLocation(statement.id),
+											range: /** @type {Range} */ (statement.id.range)
+										}
+									}
+								);
 								return true;
+							}
+						});
+					parser.hooks.pattern
+						.for(RuntimeGlobals.require)
+						.tap(PLUGIN_NAME, (pattern) => {
+							const newName = `__nested_webpack_require_${
+								/** @type {Range} */ (pattern.range)[0]
+							}__`;
+							parser.tagVariable(pattern.name, nestedWebpackIdentifierTag, {
+								name: newName,
+								declaration: {
+									updated: false,
+									loc: parser.getLocation(pattern),
+									range: /** @type {Range} */ (pattern.range)
+								}
+							});
+							if (parser.scope.topLevelScope !== true) {
+								return true;
+							}
+						});
+					parser.hooks.pattern
+						.for(RuntimeGlobals.exports)
+						.tap(PLUGIN_NAME, (pattern) => {
+							const newName = "__nested_webpack_exports__";
+							parser.tagVariable(pattern.name, nestedWebpackIdentifierTag, {
+								name: newName,
+								declaration: {
+									updated: false,
+									loc: parser.getLocation(pattern),
+									range: /** @type {Range} */ (pattern.range)
+								}
+							});
+							return true;
+						});
+					// `const`/`let` are block-pre-walked, by which point the name is no
+					// longer free and `hooks.pattern` is not dispatched for it — tag
+					// them through the kind-specific declaration hooks instead.
+					// Returning nothing lets the variable be defined, keeping the tag.
+					for (const hookMap of [
+						parser.hooks.varDeclarationConst,
+						parser.hooks.varDeclarationLet
+					]) {
+						hookMap.for(RuntimeGlobals.require).tap(PLUGIN_NAME, (ident) => {
+							parser.tagVariable(ident.name, nestedWebpackIdentifierTag, {
+								name: `__nested_webpack_require_${
+									/** @type {Range} */ (ident.range)[0]
+								}__`,
+								declaration: {
+									updated: false,
+									loc: parser.getLocation(ident),
+									range: /** @type {Range} */ (ident.range)
+								}
+							});
+						});
+						hookMap.for(RuntimeGlobals.exports).tap(PLUGIN_NAME, (ident) => {
+							parser.tagVariable(ident.name, nestedWebpackIdentifierTag, {
+								name: "__nested_webpack_exports__",
+								declaration: {
+									updated: false,
+									loc: parser.getLocation(ident),
+									range: /** @type {Range} */ (ident.range)
+								}
 							});
+						});
+					}
+					// Update single `var __webpack_require__ = {};` and `var __webpack_exports__ = {};` without expression
+					parser.hooks.declarator.tap(PLUGIN_NAME, (declarator) => {
+						if (
+							declarator.id.type === "Identifier" &&
+							(declarator.id.name === RuntimeGlobals.exports ||
+								declarator.id.name === RuntimeGlobals.require)
+						) {
+							const tagData = /** @type {CompatibilitySettings | undefined} */ (
+								parser.getTagData(
+									declarator.id.name,
+									nestedWebpackIdentifierTag
+								)
+							);
+							if (!tagData) return;
+							const { name, declaration } = tagData;
+							if (!declaration.updated) {
+								const dep = new ConstDependency(name, declaration.range);
+								dep.loc = declaration.loc;
+								parser.state.module.addPresentationalDependency(dep);
+								declaration.updated = true;
+							}
+						}
+					});
+					parser.hooks.expression
+						.for(nestedWebpackIdentifierTag)
+						.tap(PLUGIN_NAME, (expr) => {
+							const { name, declaration } =
+								/** @type {CompatibilitySettings} */
+								(parser.currentTagData);
+							if (!declaration.updated) {
+								const dep = new ConstDependency(name, declaration.range);
+								dep.loc = declaration.loc;
+								parser.state.module.addPresentationalDependency(dep);
+								declaration.updated = true;
+							}
+							const dep = new ConstDependency(
+								name,
+								/** @type {Range} */ (expr.range)
+							);
+							dep.loc = parser.getLocation(expr);
+							parser.state.module.addPresentationalDependency(dep);
+							return true;
+						});
+
+					// Handle hashbang
+					parser.hooks.program.tap(PLUGIN_NAME, (program, comments) => {
+						if (comments.length === 0) return;
+						const c = comments[0];
+						if (c.type === "Line" && /** @type {Range} */ (c.range)[0] === 0) {
+							if (parser.state.source.slice(0, 2).toString() !== "#!") return;
+							// this is a hashbang comment
+							const dep = new ConstDependency("//", 0);
+							dep.loc = parser.getLocation(c);
+							parser.state.module.addPresentationalDependency(dep);
+						}
 					});
+				};
+
+				normalModuleFactory.hooks.parser
+					.for(JAVASCRIPT_MODULE_TYPE_AUTO)
+					.tap(PLUGIN_NAME, handler);
+				normalModuleFactory.hooks.parser
+					.for(JAVASCRIPT_MODULE_TYPE_DYNAMIC)
+					.tap(PLUGIN_NAME, handler);
+				normalModuleFactory.hooks.parser
+					.for(JAVASCRIPT_MODULE_TYPE_ESM)
+					.tap(PLUGIN_NAME, handler);
 			}
 		);
 	}
 }
+
 module.exports = CompatibilityPlugin;
+module.exports.nestedWebpackIdentifierTag = nestedWebpackIdentifierTag;
diff --git a/lib/Compilation.js b/lib/Compilation.js
index 1fd02ed4f6f..6ef8db5713c 100644
--- a/lib/Compilation.js
+++ b/lib/Compilation.js
@@ -1,1031 +1,4483 @@
 /*
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
-	*/
+*/
+
 "use strict";
 
-const asyncLib = require("neo-async");
 const util = require("util");
-const { CachedSource } = require("webpack-sources");
+const asyncLib = require("neo-async");
 const {
-	Tapable,
-	SyncHook,
+	AsyncParallelHook,
+	AsyncSeriesBailHook,
+	AsyncSeriesHook,
+	HookMap,
 	SyncBailHook,
-	SyncWaterfallHook,
-	AsyncSeriesHook
+	SyncHook,
+	SyncWaterfallHook
 } = require("tapable");
-const EntryModuleNotFoundError = require("./EntryModuleNotFoundError");
-const ModuleNotFoundError = require("./ModuleNotFoundError");
-const ModuleDependencyWarning = require("./ModuleDependencyWarning");
-const ModuleDependencyError = require("./ModuleDependencyError");
-const ChunkGroup = require("./ChunkGroup");
+const { CachedSource } = require("webpack-sources");
+const { MultiItemCache } = require("./CacheFacade");
 const Chunk = require("./Chunk");
+const ChunkGraph = require("./ChunkGraph");
+const ChunkGroup = require("./ChunkGroup");
+const ChunkTemplate = require("./ChunkTemplate");
+const CodeGenerationResults = require("./CodeGenerationResults");
+const Dependency = require("./Dependency");
+const DependencyTemplates = require("./DependencyTemplates");
 const Entrypoint = require("./Entrypoint");
+const ErrorHelpers = require("./ErrorHelpers");
+const FileSystemInfo = require("./FileSystemInfo");
+const LazyBarrelController = require("./LazyBarrel");
 const MainTemplate = require("./MainTemplate");
-const ChunkTemplate = require("./ChunkTemplate");
-const HotUpdateChunkTemplate = require("./HotUpdateChunkTemplate");
+const Module = require("./Module");
+const ModuleGraph = require("./ModuleGraph");
+const ModuleProfile = require("./ModuleProfile");
 const ModuleTemplate = require("./ModuleTemplate");
+const { WEBPACK_MODULE_TYPE_RUNTIME } = require("./ModuleTypeConstants");
+const RuntimeGlobals = require("./RuntimeGlobals");
 const RuntimeTemplate = require("./RuntimeTemplate");
-const Dependency = require("./Dependency");
-const ChunkRenderError = require("./ChunkRenderError");
-const AsyncDependencyToInitialChunkError = require("./AsyncDependencyToInitialChunkError");
 const Stats = require("./Stats");
-const Semaphore = require("./util/Semaphore");
+const buildChunkGraph = require("./buildChunkGraph");
+const {
+	makeWebpackError,
+	tryRunOrWebpackError
+} = require("./errors/HookWebpackError");
+const WebpackError = require("./errors/WebpackError");
+const { LogType, Logger } = require("./logging/Logger");
+const StatsFactory = require("./stats/StatsFactory");
+const StatsPrinter = require("./stats/StatsPrinter");
+const { equals: arrayEquals } = require("./util/ArrayHelpers");
+const AsyncQueue = require("./util/AsyncQueue");
+const LazySet = require("./util/LazySet");
+const { getOrInsert } = require("./util/MapHelpers");
+const WeakTupleMap = require("./util/WeakTupleMap");
+const { cachedCleverMerge } = require("./util/cleverMerge");
+const {
+	compareErrors,
+	compareIds,
+	compareModulesByIdentifier,
+	compareSelect,
+	concatComparators
+} = require("./util/comparators");
 const createHash = require("./util/createHash");
-const Queue = require("./util/Queue");
-const SortableSet = require("./util/SortableSet");
-const GraphHelpers = require("./GraphHelpers");
-
-const byId = (a, b) => {
-	if (a.id < b.id) return -1;
-	if (a.id > b.id) return 1;
-	return 0;
-};
-
-const byIdOrIdentifier = (a, b) => {
-	if (a.id < b.id) return -1;
-	if (a.id > b.id) return 1;
-	const identA = a.identifier();
-	const identB = b.identifier();
-	if (identA < identB) return -1;
-	if (identA > identB) return 1;
-	return 0;
-};
-
-const byIndexOrIdentifier = (a, b) => {
-	if (a.index < b.index) return -1;
-	if (a.index > b.index) return 1;
-	const identA = a.identifier();
-	const identB = b.identifier();
-	if (identA < identB) return -1;
-	if (identA > identB) return 1;
-	return 0;
-};
-
-const byNameOrHash = (a, b) => {
-	if (a.name < b.name) return -1;
-	if (a.name > b.name) return 1;
-	if (a.fullHash < b.fullHash) return -1;
-	if (a.fullhash > b.fullHash) return 1;
-	return 0;
-};
-
-const iterationBlockVariable = (variables, fn) => {
-	for (
-		let indexVariable = 0;
-		indexVariable < variables.length;
-		indexVariable++
-	) {
-		const varDep = variables[indexVariable].dependencies;
-		for (let indexVDep = 0; indexVDep < varDep.length; indexVDep++) {
-			fn(varDep[indexVDep]);
+const {
+	arrayToSetDeprecation,
+	createFakeHook,
+	soonFrozenObjectDeprecation
+} = require("./util/deprecation");
+const memoize = require("./util/memoize");
+const processAsyncTree = require("./util/processAsyncTree");
+const { getRuntimeKey } = require("./util/runtime");
+const { isSourceEqual } = require("./util/source");
+
+const getModuleStoreError = memoize(() => require("./errors/ModuleStoreError"));
+
+const getModuleRestoreError = memoize(() =>
+	require("./errors/ModuleRestoreError")
+);
+
+const getModuleNotFoundError = memoize(() =>
+	require("./errors/ModuleNotFoundError")
+);
+
+const getModuleHashingError = memoize(() =>
+	require("./errors/ModuleHashingError")
+);
+
+const getModuleDependencyWarning = memoize(() =>
+	require("./errors/ModuleDependencyWarning")
+);
+
+const getModuleDependencyError = memoize(() =>
+	require("./errors/ModuleDependencyError")
+);
+
+const getCodeGenerationError = memoize(() =>
+	require("./errors/CodeGenerationError")
+);
+
+const getChunkRenderError = memoize(() => require("./errors/ChunkRenderError"));
+
+const getBuildCycleError = memoize(() => require("./errors/BuildCycleError"));
+
+/** @typedef {import("webpack-sources").Source} Source */
+/** @typedef {import("../declarations/WebpackOptions").OutputNormalized} OutputOptions */
+/** @typedef {import("../declarations/WebpackOptions").HashFunction} HashFunction */
+/** @typedef {import("../declarations/WebpackOptions").HashDigest} HashDigest */
+/** @typedef {import("../declarations/WebpackOptions").HashDigestLength} HashDigestLength */
+/** @typedef {import("../declarations/WebpackOptions").StatsOptions} StatsOptions */
+/** @typedef {import("../declarations/WebpackOptions").Plugins} Plugins */
+/** @typedef {import("./config/defaults").WebpackOptionsNormalizedWithDefaults} WebpackOptions */
+/** @typedef {import("./config/defaults").OutputNormalizedWithDefaults} OutputOptionsWithDefaults */
+/** @typedef {import("./AsyncDependenciesBlock")} AsyncDependenciesBlock */
+/** @typedef {import("./Cache")} Cache */
+/** @typedef {import("./CacheFacade")} CacheFacade */
+/** @typedef {import("./Chunk").ChunkName} ChunkName */
+/** @typedef {import("./Chunk").ChunkId} ChunkId */
+/** @typedef {import("./ChunkGroup").ChunkGroupOptions} ChunkGroupOptions */
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./Compiler").CompilationParams} CompilationParams */
+/** @typedef {import("./Compiler").MemCache} MemCache */
+/** @typedef {import("./Compiler").WeakReferences} WeakReferences */
+/** @typedef {import("./Compiler").ModuleMemCachesItem} ModuleMemCachesItem */
+/** @typedef {import("./Compiler").Records} Records */
+/** @typedef {import("./DependenciesBlock")} DependenciesBlock */
+/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */
+/** @typedef {import("./Dependency").ReferencedExports} ReferencedExports */
+/** @typedef {import("./Entrypoint").EntryOptions} EntryOptions */
+/** @typedef {import("./Module").NameForCondition} NameForCondition */
+/** @typedef {import("./Module").BuildInfo} BuildInfo */
+/** @typedef {import("./Module").ValueCacheVersions} ValueCacheVersions */
+/** @typedef {import("./Module").RuntimeRequirements} RuntimeRequirements */
+/** @typedef {import("./Module").SourceTypes} SourceTypes */
+/** @typedef {import("./NormalModule").NormalModuleCompilationHooks} NormalModuleCompilationHooks */
+/** @typedef {import("./Module").FactoryMeta} FactoryMeta */
+/** @typedef {import("./Module").CodeGenerationResult} CodeGenerationResult */
+/** @typedef {import("./ModuleFactory")} ModuleFactory */
+/** @typedef {import("../declarations/WebpackOptions").ResolveOptions} ResolveOptions */
+/** @typedef {import("./ChunkGraph").ModuleId} ModuleId */
+/** @typedef {import("./ModuleGraphConnection")} ModuleGraphConnection */
+/** @typedef {import("./ModuleFactory").ModuleFactoryCreateDataContextInfo} ModuleFactoryCreateDataContextInfo */
+/** @typedef {import("./ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */
+/** @typedef {import("./NormalModule")} NormalModule */
+/** @typedef {import("./NormalModule").AnyLoaderContext} AnyLoaderContext */
+/** @typedef {import("./NormalModule").ParserOptions} ParserOptions */
+/** @typedef {import("./NormalModule").GeneratorOptions} GeneratorOptions */
+/** @typedef {import("./RequestShortener")} RequestShortener */
+/** @typedef {import("./RuntimeModule")} RuntimeModule */
+/** @typedef {import("./Template").RenderManifestEntry} RenderManifestEntry */
+/** @typedef {import("./Template").RenderManifestOptions} RenderManifestOptions */
+/** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsAsset} StatsAsset */
+/** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsError} StatsError */
+/** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsModule} StatsModule */
+/** @typedef {import("./TemplatedPathPlugin").TemplatePath} TemplatePath */
+/** @typedef {import("./util/Hash")} Hash */
+
+/**
+ * Defines the shared type used by this module.
+ * @template T
+ * @typedef {import("tapable").AsArray} AsArray
+ */
+
+/**
+ * Defines the shared type used by this module.
+ * @template T
+ * @typedef {import("./util/deprecation").FakeHook} FakeHook
+ */
+/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */
+/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
+
+/**
+ * Defines the callback callback.
+ * @callback Callback
+ * @param {(WebpackError | null)=} err
+ * @returns {void}
+ */
+
+/**
+ * Defines the module callback callback.
+ * @callback ModuleCallback
+ * @param {WebpackError | null=} err
+ * @param {Module | null=} result
+ * @returns {void}
+ */
+
+/**
+ * Defines the module factory result callback callback.
+ * @callback ModuleFactoryResultCallback
+ * @param {WebpackError | null=} err
+ * @param {ModuleFactoryResult | null=} result
+ * @returns {void}
+ */
+
+/**
+ * Defines the module or module factory result callback callback.
+ * @callback ModuleOrModuleFactoryResultCallback
+ * @param {WebpackError | null=} err
+ * @param {Module | ModuleFactoryResult | null=} result
+ * @returns {void}
+ */
+
+/**
+ * Defines the execute module callback callback.
+ * @callback ExecuteModuleCallback
+ * @param {WebpackError | null=} err
+ * @param {ExecuteModuleResult | null=} result
+ * @returns {void}
+ */
+
+/** @typedef {new (...args: EXPECTED_ANY[]) => Dependency} DependencyConstructor */
+
+/** @typedef {Record} CompilationAssets */
+
+/**
+ * Defines the available modules chunk group mapping type used by this module.
+ * @typedef {object} AvailableModulesChunkGroupMapping
+ * @property {ChunkGroup} chunkGroup
+ * @property {Set} availableModules
+ * @property {boolean} needCopy
+ */
+
+/**
+ * Defines the dependencies block like type used by this module.
+ * @typedef {object} DependenciesBlockLike
+ * @property {Dependency[]} dependencies
+ * @property {AsyncDependenciesBlock[]} blocks
+ */
+
+/** @typedef {Set} Chunks */
+
+/**
+ * Defines the chunk path data type used by this module.
+ * @typedef {object} ChunkPathData
+ * @property {string | number} id
+ * @property {string=} name
+ * @property {string} hash
+ * @property {HashWithLengthFunction=} hashWithLength
+ * @property {HashWithDigestFunction=} hashWithDigest builds `[chunkhash:]` per chunk in the runtime chunk-filename map
+ * @property {(Record)=} contentHash
+ * @property {(Record)=} contentHashWithLength
+ * @property {(Record)=} contentHashWithDigest builds `[contenthash:]` per chunk in the runtime chunk-filename map
+ */
+
+/**
+ * Defines the chunk hash context type used by this module.
+ * @typedef {object} ChunkHashContext
+ * @property {CodeGenerationResults} codeGenerationResults results of code generation
+ * @property {RuntimeTemplate} runtimeTemplate the runtime template
+ * @property {ModuleGraph} moduleGraph the module graph
+ * @property {ChunkGraph} chunkGraph the chunk graph
+ */
+
+/**
+ * Defines the runtime requirements context type used by this module.
+ * @typedef {object} RuntimeRequirementsContext
+ * @property {ChunkGraph} chunkGraph the chunk graph
+ * @property {CodeGenerationResults} codeGenerationResults the code generation results
+ */
+
+/**
+ * Defines the execute module options type used by this module.
+ * @typedef {object} ExecuteModuleOptions
+ * @property {EntryOptions=} entryOptions
+ */
+
+/** @typedef {LazySet} FileSystemDependencies */
+
+/** @typedef {EXPECTED_ANY} ExecuteModuleExports */
+
+/**
+ * Defines the execute module result type used by this module.
+ * @typedef {object} ExecuteModuleResult
+ * @property {ExecuteModuleExports} exports
+ * @property {boolean} cacheable
+ * @property {string[]} notCacheableReasons reasons why the executed modules are not cacheable
+ * @property {ExecuteModuleAssets} assets
+ * @property {FileSystemDependencies} fileDependencies
+ * @property {FileSystemDependencies} contextDependencies
+ * @property {FileSystemDependencies} missingDependencies
+ * @property {FileSystemDependencies} buildDependencies
+ */
+
+/**
+ * Defines the execute module object type used by this module.
+ * @typedef {object} ExecuteModuleObject
+ * @property {string=} id module id
+ * @property {ExecuteModuleExports} exports exports
+ * @property {boolean} loaded is loaded
+ * @property {Error=} error error
+ */
+
+/**
+ * Defines the execute module argument type used by this module.
+ * @typedef {object} ExecuteModuleArgument
+ * @property {Module} module
+ * @property {ExecuteModuleObject=} moduleObject
+ * @property {CodeGenerationResult} codeGenerationResult
+ */
+
+/** @typedef {((id: string) => ExecuteModuleExports) & { i?: ((options: ExecuteOptions) => void)[], c?: Record, p?: string }} WebpackRequire */
+
+/**
+ * Defines the execute options type used by this module.
+ * @typedef {object} ExecuteOptions
+ * @property {string=} id module id
+ * @property {ExecuteModuleObject} module module
+ * @property {WebpackRequire} require require function
+ */
+
+/** @typedef {Map} ExecuteModuleAssets */
+
+/**
+ * Defines the execute module context type used by this module.
+ * @typedef {object} ExecuteModuleContext
+ * @property {ExecuteModuleAssets} assets
+ * @property {Chunk} chunk
+ * @property {ChunkGraph} chunkGraph
+ * @property {WebpackRequire=} __webpack_require__
+ */
+
+/**
+ * Defines the entry data type used by this module.
+ * @typedef {object} EntryData
+ * @property {Dependency[]} dependencies dependencies of the entrypoint that should be evaluated at startup
+ * @property {Dependency[]} includeDependencies dependencies of the entrypoint that should be included but not evaluated
+ * @property {EntryOptions} options options of the entrypoint
+ */
+
+/**
+ * Defines the log entry type used by this module.
+ * @typedef {object} LogEntry
+ * @property {keyof LogType} type
+ * @property {EXPECTED_ANY[]=} args
+ * @property {number} time
+ * @property {string[]=} trace
+ */
+
+/**
+ * Defines the known asset info type used by this module.
+ * @typedef {object} KnownAssetInfo
+ * @property {boolean=} immutable true, if the asset can be long term cached forever (contains a hash)
+ * @property {boolean=} minimized whether the asset is minimized
+ * @property {string | string[]=} fullhash the value(s) of the full hash used for this asset
+ * @property {string | string[]=} chunkhash the value(s) of the chunk hash used for this asset
+ * @property {string | string[]=} modulehash the value(s) of the module hash used for this asset
+ * @property {string | string[]=} contenthash the value(s) of the content hash used for this asset
+ * @property {Record=} contenthashDigest maps a `[contenthash:]` value to its digest, so `RealContentHashPlugin` re-encodes the recomputed hash in it
+ * @property {string=} sourceFilename when asset was created from a source file (potentially transformed), the original filename relative to compilation context
+ * @property {number=} size size in bytes, only set after asset has been emitted
+ * @property {boolean=} development true, when asset is only used for development and doesn't count towards user-facing assets
+ * @property {boolean=} hotModuleReplacement true, when asset ships data for updating an existing application (HMR)
+ * @property {boolean=} javascriptModule true, when asset is javascript and an ESM
+ * @property {boolean=} manifest true, when file is a manifest
+ * @property {Record=} related object of pointers to other assets, keyed by type of relation (only points from parent to child)
+ */
+
+/** @typedef {KnownAssetInfo & Record} AssetInfo */
+
+/** @typedef {{ path: string, info: AssetInfo }} InterpolatedPathAndAssetInfo */
+
+/**
+ * Defines the asset type used by this module.
+ * @typedef {object} Asset
+ * @property {string} name the filename of the asset
+ * @property {Source} source source of the asset
+ * @property {AssetInfo} info info about the asset
+ */
+
+/** @typedef {(length: number) => string} HashWithLengthFunction */
+
+/** @typedef {(digest: string, length?: number) => string} HashWithDigestFunction */
+
+/**
+ * Defines the module path data type used by this module.
+ * @typedef {object} ModulePathData
+ * @property {string | number} id
+ * @property {string} hash
+ * @property {HashWithLengthFunction=} hashWithLength
+ */
+
+/** @typedef {(id: string | number) => string | number} PrepareIdFunction */
+
+/**
+ * Defines the path data type used by this module.
+ * @typedef {object} PathData
+ * @property {ChunkGraph=} chunkGraph
+ * @property {string=} hash
+ * @property {string=} fullHash untruncated compilation hash, for re-encoding `[fullhash:]`
+ * @property {string=} fullHashDigest digest `fullHash` is encoded in (defaults to `hashDigest`)
+ * @property {HashWithLengthFunction=} hashWithLength
+ * @property {HashWithDigestFunction=} hashWithDigest builds `[fullhash:]`/`[hash:]` in the runtime chunk-filename context, where it throws because a runtime `getFullHash()` expression cannot be re-encoded
+ * @property {(Chunk | ChunkPathData)=} chunk
+ * @property {(Module | ModulePathData)=} module
+ * @property {RuntimeSpec=} runtime
+ * @property {string=} filename
+ * @property {string=} basename
+ * @property {string=} query
+ * @property {string=} contentHashType
+ * @property {string=} contentHash
+ * @property {string=} contentHashFull untruncated module/asset content hash, for re-encoding `[contenthash:]` from full entropy
+ * @property {HashWithLengthFunction=} contentHashWithLength
+ * @property {string=} hashDigest digest the stored hashes are encoded in (for `[hash:]`)
+ * @property {boolean=} realContentHash whether `optimization.realContentHash` recomputes content hashes (records an inline `[contenthash:]` so it re-encodes in that digest)
+ * @property {boolean=} hashAsFullHash treat `[hash]` as `[fullhash]` rather than the module hash (CSS local idents)
+ * @property {boolean=} noChunkHash
+ * @property {string=} url
+ * @property {string=} local
+ * @property {string=} uniqueName
+ * @property {PrepareIdFunction=} prepareId
+ */
+
+/**
+ * Path data narrowed for the chunk filename / chunk asset interpolation context,
+ * where `chunk` is always provided. Use as the type parameter to `TemplatePathFn`
+ * for callbacks that receive a chunk context (for example `output.filename`,
+ * `output.chunkFilename`, `output.cssFilename`, `output.cssChunkFilename`,
+ * `optimization.splitChunks.cacheGroups[*].filename`).
+ * @typedef {PathData & { chunk: Chunk | ChunkPathData }} PathDataChunk
+ */
+
+/**
+ * Path data narrowed for the module asset interpolation context, where `module`
+ * and `chunkGraph` are always provided. Use as the type parameter to
+ * `TemplatePathFn` for callbacks that receive a module context (for example
+ * `output.assetModuleFilename`, the per-module `generator.filename` /
+ * `generator.outputPath`, and `module.parser.css.localIdentName`).
+ * @typedef {PathData & { module: Module | ModulePathData, chunkGraph: ChunkGraph }} PathDataModule
+ */
+
+/** @typedef {"module" | "chunk" | "root-of-chunk" | "nested"} ExcludeModulesType */
+
+/**
+ * Defines the known normalized stats options type used by this module.
+ * @typedef {object} KnownNormalizedStatsOptions
+ * @property {string} context
+ * @property {RequestShortener} requestShortener
+ * @property {string | false} chunksSort
+ * @property {string | false} modulesSort
+ * @property {string | false} chunkModulesSort
+ * @property {string | false} nestedModulesSort
+ * @property {string | false} assetsSort
+ * @property {boolean} ids
+ * @property {boolean} cachedAssets
+ * @property {boolean} groupAssetsByEmitStatus
+ * @property {boolean} groupAssetsByPath
+ * @property {boolean} groupAssetsByExtension
+ * @property {number} assetsSpace
+ * @property {((value: string, asset: StatsAsset) => boolean)[]} excludeAssets
+ * @property {((name: string, module: StatsModule, type: ExcludeModulesType) => boolean)[]} excludeModules
+ * @property {((warning: StatsError, textValue: string) => boolean)[]} warningsFilter
+ * @property {boolean} cachedModules
+ * @property {boolean} orphanModules
+ * @property {boolean} dependentModules
+ * @property {boolean} runtimeModules
+ * @property {boolean} groupModulesByCacheStatus
+ * @property {boolean} groupModulesByLayer
+ * @property {boolean} groupModulesByAttributes
+ * @property {boolean} groupModulesByPath
+ * @property {boolean} groupModulesByExtension
+ * @property {boolean} groupModulesByType
+ * @property {boolean | "auto"} entrypoints
+ * @property {boolean} chunkGroups
+ * @property {boolean} chunkGroupAuxiliary
+ * @property {boolean} chunkGroupChildren
+ * @property {number} chunkGroupMaxAssets
+ * @property {number} modulesSpace
+ * @property {number} chunkModulesSpace
+ * @property {number} nestedModulesSpace
+ * @property {false | "none" | "error" | "warn" | "info" | "log" | "verbose"} logging
+ * @property {((value: string) => boolean)[]} loggingDebug
+ * @property {boolean} loggingTrace
+ * @property {EXPECTED_ANY} _env
+ */
+
+/** @typedef {KnownNormalizedStatsOptions & Omit & Record} NormalizedStatsOptions */
+
+/**
+ * Defines the known create stats options context type used by this module.
+ * @typedef {object} KnownCreateStatsOptionsContext
+ * @property {boolean=} forToString
+ */
+
+/** @typedef {KnownCreateStatsOptionsContext & Record} CreateStatsOptionsContext */
+
+/** @typedef {{ module: Module, hash: string, runtime: RuntimeSpec, runtimes: RuntimeSpec[] }} CodeGenerationJob */
+
+/** @typedef {CodeGenerationJob[]} CodeGenerationJobs */
+
+/** @typedef {{ javascript: ModuleTemplate }} ModuleTemplates */
+
+/** @typedef {Set} NotCodeGeneratedModules */
+
+/** @type {AssetInfo} */
+const EMPTY_ASSET_INFO = Object.freeze({});
+
+const esmDependencyCategory = "esm";
+
+// TODO webpack 6: remove
+const deprecatedNormalModuleLoaderHook = util.deprecate(
+	/**
+	 * Handles the callback logic for this hook.
+	 * @param {Compilation} compilation compilation
+	 * @returns {NormalModuleCompilationHooks["loader"]} hooks
+	 */
+	(compilation) =>
+		require("./NormalModule").getCompilationHooks(compilation).loader,
+	"Compilation.hooks.normalModuleLoader was moved to NormalModule.getCompilationHooks(compilation).loader",
+	"DEP_WEBPACK_COMPILATION_NORMAL_MODULE_LOADER_HOOK"
+);
+
+// TODO webpack 6: remove
+/**
+ * Define removed module templates.
+ * @param {ModuleTemplates | undefined} moduleTemplates module templates
+ */
+const defineRemovedModuleTemplates = (moduleTemplates) => {
+	Object.defineProperties(moduleTemplates, {
+		asset: {
+			enumerable: false,
+			configurable: false,
+			get: () => {
+				throw new WebpackError(
+					"Compilation.moduleTemplates.asset has been removed"
+				);
+			}
+		},
+		webassembly: {
+			enumerable: false,
+			configurable: false,
+			get: () => {
+				throw new WebpackError(
+					"Compilation.moduleTemplates.webassembly has been removed"
+				);
+			}
 		}
-	}
+	});
+	moduleTemplates = undefined;
 };
 
-const iterationOfArrayCallback = (arr, fn) => {
-	for (let index = 0; index < arr.length; index++) {
-		fn(arr[index]);
+const byId = compareSelect((c) => c.id, compareIds);
+
+const byNameOrHash = concatComparators(
+	compareSelect((c) => c.name, compareIds),
+	compareSelect((c) => c.fullHash, compareIds)
+);
+
+/**
+ * Defines the known unsafe cache data type used by this module.
+ * @typedef {object} KnownUnsafeCacheData
+ * @property {FactoryMeta=} factoryMeta factory meta
+ * @property {ResolveOptions=} resolveOptions resolve options
+ * @property {ParserOptions=} parserOptions
+ * @property {GeneratorOptions=} generatorOptions
+ */
+
+/** @typedef {KnownUnsafeCacheData & Record} UnsafeCacheData */
+
+/**
+ * Defines the module with restore from unsafe cache type used by this module.
+ * @typedef {Module & { restoreFromUnsafeCache?: (unsafeCacheData: UnsafeCacheData, moduleFactory: ModuleFactory, compilationParams: CompilationParams) => void }} ModuleWithRestoreFromUnsafeCache
+ */
+
+/** @typedef {(module: Module) => boolean} UnsafeCachePredicate */
+
+/** @type {WeakMap} */
+const unsafeCacheDependencies = new WeakMap();
+
+/** @type {WeakMap} */
+const unsafeCacheData = new WeakMap();
+
+/** @typedef {EXPECTED_OBJECT} DependencyReportCacheToken */
+
+/**
+ * Renewed when the module or a transitively referenced module is rebuilt.
+ * @type {WeakMap}
+ */
+const dependencyReportCacheTokens = new WeakMap();
+
+/**
+ * Modules known to have no dependency errors/warnings, per cache token.
+ * @type {WeakMap>}
+ */
+const modulesWithoutProblems = new WeakMap();
+
+/**
+ * Reduce affect type.
+ * @param {Readonly} connections connections
+ * @returns {symbol | boolean} result
+ */
+const reduceAffectType = (connections) => {
+	let affected = false;
+	for (const { dependency } of connections) {
+		if (!dependency) continue;
+		const type = dependency.couldAffectReferencingModule();
+		if (type === Dependency.TRANSITIVE) return Dependency.TRANSITIVE;
+		if (type === false) continue;
+		affected = true;
 	}
+	return affected;
 };
 
-function addAllToSet(set, otherSet) {
-	for (const item of otherSet) {
-		set.add(item);
-	}
-}
+/** @typedef {{ id: ModuleId, modules?: Map, blocks?: (ChunkId | null)[], sourceTypes?: SourceTypes }} References */
+/** @typedef {Map>} ModuleMemCaches */
+/** @typedef {{ chunk: Chunk, load: () => Promise, create: (loaded: EXPECTED_ANY) => RuntimeModule, chunkGraph: ChunkGraph }} PendingRuntimeModule */
+
+class Compilation {
+	/**
+	 * Creates an instance of Compilation.
+	 * @param {Compiler} compiler the compiler which created the compilation
+	 * @param {CompilationParams} params the compilation parameters
+	 */
+	constructor(compiler, params) {
+		/** @type {boolean} */
+		this._backCompat = compiler._backCompat;
+
+		const getNormalModuleLoader = () => deprecatedNormalModuleLoaderHook(this);
+		/** @typedef {{ additionalAssets?: boolean | ((assets: CompilationAssets) => void) }} ProcessAssetsAdditionalOptions */
+		/** @type {AsyncSeriesHook<[CompilationAssets], ProcessAssetsAdditionalOptions>} */
+		const processAssetsHook = new AsyncSeriesHook(["assets"]);
+
+		/** @type {Set} */
+		let savedAssets = new Set();
+		/**
+		 * Returns new assets.
+		 * @param {CompilationAssets} assets assets
+		 * @returns {CompilationAssets} new assets
+		 */
+		const popNewAssets = (assets) => {
+			/** @type {undefined | CompilationAssets} */
+			let newAssets;
+			for (const file of Object.keys(assets)) {
+				if (savedAssets.has(file)) continue;
+				if (newAssets === undefined) {
+					newAssets = Object.create(null);
+				}
+				/** @type {CompilationAssets} */
+				(newAssets)[file] = assets[file];
+				savedAssets.add(file);
+			}
+			return /** @type {CompilationAssets} */ (newAssets);
+		};
+		processAssetsHook.intercept({
+			name: "Compilation",
+			call: () => {
+				savedAssets = new Set(Object.keys(this.assets));
+			},
+			register: (tap) => {
+				const { type, name } = tap;
+				const { fn, additionalAssets, ...remainingTap } = tap;
+				const additionalAssetsFn =
+					additionalAssets === true ? fn : additionalAssets;
+				/** @typedef {WeakSet} ProcessedAssets */
+
+				/** @type {ProcessedAssets | undefined} */
+				const processedAssets = additionalAssetsFn ? new WeakSet() : undefined;
+				/**
+				 * Gets available assets.
+				 * @param {CompilationAssets} assets to be processed by additionalAssetsFn
+				 * @returns {CompilationAssets} available assets
+				 */
+				const getAvailableAssets = (assets) => {
+					/** @type {CompilationAssets} */
+					const availableAssets = {};
+					for (const file of Object.keys(assets)) {
+						// https://github.com/webpack-contrib/compression-webpack-plugin/issues/390
+						if (this.assets[file]) {
+							availableAssets[file] = assets[file];
+						}
+					}
+					return availableAssets;
+				};
+				switch (type) {
+					case "sync":
+						if (additionalAssetsFn) {
+							this.hooks.processAdditionalAssets.tap(name, (assets) => {
+								if (
+									/** @type {ProcessedAssets} */
+									(processedAssets).has(this.assets)
+								) {
+									additionalAssetsFn(getAvailableAssets(assets));
+								}
+							});
+						}
+						return {
+							...remainingTap,
+							type: "async",
+							/**
+							 * Processes the provided asset.
+							 * @param {CompilationAssets} assets assets
+							 * @param {(err?: Error | null, result?: void) => void} callback callback
+							 * @returns {void}
+							 */
+							fn: (assets, callback) => {
+								try {
+									fn(assets);
+								} catch (err) {
+									return callback(/** @type {Error} */ (err));
+								}
+								if (processedAssets !== undefined) {
+									processedAssets.add(this.assets);
+								}
+								const newAssets = popNewAssets(assets);
+								if (newAssets !== undefined) {
+									this.hooks.processAdditionalAssets.callAsync(
+										newAssets,
+										callback
+									);
+									return;
+								}
+								callback();
+							}
+						};
+					case "async":
+						if (additionalAssetsFn) {
+							this.hooks.processAdditionalAssets.tapAsync(
+								name,
+								(assets, callback) => {
+									if (
+										/** @type {ProcessedAssets} */
+										(processedAssets).has(this.assets)
+									) {
+										return additionalAssetsFn(
+											getAvailableAssets(assets),
+											callback
+										);
+									}
+									callback();
+								}
+							);
+						}
+						return {
+							...remainingTap,
+							/**
+							 * Processes the provided asset.
+							 * @param {CompilationAssets} assets assets
+							 * @param {(err?: Error | null, result?: void) => void} callback callback
+							 * @returns {void}
+							 */
+							fn: (assets, callback) => {
+								fn(
+									assets,
+									/**
+									 * Handles the callback logic for this hook.
+									 * @param {Error} err err
+									 * @returns {void}
+									 */
+									(err) => {
+										if (err) return callback(err);
+										if (processedAssets !== undefined) {
+											processedAssets.add(this.assets);
+										}
+										const newAssets = popNewAssets(assets);
+										if (newAssets !== undefined) {
+											this.hooks.processAdditionalAssets.callAsync(
+												newAssets,
+												callback
+											);
+											return;
+										}
+										callback();
+									}
+								);
+							}
+						};
+					case "promise":
+						if (additionalAssetsFn) {
+							this.hooks.processAdditionalAssets.tapPromise(name, (assets) => {
+								if (
+									/** @type {ProcessedAssets} */
+									(processedAssets).has(this.assets)
+								) {
+									return additionalAssetsFn(getAvailableAssets(assets));
+								}
+								return Promise.resolve();
+							});
+						}
+						return {
+							...remainingTap,
+							/**
+							 * Returns result.
+							 * @param {CompilationAssets} assets assets
+							 * @returns {Promise} result
+							 */
+							fn: (assets) => {
+								const p = fn(assets);
+								if (!p || !p.then) return p;
+								return p.then(() => {
+									if (processedAssets !== undefined) {
+										processedAssets.add(this.assets);
+									}
+									const newAssets = popNewAssets(assets);
+									if (newAssets !== undefined) {
+										return this.hooks.processAdditionalAssets.promise(
+											newAssets
+										);
+									}
+								});
+							}
+						};
+				}
+			}
+		});
 
-class Compilation extends Tapable {
-	constructor(compiler) {
-		super();
-		this.hooks = {
+		/** @type {SyncHook<[CompilationAssets]>} */
+		const afterProcessAssetsHook = new SyncHook(["assets"]);
+
+		/**
+		 * Creates a process assets hook.
+		 * @template T
+		 * @param {string} name name of the hook
+		 * @param {number} stage new stage
+		 * @param {() => AsArray} getArgs get old hook function args
+		 * @param {string=} code deprecation code (not deprecated when unset)
+		 * @returns {FakeHook, "tap" | "tapAsync" | "tapPromise" | "name">> | undefined} fake hook which redirects
+		 */
+		const createProcessAssetsHook = (name, stage, getArgs, code) => {
+			if (!this._backCompat && code) return;
+			/**
+			 * Returns error message.
+			 * @param {string} reason reason
+			 * @returns {string} error message
+			 */
+			const errorMessage = (
+				reason
+			) => `Can't automatically convert plugin using Compilation.hooks.${name} to Compilation.hooks.processAssets because ${reason}.
+BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a single Compilation.hooks.processAssets hook.`;
+			/**
+			 * Normalizes tap options for migrated process-assets hooks.
+			 * @param {string | (import("tapable").TapOptions & { name: string } & ProcessAssetsAdditionalOptions)} options hook options
+			 * @returns {import("tapable").TapOptions & { name: string } & ProcessAssetsAdditionalOptions} modified options
+			 */
+			const getOptions = (options) => {
+				if (typeof options === "string") options = { name: options };
+				if (options.stage) {
+					throw new Error(errorMessage("it's using the 'stage' option"));
+				}
+				return { ...options, stage };
+			};
+			return createFakeHook(
+				{
+					name,
+					/** @type {AsyncSeriesHook["intercept"]} */
+					intercept(_interceptor) {
+						throw new Error(errorMessage("it's using 'intercept'"));
+					},
+					/** @type {AsyncSeriesHook["tap"]} */
+					tap: (options, fn) => {
+						processAssetsHook.tap(getOptions(options), () => fn(...getArgs()));
+					},
+					/** @type {AsyncSeriesHook["tapAsync"]} */
+					tapAsync: (options, fn) => {
+						processAssetsHook.tapAsync(
+							getOptions(options),
+							(assets, callback) =>
+								/** @type {EXPECTED_ANY} */ (fn)(...getArgs(), callback)
+						);
+					},
+					/** @type {AsyncSeriesHook["tapPromise"]} */
+					tapPromise: (options, fn) => {
+						processAssetsHook.tapPromise(getOptions(options), () =>
+							fn(...getArgs())
+						);
+					}
+				},
+				`${name} is deprecated (use Compilation.hooks.processAssets instead and use one of Compilation.PROCESS_ASSETS_STAGE_* as stage option)`,
+				code
+			);
+		};
+		this.hooks = Object.freeze({
+			/** @type {SyncHook<[Module]>} */
 			buildModule: new SyncHook(["module"]),
+			/** @type {SyncHook<[Module]>} */
 			rebuildModule: new SyncHook(["module"]),
+			/** @type {SyncHook<[Module, WebpackError]>} */
 			failedModule: new SyncHook(["module", "error"]),
+			/** @type {SyncHook<[Module]>} */
 			succeedModule: new SyncHook(["module"]),
+			/** @type {SyncHook<[Module]>} */
+			stillValidModule: new SyncHook(["module"]),
+
+			/** @type {SyncHook<[Dependency, EntryOptions]>} */
+			addEntry: new SyncHook(["entry", "options"]),
+			/** @type {SyncHook<[Dependency, EntryOptions, Error]>} */
+			failedEntry: new SyncHook(["entry", "options", "error"]),
+			/** @type {SyncHook<[Dependency, EntryOptions, Module]>} */
+			succeedEntry: new SyncHook(["entry", "options", "module"]),
+
+			/** @type {SyncWaterfallHook<[ReferencedExports, Dependency, RuntimeSpec]>} */
+			dependencyReferencedExports: new SyncWaterfallHook([
+				"referencedExports",
+				"dependency",
+				"runtime"
+			]),
 
-			finishModules: new SyncHook(["modules"]),
-			finishRebuildingModule: new SyncHook(["module"]),
-
+			/**
+			 * @type {SyncHook<[ExecuteModuleArgument, ExecuteModuleContext]>}
+			 * @since 5.32.0
+			 */
+			executeModule: new SyncHook(["options", "context"]),
+			/**
+			 * @type {AsyncParallelHook<[ExecuteModuleArgument, ExecuteModuleContext]>}
+			 * @since 5.33.0
+			 */
+			prepareModuleExecution: new AsyncParallelHook(["options", "context"]),
+
+			/** @type {AsyncSeriesHook<[Iterable]>} */
+			finishModules: new AsyncSeriesHook(["modules"]),
+			/** @type {AsyncSeriesHook<[Module]>} */
+			finishRebuildingModule: new AsyncSeriesHook(["module"]),
+			/** @type {SyncHook<[]>} */
 			unseal: new SyncHook([]),
+			/** @type {SyncHook<[]>} */
 			seal: new SyncHook([]),
 
-			optimizeDependenciesBasic: new SyncBailHook(["modules"]),
+			/** @type {SyncHook<[]>} */
+			beforeChunks: new SyncHook([]),
+			/**
+			 * The `afterChunks` hook is called directly after the chunks and module graph have
+			 * been created and before the chunks and modules have been optimized. This hook is useful to
+			 * inspect, analyze, and/or modify the chunk graph.
+			 * @type {SyncHook<[Iterable]>}
+			 */
+			afterChunks: new SyncHook(["chunks"]),
+
+			/** @type {SyncBailHook<[Iterable], boolean | void>} */
 			optimizeDependencies: new SyncBailHook(["modules"]),
-			optimizeDependenciesAdvanced: new SyncBailHook(["modules"]),
+			/** @type {SyncHook<[Iterable]>} */
 			afterOptimizeDependencies: new SyncHook(["modules"]),
 
+			/** @type {SyncHook<[]>} */
 			optimize: new SyncHook([]),
-
-			optimizeModulesBasic: new SyncBailHook(["modules"]),
+			/** @type {SyncBailHook<[Iterable], boolean | void>} */
 			optimizeModules: new SyncBailHook(["modules"]),
-			optimizeModulesAdvanced: new SyncBailHook(["modules"]),
+			/** @type {SyncHook<[Iterable]>} */
 			afterOptimizeModules: new SyncHook(["modules"]),
 
-			optimizeChunksBasic: new SyncBailHook(["chunks", "chunkGroups"]),
+			/** @type {SyncBailHook<[Iterable, ChunkGroup[]], boolean | void>} */
 			optimizeChunks: new SyncBailHook(["chunks", "chunkGroups"]),
-			optimizeChunksAdvanced: new SyncBailHook(["chunks", "chunkGroups"]),
+			/** @type {SyncHook<[Iterable, ChunkGroup[]]>} */
 			afterOptimizeChunks: new SyncHook(["chunks", "chunkGroups"]),
 
+			/** @type {AsyncSeriesHook<[Iterable, Iterable]>} */
 			optimizeTree: new AsyncSeriesHook(["chunks", "modules"]),
+			/** @type {SyncHook<[Iterable, Iterable]>} */
 			afterOptimizeTree: new SyncHook(["chunks", "modules"]),
 
-			optimizeChunkModulesBasic: new SyncBailHook(["chunks", "modules"]),
-			optimizeChunkModules: new SyncBailHook(["chunks", "modules"]),
-			optimizeChunkModulesAdvanced: new SyncBailHook(["chunks", "modules"]),
+			/** @type {AsyncSeriesBailHook<[Iterable, Iterable], void>} */
+			optimizeChunkModules: new AsyncSeriesBailHook(["chunks", "modules"]),
+			/** @type {SyncHook<[Iterable, Iterable]>} */
 			afterOptimizeChunkModules: new SyncHook(["chunks", "modules"]),
+			/** @type {SyncBailHook<[], boolean | void>} */
 			shouldRecord: new SyncBailHook([]),
 
+			/** @type {SyncHook<[Chunk, RuntimeRequirements, RuntimeRequirementsContext]>} */
+			additionalChunkRuntimeRequirements: new SyncHook([
+				"chunk",
+				"runtimeRequirements",
+				"context"
+			]),
+			/** @type {HookMap>} */
+			runtimeRequirementInChunk: new HookMap(
+				() => new SyncBailHook(["chunk", "runtimeRequirements", "context"])
+			),
+			/** @type {SyncHook<[Module, RuntimeRequirements, RuntimeRequirementsContext]>} */
+			additionalModuleRuntimeRequirements: new SyncHook([
+				"module",
+				"runtimeRequirements",
+				"context"
+			]),
+			/** @type {HookMap>} */
+			runtimeRequirementInModule: new HookMap(
+				() => new SyncBailHook(["module", "runtimeRequirements", "context"])
+			),
+			/** @type {SyncHook<[Chunk, RuntimeRequirements, RuntimeRequirementsContext]>} */
+			additionalTreeRuntimeRequirements: new SyncHook([
+				"chunk",
+				"runtimeRequirements",
+				"context"
+			]),
+			/** @type {HookMap>} */
+			runtimeRequirementInTree: new HookMap(
+				() => new SyncBailHook(["chunk", "runtimeRequirements", "context"])
+			),
+
+			/** @type {SyncHook<[RuntimeModule, Chunk]>} */
+			runtimeModule: new SyncHook(["module", "chunk"]),
+
+			/** @type {SyncHook<[Iterable, Records]>} */
 			reviveModules: new SyncHook(["modules", "records"]),
-			optimizeModuleOrder: new SyncHook(["modules"]),
-			advancedOptimizeModuleOrder: new SyncHook(["modules"]),
+			/** @type {SyncHook<[Iterable]>} */
 			beforeModuleIds: new SyncHook(["modules"]),
+			/** @type {SyncHook<[Iterable]>} */
 			moduleIds: new SyncHook(["modules"]),
+			/** @type {SyncHook<[Iterable]>} */
 			optimizeModuleIds: new SyncHook(["modules"]),
+			/** @type {SyncHook<[Iterable]>} */
 			afterOptimizeModuleIds: new SyncHook(["modules"]),
 
+			/** @type {SyncHook<[Iterable, Records]>} */
 			reviveChunks: new SyncHook(["chunks", "records"]),
-			optimizeChunkOrder: new SyncHook(["chunks"]),
+			/** @type {SyncHook<[Iterable]>} */
 			beforeChunkIds: new SyncHook(["chunks"]),
+			/** @type {SyncHook<[Iterable]>} */
+			chunkIds: new SyncHook(["chunks"]),
+			/** @type {SyncHook<[Iterable]>} */
 			optimizeChunkIds: new SyncHook(["chunks"]),
+			/** @type {SyncHook<[Iterable]>} */
 			afterOptimizeChunkIds: new SyncHook(["chunks"]),
 
+			/** @type {SyncHook<[Iterable, Records]>} */
 			recordModules: new SyncHook(["modules", "records"]),
+			/** @type {SyncHook<[Iterable, Records]>} */
 			recordChunks: new SyncHook(["chunks", "records"]),
 
+			/** @type {SyncHook<[Iterable]>} */
+			optimizeCodeGeneration: new SyncHook(["modules"]),
+
+			/** @type {SyncHook<[]>} */
+			beforeModuleHash: new SyncHook([]),
+			/** @type {SyncHook<[]>} */
+			afterModuleHash: new SyncHook([]),
+
+			/** @type {SyncHook<[]>} */
+			beforeCodeGeneration: new SyncHook([]),
+			/** @type {SyncHook<[]>} */
+			afterCodeGeneration: new SyncHook([]),
+
+			/** @type {SyncHook<[]>} */
+			beforeRuntimeRequirements: new SyncHook([]),
+			/** @type {SyncHook<[]>} */
+			afterRuntimeRequirements: new SyncHook([]),
+
+			/** @type {SyncHook<[]>} */
 			beforeHash: new SyncHook([]),
+			/** @type {SyncHook<[Chunk]>} */
 			contentHash: new SyncHook(["chunk"]),
+			/** @type {SyncHook<[]>} */
 			afterHash: new SyncHook([]),
-
+			/** @type {SyncHook<[Records]>} */
 			recordHash: new SyncHook(["records"]),
-
+			/** @type {SyncHook<[Compilation, Records]>} */
 			record: new SyncHook(["compilation", "records"]),
 
+			/** @type {SyncHook<[]>} */
 			beforeModuleAssets: new SyncHook([]),
+			/** @type {SyncBailHook<[], boolean | void>} */
 			shouldGenerateChunkAssets: new SyncBailHook([]),
+			/** @type {SyncHook<[]>} */
 			beforeChunkAssets: new SyncHook([]),
-			additionalChunkAssets: new SyncHook(["chunks"]),
-
-			additionalAssets: new AsyncSeriesHook([]),
-			optimizeChunkAssets: new AsyncSeriesHook(["chunks"]),
-			afterOptimizeChunkAssets: new SyncHook(["chunks"]),
-			optimizeAssets: new AsyncSeriesHook(["assets"]),
-			afterOptimizeAssets: new SyncHook(["assets"]),
-
+			// TODO webpack 6 remove
+			/** @deprecated */
+			additionalChunkAssets:
+				/** @type {FakeHook, "tap" | "tapAsync" | "tapPromise" | "name">>} */
+				(
+					createProcessAssetsHook(
+						"additionalChunkAssets",
+						Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,
+						() => [this.chunks],
+						"DEP_WEBPACK_COMPILATION_ADDITIONAL_CHUNK_ASSETS"
+					)
+				),
+
+			// TODO webpack 6 deprecate
+			/** @deprecated */
+			additionalAssets:
+				/** @type {FakeHook, "tap" | "tapAsync" | "tapPromise" | "name">>} */
+				(
+					createProcessAssetsHook(
+						"additionalAssets",
+						Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,
+						() => []
+					)
+				),
+			// TODO webpack 6 remove
+			/** @deprecated */
+			optimizeChunkAssets:
+				/** @type {FakeHook, "tap" | "tapAsync" | "tapPromise" | "name">>} */
+				(
+					createProcessAssetsHook(
+						"optimizeChunkAssets",
+						Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE,
+						() => [this.chunks],
+						"DEP_WEBPACK_COMPILATION_OPTIMIZE_CHUNK_ASSETS"
+					)
+				),
+			// TODO webpack 6 remove
+			/** @deprecated */
+			afterOptimizeChunkAssets:
+				/** @type {FakeHook, "tap" | "tapAsync" | "tapPromise" | "name">>} */
+				(
+					createProcessAssetsHook(
+						"afterOptimizeChunkAssets",
+						Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE + 1,
+						() => [this.chunks],
+						"DEP_WEBPACK_COMPILATION_AFTER_OPTIMIZE_CHUNK_ASSETS"
+					)
+				),
+			// TODO webpack 6 deprecate
+			/** @deprecated */
+			optimizeAssets: processAssetsHook,
+			// TODO webpack 6 deprecate
+			/** @deprecated */
+			afterOptimizeAssets: afterProcessAssetsHook,
+
+			processAssets: processAssetsHook,
+			afterProcessAssets: afterProcessAssetsHook,
+			/**
+			 * @type {AsyncSeriesHook<[CompilationAssets]>}
+			 * @since 5.8.0
+			 */
+			processAdditionalAssets: new AsyncSeriesHook(["assets"]),
+
+			/** @type {SyncBailHook<[], boolean | void>} */
 			needAdditionalSeal: new SyncBailHook([]),
+			/** @type {AsyncSeriesHook<[]>} */
 			afterSeal: new AsyncSeriesHook([]),
 
-			chunkHash: new SyncHook(["chunk", "chunkHash"]),
+			/** @type {SyncWaterfallHook<[RenderManifestEntry[], RenderManifestOptions]>} */
+			renderManifest: new SyncWaterfallHook(["result", "options"]),
+
+			/** @type {SyncHook<[Hash]>} */
+			fullHash: new SyncHook(["hash"]),
+			/** @type {SyncHook<[Chunk, Hash, ChunkHashContext]>} */
+			chunkHash: new SyncHook(["chunk", "chunkHash", "ChunkHashContext"]),
+
+			/** @type {SyncHook<[Module, string]>} */
 			moduleAsset: new SyncHook(["module", "filename"]),
+			/** @type {SyncHook<[Chunk, string]>} */
 			chunkAsset: new SyncHook(["chunk", "filename"]),
 
-			assetPath: new SyncWaterfallHook(["filename", "data"]), // TODO MainTemplate
+			/** @type {SyncWaterfallHook<[string, PathData, AssetInfo | undefined]>} */
+			assetPath: new SyncWaterfallHook(["path", "options", "assetInfo"]),
 
+			/** @type {SyncBailHook<[], boolean | void>} */
 			needAdditionalPass: new SyncBailHook([]),
+
+			/** @type {SyncHook<[Compiler, string, number]>} */
 			childCompiler: new SyncHook([
 				"childCompiler",
 				"compilerName",
 				"compilerIndex"
 			]),
 
-			// TODO the following hooks are weirdly located here
-			// TODO move them for webpack 5
-			normalModuleLoader: new SyncHook(["loaderContext", "module"]),
-
-			optimizeExtractedChunksBasic: new SyncBailHook(["chunks"]),
-			optimizeExtractedChunks: new SyncBailHook(["chunks"]),
-			optimizeExtractedChunksAdvanced: new SyncBailHook(["chunks"]),
-			afterOptimizeExtractedChunks: new SyncHook(["chunks"])
-		};
-		this._pluginCompat.tap("Compilation", options => {
-			switch (options.name) {
-				case "optimize-tree":
-				case "additional-assets":
-				case "optimize-chunk-assets":
-				case "optimize-assets":
-				case "after-seal":
-					options.async = true;
-					break;
+			/** @type {SyncBailHook<[string, LogEntry], boolean | void>} */
+			log: new SyncBailHook(["origin", "logEntry"]),
+
+			/** @type {SyncWaterfallHook<[Error[]]>} */
+			processWarnings: new SyncWaterfallHook(["warnings"]),
+			/** @type {SyncWaterfallHook<[Error[]]>} */
+			processErrors: new SyncWaterfallHook(["errors"]),
+
+			/** @type {HookMap, CreateStatsOptionsContext]>>} */
+			statsPreset: new HookMap(() => new SyncHook(["options", "context"])),
+			/** @type {SyncHook<[Partial, CreateStatsOptionsContext]>} */
+			statsNormalize: new SyncHook(["options", "context"]),
+			/** @type {SyncHook<[StatsFactory, NormalizedStatsOptions]>} */
+			statsFactory: new SyncHook(["statsFactory", "options"]),
+			/** @type {SyncHook<[StatsPrinter, NormalizedStatsOptions]>} */
+			statsPrinter: new SyncHook(["statsPrinter", "options"]),
+
+			/**
+			 * Gets normal module loader.
+			 * @deprecated
+			 * @returns {SyncHook<[AnyLoaderContext, NormalModule]>} normal module loader hook
+			 */
+			get normalModuleLoader() {
+				return getNormalModuleLoader();
 			}
 		});
+		/** @type {string=} */
 		this.name = undefined;
+		/** @type {number | undefined} */
+		this.startTime = undefined;
+		/** @type {number | undefined} */
+		this.endTime = undefined;
+		/** @type {Compiler} */
 		this.compiler = compiler;
 		this.resolverFactory = compiler.resolverFactory;
-		this.inputFileSystem = compiler.inputFileSystem;
+		/** @type {InputFileSystem} */
+		this.inputFileSystem =
+			/** @type {InputFileSystem} */
+			(compiler.inputFileSystem);
+		/** @type {FileSystemInfo} */
+		this.fileSystemInfo = new FileSystemInfo(this.inputFileSystem, {
+			unmanagedPaths: compiler.unmanagedPaths,
+			managedPaths: compiler.managedPaths,
+			immutablePaths: compiler.immutablePaths,
+			logger: this.getLogger("webpack.FileSystemInfo"),
+			hashFunction: compiler.options.output.hashFunction
+		});
+		if (compiler.fileTimestamps) {
+			this.fileSystemInfo.addFileTimestamps(compiler.fileTimestamps, true);
+		}
+		if (compiler.contextTimestamps) {
+			this.fileSystemInfo.addContextTimestamps(
+				compiler.contextTimestamps,
+				true
+			);
+		}
+		/** @type {ValueCacheVersions} */
+		this.valueCacheVersions = new Map();
+		/** @type {RequestShortener} */
 		this.requestShortener = compiler.requestShortener;
-
-		const options = (this.options = compiler.options);
-		this.outputOptions = options && options.output;
-		this.bail = options && options.bail;
-		this.profile = options && options.profile;
-		this.performance = options && options.performance;
-
-		this.mainTemplate = new MainTemplate(this.outputOptions);
-		this.chunkTemplate = new ChunkTemplate(this.outputOptions);
-		this.hotUpdateChunkTemplate = new HotUpdateChunkTemplate(
-			this.outputOptions
-		);
+		/** @type {string} */
+		this.compilerPath = compiler.compilerPath;
+
+		this.logger = this.getLogger("webpack.Compilation");
+
+		const options = /** @type {WebpackOptions} */ (compiler.options);
+		this.options = options;
+		this.outputOptions =
+			/** @type {OutputOptionsWithDefaults} */
+			(options && options.output);
+		/** @type {boolean} */
+		this.bail = (options && options.bail) || false;
+		/** @type {boolean} */
+		this.profile = (options && options.profile) || false;
+
+		/** @type {CompilationParams} */
+		this.params = params;
+		/** @type {MainTemplate} */
+		this.mainTemplate = new MainTemplate(this.outputOptions, this);
+		/** @type {ChunkTemplate} */
+		this.chunkTemplate = new ChunkTemplate(this.outputOptions, this);
+		/** @type {RuntimeTemplate} */
 		this.runtimeTemplate = new RuntimeTemplate(
+			this,
 			this.outputOptions,
 			this.requestShortener
 		);
+		/** @type {ModuleTemplates} */
 		this.moduleTemplates = {
-			javascript: new ModuleTemplate(this.runtimeTemplate, "javascript"),
-			webassembly: new ModuleTemplate(this.runtimeTemplate, "webassembly")
+			javascript: new ModuleTemplate(this.runtimeTemplate, this)
 		};
+		defineRemovedModuleTemplates(this.moduleTemplates);
+
+		// We need to think how implement types here
+		/** @type {ModuleMemCaches | undefined} */
+		this.moduleMemCaches = undefined;
+		/** @type {ModuleMemCaches | undefined} */
+		this.moduleMemCaches2 = undefined;
+		/** @type {ModuleGraph} */
+		this.moduleGraph = new ModuleGraph();
+		/** @type {ChunkGraph} */
+		this.chunkGraph = new ChunkGraph(
+			this.moduleGraph,
+			this.outputOptions.hashFunction
+		);
+		/** @type {CodeGenerationResults | undefined} */
+		this.codeGenerationResults = undefined;
+
+		/** @type {AsyncQueue} */
+		this.processDependenciesQueue = new AsyncQueue({
+			name: "processDependencies",
+			parallelism: options.parallelism || 100,
+			processor: this._processModuleDependencies.bind(this)
+		});
+		/** @type {AsyncQueue} */
+		this.addModuleQueue = new AsyncQueue({
+			name: "addModule",
+			parent: this.processDependenciesQueue,
+			getKey: (module) => module.identifier(),
+			processor: this._addModule.bind(this)
+		});
+		/** @type {AsyncQueue} */
+		this.factorizeQueue = new AsyncQueue({
+			name: "factorize",
+			parent: this.addModuleQueue,
+			processor: this._factorizeModule.bind(this)
+		});
+		/** @type {AsyncQueue} */
+		this.buildQueue = new AsyncQueue({
+			name: "build",
+			parent: this.factorizeQueue,
+			processor: this._buildModule.bind(this)
+		});
+		/** @type {AsyncQueue} */
+		this.rebuildQueue = new AsyncQueue({
+			name: "rebuild",
+			parallelism: options.parallelism || 100,
+			processor: this._rebuildModule.bind(this)
+		});
 
-		this.semaphore = new Semaphore(options.parallelism || 100);
-
-		this.entries = [];
-		this._preparedEntrypoints = [];
+		/**
+		 * Modules in value are building during the build of Module in key.
+		 * Means value blocking key from finishing.
+		 * Needed to detect build cycles.
+		 * @type {WeakMap>}
+		 */
+		this.creatingModuleDuringBuild = new WeakMap();
+
+		/** @type {Map, EntryData>} */
+		this.entries = new Map();
+		/** @type {EntryData} */
+		this.globalEntry = {
+			dependencies: [],
+			includeDependencies: [],
+			options: {
+				name: undefined
+			}
+		};
+		/** @type {Map} */
 		this.entrypoints = new Map();
-		this.chunks = [];
+		/** @type {Entrypoint[]} */
+		this.asyncEntrypoints = [];
+		/** @type {Chunks} */
+		this.chunks = new Set();
+		/** @type {ChunkGroup[]} */
 		this.chunkGroups = [];
+		/** @type {Map} */
 		this.namedChunkGroups = new Map();
+		/** @type {Map} */
 		this.namedChunks = new Map();
-		this.modules = [];
+		/** @type {Set} */
+		this.modules = new Set();
+		if (this._backCompat) {
+			arrayToSetDeprecation(this.chunks, "Compilation.chunks");
+			arrayToSetDeprecation(this.modules, "Compilation.modules");
+		}
+		/**
+		 * @private
+		 * @type {Map}
+		 */
 		this._modules = new Map();
-		this.cache = null;
+		/** @type {Records | null} */
 		this.records = null;
-		this.nextFreeModuleIndex = undefined;
-		this.nextFreeModuleIndex2 = undefined;
+		/** @type {string[]} */
 		this.additionalChunkAssets = [];
+		/** @type {CompilationAssets} */
 		this.assets = {};
+		/** @type {Map} */
+		this.assetsInfo = new Map();
+		/** @type {Map>>} */
+		this._assetsRelatedIn = new Map();
+		/** @type {Map> | undefined} */
+		this._assetToChunkIndex = undefined;
+		/** @type {Map> | undefined} */
+		this._assetToChunkAuxiliaryIndex = undefined;
+		/** @type {Error[]} */
 		this.errors = [];
+		/** @type {Error[]} */
 		this.warnings = [];
+		// Hints reported with `performance.hints: "stats"` — surfaced in stats
+		// only, so they neither fail the build nor count as warnings.
+		/** @type {Error[]} */
+		this.hints = [];
+		/** @type {Compilation[]} */
 		this.children = [];
+		/** @type {Map} */
+		this.logging = new Map();
+		/** @type {Map} */
 		this.dependencyFactories = new Map();
-		this.dependencyTemplates = new Map();
-		this.dependencyTemplates.set("hash", "");
+		/** @type {DependencyTemplates} */
+		this.dependencyTemplates = new DependencyTemplates(
+			this.outputOptions.hashFunction
+		);
+		/** @type {Record} */
 		this.childrenCounters = {};
+		/** @type {Set | null} */
 		this.usedChunkIds = null;
+		/** @type {Set | null} */
 		this.usedModuleIds = null;
-		this.fileTimestamps = undefined;
-		this.contextTimestamps = undefined;
-		this.compilationDependencies = undefined;
+		/** @type {boolean} */
+		this.needAdditionalPass = false;
+		/** @type {Set} */
+		this._restoredUnsafeCacheModuleEntries = new Set();
+		/** @type {Map} */
+		this._restoredUnsafeCacheEntries = new Map();
+		/** @type {WeakSet} */
+		this.builtModules = new WeakSet();
+		/** @type {WeakSet} */
+		this.codeGeneratedModules = new WeakSet();
+		/** @type {WeakSet} */
+		this.buildTimeExecutedModules = new WeakSet();
+		/** @type {Set} */
+		this.emittedAssets = new Set();
+		/** @type {Set} */
+		this.comparedForEmitAssets = new Set();
+		/** @type {FileSystemDependencies} */
+		this.fileDependencies = new LazySet();
+		/** @type {FileSystemDependencies} */
+		this.contextDependencies = new LazySet();
+		/** @type {FileSystemDependencies} */
+		this.missingDependencies = new LazySet();
+		/** @type {FileSystemDependencies} */
+		this.buildDependencies = new LazySet();
+		// TODO webpack 6 remove
+		/**
+		 * @deprecated
+		 * @type {{ add: (item: string) => FileSystemDependencies }}
+		 */
+		this.compilationDependencies = {
+			add: util.deprecate(
+				/**
+				 * Handles the add callback for this hook.
+				 * @param {string} item item
+				 * @returns {FileSystemDependencies} file dependencies
+				 */
+				(item) => this.fileDependencies.add(item),
+				"Compilation.compilationDependencies is deprecated (used Compilation.fileDependencies instead)",
+				"DEP_WEBPACK_COMPILATION_COMPILATION_DEPENDENCIES"
+			)
+		};
 
-		this._buildingModules = new Map();
-		this._rebuildingModules = new Map();
+		/** @type {CacheFacade} */
+		this._modulesCache = this.getCache("Compilation/modules");
+		/** @type {CacheFacade} */
+		this._assetsCache = this.getCache("Compilation/assets");
+		/** @type {CacheFacade} */
+		this._codeGenerationCache = this.getCache("Compilation/codeGeneration");
+		/** @type {PendingRuntimeModule[] | undefined} */
+		this._pendingRuntimeModules = undefined;
+
+		const unsafeCache = options.module.unsafeCache;
+		/** @type {boolean} */
+		this._unsafeCache = Boolean(unsafeCache);
+		/** @type {UnsafeCachePredicate} */
+		this._unsafeCachePredicate =
+			typeof unsafeCache === "function" ? unsafeCache : () => true;
+
+		/** @type {LazyBarrelController} */
+		this._lazyBarrelController = new LazyBarrelController(this);
 	}
 
 	getStats() {
 		return new Stats(this);
 	}
 
-	addModule(module, cacheGroup) {
-		const identifier = module.identifier();
-		const alreadyAddedModule = this._modules.get(identifier);
-		if (alreadyAddedModule) {
-			return {
-				module: alreadyAddedModule,
-				issuer: false,
-				build: false,
-				dependencies: false
+	/**
+	 * Creates a stats options.
+	 * @param {string | boolean | StatsOptions | undefined} optionsOrPreset stats option value
+	 * @param {CreateStatsOptionsContext=} context context
+	 * @returns {NormalizedStatsOptions} normalized options
+	 */
+	createStatsOptions(optionsOrPreset, context = {}) {
+		if (typeof optionsOrPreset === "boolean") {
+			optionsOrPreset = {
+				preset: optionsOrPreset === false ? "none" : "normal"
 			};
+		} else if (typeof optionsOrPreset === "string") {
+			optionsOrPreset = { preset: optionsOrPreset };
 		}
-		const cacheName = (cacheGroup || "m") + identifier;
-		if (this.cache && this.cache[cacheName]) {
-			const cacheModule = this.cache[cacheName];
-
-			if (typeof cacheModule.updateCacheModule === "function") {
-				cacheModule.updateCacheModule(module);
+		if (typeof optionsOrPreset === "object" && optionsOrPreset !== null) {
+			// We use this method of shallow cloning this object to include
+			// properties in the prototype chain
+			/** @type {Partial} */
+			const options = {};
+			for (const key in optionsOrPreset) {
+				options[key] = optionsOrPreset[/** @type {keyof StatsOptions} */ (key)];
 			}
-
-			let rebuild = true;
-			if (this.fileTimestamps && this.contextTimestamps) {
-				rebuild = cacheModule.needRebuild(
-					this.fileTimestamps,
-					this.contextTimestamps
-				);
+			if (options.preset !== undefined) {
+				this.hooks.statsPreset.for(options.preset).call(options, context);
 			}
+			this.hooks.statsNormalize.call(options, context);
+			return /** @type {NormalizedStatsOptions} */ (options);
+		}
+		/** @type {Partial} */
+		const options = {};
+		this.hooks.statsNormalize.call(options, context);
+		return /** @type {NormalizedStatsOptions} */ (options);
+	}
 
-			if (!rebuild) {
-				cacheModule.disconnect();
-				this._modules.set(identifier, cacheModule);
-				this.modules.push(cacheModule);
-				for (const err of cacheModule.errors) {
-					this.errors.push(err);
+	/**
+	 * Creates a stats factory.
+	 * @param {NormalizedStatsOptions} options options
+	 * @returns {StatsFactory} the stats factory
+	 */
+	createStatsFactory(options) {
+		const statsFactory = new StatsFactory();
+		this.hooks.statsFactory.call(statsFactory, options);
+		return statsFactory;
+	}
+
+	/**
+	 * Creates a stats printer.
+	 * @param {NormalizedStatsOptions} options options
+	 * @returns {StatsPrinter} the stats printer
+	 */
+	createStatsPrinter(options) {
+		const statsPrinter = new StatsPrinter();
+		this.hooks.statsPrinter.call(statsPrinter, options);
+		return statsPrinter;
+	}
+
+	/**
+	 * Returns the cache facade instance.
+	 * @param {string} name cache name
+	 * @returns {CacheFacade} the cache facade instance
+	 */
+	getCache(name) {
+		return this.compiler.getCache(name);
+	}
+
+	/**
+	 * Returns a logger with that name.
+	 * @param {string | (() => string)} name name of the logger, or function called once to get the logger name
+	 * @returns {Logger} a logger with that name
+	 */
+	getLogger(name) {
+		if (!name) {
+			throw new TypeError("Compilation.getLogger(name) called without a name");
+		}
+		/** @type {LogEntry[] | undefined} */
+		let logEntries;
+		return new Logger(
+			(type, args) => {
+				if (typeof name === "function") {
+					name = name();
+					if (!name) {
+						throw new TypeError(
+							"Compilation.getLogger(name) called with a function not returning a name"
+						);
+					}
 				}
-				for (const err of cacheModule.warnings) {
-					this.warnings.push(err);
+				/** @type {LogEntry["trace"]} */
+				let trace;
+				switch (type) {
+					case LogType.warn:
+					case LogType.error:
+					case LogType.trace:
+						trace = ErrorHelpers.cutOffLoaderExecution(
+							/** @type {string} */ (new Error("Trace").stack)
+						)
+							.split("\n")
+							.slice(3);
+						break;
 				}
-				return {
-					module: cacheModule,
-					issuer: true,
-					build: false,
-					dependencies: true
+				/** @type {LogEntry} */
+				const logEntry = {
+					time: Date.now(),
+					type,
+					args,
+					trace
 				};
+				/* eslint-disable no-console */
+				if (this.hooks.log.call(name, logEntry) === undefined) {
+					if (
+						logEntry.type === LogType.profileEnd &&
+						typeof console.profileEnd === "function"
+					) {
+						console.profileEnd(
+							`[${name}] ${
+								/** @type {NonNullable} */ (logEntry.args)[0]
+							}`
+						);
+					}
+					if (logEntries === undefined) {
+						logEntries = this.logging.get(name);
+						if (logEntries === undefined) {
+							logEntries = [];
+							this.logging.set(name, logEntries);
+						}
+					}
+					logEntries.push(logEntry);
+					if (
+						logEntry.type === LogType.profile &&
+						typeof console.profile === "function"
+					) {
+						console.profile(
+							`[${name}] ${
+								/** @type {NonNullable} */
+								(logEntry.args)[0]
+							}`
+						);
+					}
+					/* eslint-enable no-console */
+				}
+			},
+			(childName) => {
+				if (typeof name === "function") {
+					if (typeof childName === "function") {
+						return this.getLogger(() => {
+							if (typeof name === "function") {
+								name = name();
+								if (!name) {
+									throw new TypeError(
+										"Compilation.getLogger(name) called with a function not returning a name"
+									);
+								}
+							}
+							if (typeof childName === "function") {
+								childName = childName();
+								if (!childName) {
+									throw new TypeError(
+										"Logger.getChildLogger(name) called with a function not returning a name"
+									);
+								}
+							}
+							return `${name}/${childName}`;
+						});
+					}
+					return this.getLogger(() => {
+						if (typeof name === "function") {
+							name = name();
+							if (!name) {
+								throw new TypeError(
+									"Compilation.getLogger(name) called with a function not returning a name"
+								);
+							}
+						}
+						return `${name}/${childName}`;
+					});
+				}
+				if (typeof childName === "function") {
+					return this.getLogger(() => {
+						if (typeof childName === "function") {
+							childName = childName();
+							if (!childName) {
+								throw new TypeError(
+									"Logger.getChildLogger(name) called with a function not returning a name"
+								);
+							}
+						}
+						return `${name}/${childName}`;
+					});
+				}
+				return this.getLogger(`${name}/${childName}`);
 			}
-			cacheModule.unbuild();
-			module = cacheModule;
+		);
+	}
+
+	/**
+	 * Adds the provided module to the compilation.
+	 * @param {Module} module module to be added that was created
+	 * @param {ModuleCallback} callback returns the module in the compilation,
+	 * it could be the passed one (if new), or an already existing in the compilation
+	 * @returns {void}
+	 */
+	addModule(module, callback) {
+		this.addModuleQueue.add(module, callback);
+	}
+
+	/**
+	 * Adds the provided module to the compilation.
+	 * @param {Module} module module to be added that was created
+	 * @param {ModuleCallback} callback returns the module in the compilation,
+	 * it could be the passed one (if new), or an already existing in the compilation
+	 * @returns {void}
+	 */
+	_addModule(module, callback) {
+		const identifier = module.identifier();
+		const alreadyAddedModule = this._modules.get(identifier);
+		if (alreadyAddedModule) {
+			return callback(null, alreadyAddedModule);
 		}
-		this._modules.set(identifier, module);
-		if (this.cache) {
-			this.cache[cacheName] = module;
+
+		const currentProfile = this.profile
+			? this.moduleGraph.getProfile(module)
+			: undefined;
+		if (currentProfile !== undefined) {
+			currentProfile.markRestoringStart();
 		}
-		this.modules.push(module);
-		return {
-			module: module,
-			issuer: true,
-			build: true,
-			dependencies: true
-		};
+
+		this._modulesCache.get(identifier, null, (err, cacheModule) => {
+			if (err) return callback(new (getModuleRestoreError())(module, err));
+
+			if (currentProfile !== undefined) {
+				currentProfile.markRestoringEnd();
+				currentProfile.markIntegrationStart();
+			}
+
+			if (cacheModule) {
+				cacheModule.updateCacheModule(module);
+
+				module = cacheModule;
+			}
+			this._modules.set(identifier, module);
+			this.modules.add(module);
+			if (this._backCompat) {
+				ModuleGraph.setModuleGraphForModule(module, this.moduleGraph);
+			}
+			if (currentProfile !== undefined) {
+				currentProfile.markIntegrationEnd();
+			}
+			callback(null, module);
+		});
 	}
 
+	/**
+	 * Fetches a module from a compilation by its identifier
+	 * @param {Module} module the module provided
+	 * @returns {Module} the module requested
+	 */
 	getModule(module) {
 		const identifier = module.identifier();
-		return this._modules.get(identifier);
+		return /** @type {Module} */ (this._modules.get(identifier));
 	}
 
+	/**
+	 * Attempts to search for a module by its identifier
+	 * @param {string} identifier identifier (usually path) for module
+	 * @returns {Module | undefined} attempt to search for module and return it, else undefined
+	 */
 	findModule(identifier) {
 		return this._modules.get(identifier);
 	}
 
-	waitForBuildingFinished(module, callback) {
-		let callbackList = this._buildingModules.get(module);
-		if (callbackList) {
-			callbackList.push(() => callback());
-		} else {
-			process.nextTick(callback);
-		}
+	/**
+	 * Schedules a build of the module object
+	 * @param {Module} module module to be built
+	 * @param {ModuleCallback} callback the callback
+	 * @returns {void}
+	 */
+	buildModule(module, callback) {
+		this.buildQueue.add(module, callback);
 	}
 
-	buildModule(module, optional, origin, dependencies, thisCallback) {
-		let callbackList = this._buildingModules.get(module);
-		if (callbackList) {
-			callbackList.push(thisCallback);
-			return;
+	/**
+	 * Builds the module object
+	 * @param {Module} module module to be built
+	 * @param {ModuleCallback} callback the callback
+	 * @returns {void}
+	 */
+	_buildModule(module, callback) {
+		const currentProfile = this.profile
+			? this.moduleGraph.getProfile(module)
+			: undefined;
+		if (currentProfile !== undefined) {
+			currentProfile.markBuildingStart();
 		}
-		this._buildingModules.set(module, (callbackList = [thisCallback]));
 
-		const callback = err => {
-			this._buildingModules.delete(module);
-			for (const cb of callbackList) {
-				cb(err);
-			}
-		};
+		module.needBuild(
+			{
+				compilation: this,
+				fileSystemInfo: this.fileSystemInfo,
+				valueCacheVersions: this.valueCacheVersions
+			},
+			(err, needBuild) => {
+				if (err) return callback(err);
 
-		this.hooks.buildModule.call(module);
-		module.build(
-			this.options,
-			this,
-			this.resolverFactory.get("normal", module.resolveOptions),
-			this.inputFileSystem,
-			error => {
-				const errors = module.errors;
-				for (let indexError = 0; indexError < errors.length; indexError++) {
-					const err = errors[indexError];
-					err.origin = origin;
-					err.dependencies = dependencies;
-					if (optional) {
-						this.warnings.push(err);
-					} else {
-						this.errors.push(err);
+				if (!needBuild) {
+					if (currentProfile !== undefined) {
+						currentProfile.markBuildingEnd();
 					}
+					this.hooks.stillValidModule.call(module);
+					return callback();
 				}
 
-				const warnings = module.warnings;
-				for (
-					let indexWarning = 0;
-					indexWarning < warnings.length;
-					indexWarning++
-				) {
-					const war = warnings[indexWarning];
-					war.origin = origin;
-					war.dependencies = dependencies;
-					this.warnings.push(war);
-				}
-				module.dependencies.sort(Dependency.compare);
-				if (error) {
-					this.hooks.failedModule.call(module, error);
-					return callback(error);
-				}
-				this.hooks.succeedModule.call(module);
-				return callback();
+				this.hooks.buildModule.call(module);
+				this.builtModules.add(module);
+				module.build(
+					this.options,
+					this,
+					this.resolverFactory.get("normal", module.resolveOptions),
+					/** @type {InputFileSystem} */
+					(this.inputFileSystem),
+					(err) => {
+						if (currentProfile !== undefined) {
+							currentProfile.markBuildingEnd();
+						}
+						if (err) {
+							this.hooks.failedModule.call(module, err);
+							return callback(err);
+						}
+						if (currentProfile !== undefined) {
+							currentProfile.markStoringStart();
+						}
+						this._modulesCache.store(
+							module.identifier(),
+							null,
+							module,
+							(err) => {
+								if (currentProfile !== undefined) {
+									currentProfile.markStoringEnd();
+								}
+								if (err) {
+									this.hooks.failedModule.call(
+										module,
+										/** @type {WebpackError} */ (err)
+									);
+									return callback(new (getModuleStoreError())(module, err));
+								}
+								this.hooks.succeedModule.call(module);
+								return callback();
+							}
+						);
+					}
+				);
 			}
 		);
 	}
 
+	/**
+	 * Process module dependencies.
+	 * @param {Module} module to be processed for deps
+	 * @param {ModuleCallback} callback callback to be triggered
+	 * @returns {void}
+	 */
 	processModuleDependencies(module, callback) {
-		const dependencies = new Map();
-
-		const addDependency = dep => {
-			const resourceIdent = dep.getResourceIdentifier();
-			if (resourceIdent) {
-				const factory = this.dependencyFactories.get(dep.constructor);
-				if (factory === undefined) {
-					throw new Error(
-						`No module factory available for dependency type: ${
-							dep.constructor.name
-						}`
-					);
-				}
-				let innerMap = dependencies.get(factory);
-				if (innerMap === undefined) {
-					dependencies.set(factory, (innerMap = new Map()));
-				}
-				let list = innerMap.get(resourceIdent);
-				if (list === undefined) innerMap.set(resourceIdent, (list = []));
-				list.push(dep);
-			}
-		};
+		this.processDependenciesQueue.add(module, callback);
+	}
 
-		const addDependenciesBlock = block => {
+	/**
+	 * Process module dependencies non recursive.
+	 * @param {Module} module to be processed for deps
+	 * @returns {void}
+	 */
+	processModuleDependenciesNonRecursive(module) {
+		/**
+		 * Process dependencies block.
+		 * @param {DependenciesBlock} block block
+		 */
+		const processDependenciesBlock = (block) => {
 			if (block.dependencies) {
-				iterationOfArrayCallback(block.dependencies, addDependency);
+				let i = 0;
+				for (const dep of block.dependencies) {
+					this.moduleGraph.setParents(dep, block, module, i++);
+				}
 			}
 			if (block.blocks) {
-				iterationOfArrayCallback(block.blocks, addDependenciesBlock);
-			}
-			if (block.variables) {
-				iterationBlockVariable(block.variables, addDependency);
+				for (const b of block.blocks) processDependenciesBlock(b);
 			}
 		};
 
-		try {
-			addDependenciesBlock(module);
-		} catch (e) {
-			callback(e);
-		}
+		processDependenciesBlock(module);
+	}
 
+	/**
+	 * Process module dependencies.
+	 * @param {Module} module to be processed for deps
+	 * @param {ModuleCallback} callback callback to be triggered
+	 * @returns {void}
+	 */
+	_processModuleDependencies(module, callback) {
+		/** @type {{ factory: ModuleFactory, dependencies: Dependency[], context: string | undefined, originModule: Module | null }[]} */
 		const sortedDependencies = [];
+		/** @type {boolean} */
+		const hasLowPriorityDependencies = module.dependencies.some(
+			Dependency.isLowPriorityDependency
+		);
+
+		// lazy barrel: defer re-export targets of side-effect-free modules
+		const hasLazyBarrel = this._lazyBarrelController.classify(module);
+
+		/** @type {DependenciesBlock} */
+		let currentBlock;
+
+		/** @type {Map>} */
+		let dependencies;
+		/** @type {DependencyConstructor} */
+		let factoryCacheKey;
+		/** @type {ModuleFactory} */
+		let factoryCacheKey2;
+		/** @typedef {Map} FactoryCacheValue */
+		/** @type {FactoryCacheValue | undefined} */
+		let factoryCacheValue;
+		/** @type {string} */
+		let listCacheKey1;
+		/** @type {string} */
+		let listCacheKey2;
+		/** @type {Dependency[]} */
+		let listCacheValue;
+
+		let inProgressSorting = 1;
+		let inProgressTransitive = 1;
+
+		/**
+		 * On dependencies sorted.
+		 * @param {WebpackError=} err error
+		 * @returns {void}
+		 */
+		const onDependenciesSorted = (err) => {
+			if (err) return callback(err);
+
+			// early exit without changing parallelism back and forth
+			if (sortedDependencies.length === 0 && inProgressTransitive === 1) {
+				return callback();
+			}
 
-		for (const pair1 of dependencies) {
-			for (const pair2 of pair1[1]) {
-				sortedDependencies.push({
-					factory: pair1[0],
-					dependencies: pair2[1]
+			// This is nested so we need to allow one additional task
+			this.processDependenciesQueue.increaseParallelism();
+
+			for (const item of sortedDependencies) {
+				inProgressTransitive++;
+				// eslint-disable-next-line no-loop-func
+				this.handleModuleCreation(item, (err) => {
+					// In V8, the Error objects keep a reference to the functions on the stack. These warnings &
+					// errors are created inside closures that keep a reference to the Compilation, so errors are
+					// leaking the Compilation object.
+					if (err && this.bail) {
+						if (inProgressTransitive <= 0) return;
+						inProgressTransitive = -1;
+						// eslint-disable-next-line no-self-assign
+						err.stack = err.stack;
+						onTransitiveTasksFinished(err);
+						return;
+					}
+					if (--inProgressTransitive === 0) onTransitiveTasksFinished();
 				});
 			}
-		}
+			if (--inProgressTransitive === 0) onTransitiveTasksFinished();
+		};
 
-		this.addModuleDependencies(
-			module,
-			sortedDependencies,
-			this.bail,
-			null,
-			true,
-			callback
-		);
-	}
-
-	addModuleDependencies(
-		module,
-		dependencies,
-		bail,
-		cacheGroup,
-		recursive,
-		callback
-	) {
-		const start = this.profile && Date.now();
-		const currentProfile = this.profile && {};
-
-		asyncLib.forEach(
-			dependencies,
-			(item, callback) => {
-				const dependencies = item.dependencies;
-
-				const errorAndCallback = err => {
-					err.origin = module;
-					err.dependencies = dependencies;
-					this.errors.push(err);
-					if (bail) {
-						callback(err);
-					} else {
-						callback();
-					}
-				};
-				const warningAndCallback = err => {
-					err.origin = module;
-					this.warnings.push(err);
-					callback();
-				};
+		/**
+		 * On transitive tasks finished.
+		 * @param {WebpackError=} err error
+		 * @returns {void}
+		 */
+		const onTransitiveTasksFinished = (err) => {
+			if (err) return callback(err);
+			this.processDependenciesQueue.decreaseParallelism();
 
-				const semaphore = this.semaphore;
-				semaphore.acquire(() => {
-					const factory = item.factory;
-					factory.create(
-						{
-							contextInfo: {
-								issuer: module.nameForCondition && module.nameForCondition(),
-								compiler: this.compiler.name
-							},
-							resolveOptions: module.resolveOptions,
-							context: module.context,
-							dependencies: dependencies
-						},
-						(err, dependentModule) => {
-							let afterFactory;
-
-							const isOptional = () => {
-								return dependencies.every(d => d.optional);
-							};
+			return callback();
+		};
 
-							const errorOrWarningAndCallback = err => {
-								if (isOptional()) {
-									return warningAndCallback(err);
-								} else {
-									return errorAndCallback(err);
-								}
-							};
+		/**
+		 * Process dependency.
+		 * @param {Dependency} dep dependency
+		 * @param {number} index index in block
+		 * @returns {void}
+		 */
+		const processDependency = (dep, index) => {
+			this.moduleGraph.setParents(dep, currentBlock, module, index);
+			if (
+				hasLazyBarrel &&
+				// TODO remove in webpack 6
+				// It may be missing on custom dependency types not extending the base Dependency
+				"isLazy" in dep &&
+				dep.isLazy()
+			) {
+				return;
+			}
 
+			if (this._unsafeCache) {
+				try {
+					const unsafeCachedModule = unsafeCacheDependencies.get(dep);
+					if (unsafeCachedModule === null) return;
+					if (unsafeCachedModule !== undefined) {
+						if (
+							this._restoredUnsafeCacheModuleEntries.has(unsafeCachedModule)
+						) {
+							this._handleExistingModuleFromUnsafeCache(
+								module,
+								dep,
+								unsafeCachedModule
+							);
+							this._lazyBarrelController.unlazyForDependency(
+								unsafeCachedModule,
+								dep,
+								sortedDependencies
+							);
+							return;
+						}
+						const identifier = unsafeCachedModule.identifier();
+						const cachedModule =
+							this._restoredUnsafeCacheEntries.get(identifier);
+						if (cachedModule !== undefined) {
+							// update unsafe cache to new module
+							unsafeCacheDependencies.set(dep, cachedModule);
+							this._handleExistingModuleFromUnsafeCache(
+								module,
+								dep,
+								cachedModule
+							);
+							this._lazyBarrelController.unlazyForDependency(
+								cachedModule,
+								dep,
+								sortedDependencies
+							);
+							return;
+						}
+						inProgressSorting++;
+						this._modulesCache.get(identifier, null, (err, cachedModule) => {
 							if (err) {
-								semaphore.release();
-								return errorOrWarningAndCallback(
-									new ModuleNotFoundError(module, err)
+								if (inProgressSorting <= 0) return;
+								inProgressSorting = -1;
+								onDependenciesSorted(/** @type {WebpackError} */ (err));
+								return;
+							}
+							try {
+								if (!this._restoredUnsafeCacheEntries.has(identifier)) {
+									const data = unsafeCacheData.get(cachedModule);
+									if (data === undefined) {
+										processDependencyForResolving(dep);
+										if (--inProgressSorting === 0) onDependenciesSorted();
+										return;
+									}
+									if (cachedModule !== unsafeCachedModule) {
+										unsafeCacheDependencies.set(dep, cachedModule);
+									}
+									cachedModule.restoreFromUnsafeCache(
+										data,
+										this.params.normalModuleFactory,
+										this.params
+									);
+									this._restoredUnsafeCacheEntries.set(
+										identifier,
+										cachedModule
+									);
+									this._restoredUnsafeCacheModuleEntries.add(cachedModule);
+									if (!this.modules.has(cachedModule)) {
+										inProgressTransitive++;
+										this._handleNewModuleFromUnsafeCache(
+											module,
+											dep,
+											cachedModule,
+											(err) => {
+												if (err) {
+													if (inProgressTransitive <= 0) return;
+													inProgressTransitive = -1;
+													onTransitiveTasksFinished(err);
+												}
+												if (--inProgressTransitive === 0) {
+													return onTransitiveTasksFinished();
+												}
+											}
+										);
+										if (--inProgressSorting === 0) onDependenciesSorted();
+										return;
+									}
+								}
+								if (unsafeCachedModule !== cachedModule) {
+									unsafeCacheDependencies.set(dep, cachedModule);
+								}
+								this._handleExistingModuleFromUnsafeCache(
+									module,
+									dep,
+									cachedModule
+								); // a3
+								this._lazyBarrelController.unlazyForDependency(
+									cachedModule,
+									dep,
+									sortedDependencies
 								);
+							} catch (err) {
+								if (inProgressSorting <= 0) return;
+								inProgressSorting = -1;
+								onDependenciesSorted(/** @type {WebpackError} */ (err));
+								return;
 							}
-							if (!dependentModule) {
-								semaphore.release();
-								return process.nextTick(callback);
+							if (--inProgressSorting === 0) onDependenciesSorted();
+						});
+						return;
+					}
+				} catch (err) {
+					// eslint-disable-next-line no-console
+					console.error(err);
+				}
+			}
+			processDependencyForResolving(dep);
+		};
+
+		/**
+		 * Process dependency for resolving.
+		 * @param {Dependency} dep dependency
+		 * @returns {void}
+		 */
+		const processDependencyForResolving = (dep) => {
+			const resourceIdent = dep.getResourceIdentifier();
+			if (resourceIdent !== undefined && resourceIdent !== null) {
+				const category = dep.category;
+				const constructor =
+					/** @type {DependencyConstructor} */
+					(dep.constructor);
+				if (factoryCacheKey === constructor) {
+					// Fast path 1: same constructor as prev item
+					if (listCacheKey1 === category && listCacheKey2 === resourceIdent) {
+						// Super fast path 1: also same resource
+						listCacheValue.push(dep);
+						return;
+					}
+				} else {
+					const factory = this.dependencyFactories.get(constructor);
+					if (factory === undefined) {
+						throw new Error(
+							`No module factory available for dependency type: ${constructor.name}`
+						);
+					}
+					if (factoryCacheKey2 === factory) {
+						// Fast path 2: same factory as prev item
+						factoryCacheKey = constructor;
+						if (listCacheKey1 === category && listCacheKey2 === resourceIdent) {
+							// Super fast path 2: also same resource
+							listCacheValue.push(dep);
+							return;
+						}
+					} else {
+						// Slow path
+						if (factoryCacheKey2 !== undefined) {
+							// Archive last cache entry
+							if (dependencies === undefined) dependencies = new Map();
+							dependencies.set(
+								factoryCacheKey2,
+								/** @type {FactoryCacheValue} */ (factoryCacheValue)
+							);
+							factoryCacheValue = dependencies.get(factory);
+							if (factoryCacheValue === undefined) {
+								factoryCacheValue = new Map();
 							}
-							if (currentProfile) {
-								afterFactory = Date.now();
-								currentProfile.factory = afterFactory - start;
+						} else {
+							factoryCacheValue = new Map();
+						}
+						factoryCacheKey = constructor;
+						factoryCacheKey2 = factory;
+					}
+				}
+				// Here webpack is using heuristic that assumes
+				// mostly esm dependencies would be used
+				// so we don't allocate extra string for them
+				const cacheKey =
+					category === esmDependencyCategory
+						? resourceIdent
+						: `${category}${resourceIdent}`;
+				let list = /** @type {FactoryCacheValue} */ (factoryCacheValue).get(
+					cacheKey
+				);
+				if (list === undefined) {
+					/** @type {FactoryCacheValue} */
+					(factoryCacheValue).set(cacheKey, (list = []));
+					const newItem = {
+						factory: factoryCacheKey2,
+						dependencies: list,
+						context: dep.getContext(),
+						originModule: module
+					};
+					if (hasLowPriorityDependencies) {
+						let insertIndex = sortedDependencies.length;
+						while (insertIndex > 0) {
+							const item = sortedDependencies[insertIndex - 1];
+							const isAllLowPriorityDependencies = item.dependencies.every(
+								Dependency.isLowPriorityDependency
+							);
+							if (isAllLowPriorityDependencies) {
+								insertIndex--;
+							} else {
+								break;
 							}
+						}
+						sortedDependencies.splice(insertIndex, 0, newItem);
+					} else {
+						sortedDependencies.push(newItem);
+					}
+				}
+				list.push(dep);
+				listCacheKey1 = category;
+				listCacheKey2 = resourceIdent;
+				listCacheValue = list;
+			}
+		};
 
-							const iterationDependencies = depend => {
-								for (let index = 0; index < depend.length; index++) {
-									const dep = depend[index];
-									dep.module = dependentModule;
-									dependentModule.addReason(module, dep);
-								}
-							};
+		try {
+			/** @type {DependenciesBlock[]} */
+			const queue = [module];
+			do {
+				const block = /** @type {DependenciesBlock} */ (queue.pop());
+				if (block.dependencies) {
+					currentBlock = block;
+					let i = 0;
+					for (const dep of block.dependencies) processDependency(dep, i++);
+				}
+				if (block.blocks) {
+					for (const b of block.blocks) queue.push(b);
+				}
+			} while (queue.length !== 0);
+		} catch (err) {
+			return callback(/** @type {WebpackError} */ (err));
+		}
 
-							const addModuleResult = this.addModule(
-								dependentModule,
-								cacheGroup
-							);
-							dependentModule = addModuleResult.module;
-							iterationDependencies(dependencies);
+		if (--inProgressSorting === 0) onDependenciesSorted();
+	}
 
-							const afterBuild = () => {
-								if (currentProfile) {
-									const afterBuilding = Date.now();
-									currentProfile.building = afterBuilding - afterFactory;
-								}
+	/**
+	 * Handle new module from unsafe cache.
+	 * @private
+	 * @param {Module} originModule original module
+	 * @param {Dependency} dependency dependency
+	 * @param {Module} module cached module
+	 * @param {Callback} callback callback
+	 */
+	_handleNewModuleFromUnsafeCache(originModule, dependency, module, callback) {
+		const moduleGraph = this.moduleGraph;
 
-								if (recursive && addModuleResult.dependencies) {
-									this.processModuleDependencies(dependentModule, callback);
-								} else {
-									return callback();
-								}
-							};
+		moduleGraph.setResolvedModule(originModule, dependency, module);
 
-							if (addModuleResult.issuer) {
-								if (currentProfile) {
-									dependentModule.profile = currentProfile;
-								}
+		moduleGraph.setIssuerIfUnset(
+			module,
+			originModule !== undefined ? originModule : null
+		);
 
-								dependentModule.issuer = module;
-							} else {
-								if (this.profile) {
-									if (module.profile) {
-										const time = Date.now() - start;
-										if (
-											!module.profile.dependencies ||
-											time > module.profile.dependencies
-										) {
-											module.profile.dependencies = time;
-										}
-									}
-								}
-							}
+		this._modules.set(module.identifier(), module);
+		this.modules.add(module);
+		if (this._backCompat) {
+			ModuleGraph.setModuleGraphForModule(module, this.moduleGraph);
+		}
 
-							if (addModuleResult.build) {
-								this.buildModule(
-									dependentModule,
-									isOptional(),
-									module,
-									dependencies,
-									err => {
-										if (err) {
-											semaphore.release();
-											return errorOrWarningAndCallback(err);
-										}
+		this._handleModuleBuildAndDependencies(
+			originModule,
+			module,
+			true,
+			false,
+			[dependency],
+			callback
+		);
+	}
 
-										if (currentProfile) {
-											const afterBuilding = Date.now();
-											currentProfile.building = afterBuilding - afterFactory;
-										}
+	/**
+	 * Handle existing module from unsafe cache.
+	 * @private
+	 * @param {Module} originModule original modules
+	 * @param {Dependency} dependency dependency
+	 * @param {Module} module cached module
+	 */
+	_handleExistingModuleFromUnsafeCache(originModule, dependency, module) {
+		const moduleGraph = this.moduleGraph;
+
+		moduleGraph.setResolvedModule(originModule, dependency, module);
+	}
 
-										semaphore.release();
-										afterBuild();
-									}
-								);
-							} else {
-								semaphore.release();
-								this.waitForBuildingFinished(dependentModule, afterBuild);
-							}
+	/**
+	 * Processes the provided factorize module option.
+	 * @param {FactorizeModuleOptions} options options
+	 * @param {ModuleOrModuleFactoryResultCallback} callback callback
+	 * @returns {void}
+	 */
+	_factorizeModule(
+		{
+			currentProfile,
+			factory,
+			dependencies,
+			originModule,
+			factoryResult,
+			contextInfo,
+			context
+		},
+		callback
+	) {
+		if (currentProfile !== undefined) {
+			currentProfile.markFactoryStart();
+		}
+		factory.create(
+			{
+				contextInfo: {
+					issuer: originModule
+						? /** @type {NameForCondition} */ (originModule.nameForCondition())
+						: "",
+					issuerLayer: originModule ? originModule.layer : null,
+					compiler: this.compiler.name,
+					...contextInfo
+				},
+				resolveOptions: originModule ? originModule.resolveOptions : undefined,
+				context:
+					context ||
+					(originModule
+						? /** @type {string} */ (originModule.context)
+						: this.compiler.context),
+				dependencies
+			},
+			(err, result) => {
+				if (result) {
+					// TODO webpack 6: remove
+					// For backward-compat
+					if (result.module === undefined && result instanceof Module) {
+						result = {
+							module: result
+						};
+					}
+					if (!factoryResult) {
+						const {
+							fileDependencies,
+							contextDependencies,
+							missingDependencies
+						} = result;
+						if (fileDependencies) {
+							this.fileDependencies.addAll(fileDependencies);
+						}
+						if (contextDependencies) {
+							this.contextDependencies.addAll(contextDependencies);
 						}
+						if (missingDependencies) {
+							this.missingDependencies.addAll(missingDependencies);
+						}
+					}
+				}
+				if (err) {
+					const notFoundError = new (getModuleNotFoundError())(
+						originModule,
+						err,
+						/** @type {DependencyLocation} */
+						(dependencies.map((d) => d.loc).find(Boolean))
 					);
-				});
-			},
-			err => {
-				// In V8, the Error objects keep a reference to the functions on the stack. These warnings &
-				// errors are created inside closures that keep a reference to the Compilation, so errors are
-				// leaking the Compilation object.
+					return callback(notFoundError, factoryResult ? result : undefined);
+				}
+				if (!result) {
+					return callback();
+				}
 
-				if (err) {
-					err.stack = err.stack;
-					return callback(err);
+				if (currentProfile !== undefined) {
+					currentProfile.markFactoryEnd();
 				}
 
-				return process.nextTick(callback);
+				callback(null, factoryResult ? result : result.module);
 			}
 		);
 	}
 
-	_addModuleChain(context, dependency, onModule, callback) {
-		const start = this.profile && Date.now();
-		const currentProfile = this.profile && {};
+	/**
+	 * Processes the provided module callback.
+	 * @overload
+	 * @param {FactorizeModuleOptions & { factoryResult?: false }} options options
+	 * @param {ModuleCallback} callback callback
+	 * @returns {void}
+	 */
+	/**
+	 * Processes the provided module factory result callback.
+	 * @overload
+	 * @param {FactorizeModuleOptions & { factoryResult: true }} options options
+	 * @param {ModuleFactoryResultCallback} callback callback
+	 * @returns {void}
+	 */
+	/**
+	 * Processes the provided |.
+	 * @param {FactorizeModuleOptions & { factoryResult?: false } | FactorizeModuleOptions & { factoryResult: true }} options options
+	 * @param {ModuleCallback | ModuleFactoryResultCallback} callback callback
+	 */
+	factorizeModule(options, callback) {
+		this.factorizeQueue.add(
+			options,
+			/** @type {ModuleOrModuleFactoryResultCallback} */
+			(callback)
+		);
+	}
 
-		const errorAndCallback = this.bail
-			? err => {
-					callback(err);
-			  }
-			: err => {
-					err.dependencies = [dependency];
+	/**
+	 * Defines the handle module creation options type used by this module.
+	 * @typedef {object} HandleModuleCreationOptions
+	 * @property {ModuleFactory} factory
+	 * @property {Dependency[]} dependencies
+	 * @property {Module | null} originModule
+	 * @property {Partial=} contextInfo
+	 * @property {string=} context
+	 * @property {boolean=} recursive recurse into dependencies of the created module
+	 * @property {boolean=} connectOrigin connect the resolved module with the origin module
+	 * @property {boolean=} checkCycle check the cycle dependencies of the created module
+	 */
+
+	/**
+	 * Handle module creation.
+	 * @param {HandleModuleCreationOptions} options options object
+	 * @param {ModuleCallback} callback callback
+	 * @returns {void}
+	 */
+	handleModuleCreation(
+		{
+			factory,
+			dependencies,
+			originModule,
+			contextInfo,
+			context,
+			recursive = true,
+			connectOrigin = recursive,
+			checkCycle = !recursive
+		},
+		callback
+	) {
+		const moduleGraph = this.moduleGraph;
+
+		const currentProfile = this.profile ? new ModuleProfile() : undefined;
+
+		this.factorizeModule(
+			{
+				currentProfile,
+				factory,
+				dependencies,
+				factoryResult: true,
+				originModule,
+				contextInfo,
+				context
+			},
+			(err, factoryResult) => {
+				const applyFactoryResultDependencies = () => {
+					const { fileDependencies, contextDependencies, missingDependencies } =
+						/** @type {ModuleFactoryResult} */ (factoryResult);
+					if (fileDependencies) {
+						this.fileDependencies.addAll(fileDependencies);
+					}
+					if (contextDependencies) {
+						this.contextDependencies.addAll(contextDependencies);
+					}
+					if (missingDependencies) {
+						this.missingDependencies.addAll(missingDependencies);
+					}
+				};
+				if (err) {
+					if (factoryResult) applyFactoryResultDependencies();
+					if (dependencies.every((d) => d.optional)) {
+						this.warnings.push(err);
+						return callback();
+					}
 					this.errors.push(err);
-					callback();
-			  };
+					return callback(err);
+				}
 
-		if (
-			typeof dependency !== "object" ||
-			dependency === null ||
-			!dependency.constructor
-		) {
-			throw new Error("Parameter 'dependency' must be a Dependency");
-		}
+				const newModule =
+					/** @type {ModuleFactoryResult} */
+					(factoryResult).module;
 
-		const moduleFactory = this.dependencyFactories.get(dependency.constructor);
-		if (!moduleFactory) {
-			throw new Error(
-				`No dependency factory available for this dependency type: ${
-					dependency.constructor.name
-				}`
-			);
-		}
+				if (!newModule) {
+					applyFactoryResultDependencies();
+					return callback();
+				}
+				if (currentProfile !== undefined) {
+					moduleGraph.setProfile(newModule, currentProfile);
+				}
 
-		this.semaphore.acquire(() => {
-			moduleFactory.create(
-				{
-					contextInfo: {
-						issuer: "",
-						compiler: this.compiler.name
-					},
-					context: context,
-					dependencies: [dependency]
-				},
-				(err, module) => {
+				this.addModule(newModule, (err, _module) => {
 					if (err) {
-						this.semaphore.release();
-						return errorAndCallback(new EntryModuleNotFoundError(err));
-					}
-
-					let afterFactory;
+						applyFactoryResultDependencies();
+						if (!err.module) {
+							err.module = _module;
+						}
+						this.errors.push(err);
 
-					if (currentProfile) {
-						afterFactory = Date.now();
-						currentProfile.factory = afterFactory - start;
+						return callback(err);
 					}
 
-					const addModuleResult = this.addModule(module);
-					module = addModuleResult.module;
+					const module =
+						/** @type {ModuleWithRestoreFromUnsafeCache} */
+						(_module);
 
-					onModule(module);
-
-					dependency.module = module;
-					module.addReason(null, dependency);
-
-					const afterBuild = () => {
-						if (currentProfile) {
-							const afterBuilding = Date.now();
-							currentProfile.building = afterBuilding - afterFactory;
+					if (
+						this._unsafeCache &&
+						/** @type {ModuleFactoryResult} */
+						(factoryResult).cacheable !== false &&
+						module.restoreFromUnsafeCache &&
+						this._unsafeCachePredicate(module)
+					) {
+						const unsafeCacheableModule =
+							/** @type {ModuleWithRestoreFromUnsafeCache} */
+							(module);
+						for (const dependency of dependencies) {
+							moduleGraph.setResolvedModule(
+								connectOrigin ? originModule : null,
+								dependency,
+								unsafeCacheableModule
+							);
+							unsafeCacheDependencies.set(dependency, unsafeCacheableModule);
+						}
+						if (!unsafeCacheData.has(unsafeCacheableModule)) {
+							unsafeCacheData.set(
+								unsafeCacheableModule,
+								unsafeCacheableModule.getUnsafeCacheData()
+							);
+						}
+					} else {
+						applyFactoryResultDependencies();
+						for (const dependency of dependencies) {
+							moduleGraph.setResolvedModule(
+								connectOrigin ? originModule : null,
+								dependency,
+								module
+							);
 						}
+					}
 
-						if (addModuleResult.dependencies) {
-							this.processModuleDependencies(module, err => {
-								if (err) return callback(err);
-								callback(null, module);
-							});
+					moduleGraph.setIssuerIfUnset(
+						module,
+						originModule !== undefined ? originModule : null
+					);
+					if (module !== newModule && currentProfile !== undefined) {
+						const otherProfile = moduleGraph.getProfile(module);
+						if (otherProfile !== undefined) {
+							currentProfile.mergeInto(otherProfile);
 						} else {
-							return callback(null, module);
+							moduleGraph.setProfile(module, currentProfile);
 						}
-					};
+					}
 
-					if (addModuleResult.issuer) {
-						if (currentProfile) {
-							module.profile = currentProfile;
+					this._handleModuleBuildAndDependencies(
+						originModule,
+						module,
+						recursive,
+						checkCycle,
+						dependencies,
+						callback
+					);
+				});
+			}
+		);
+	}
+
+	/**
+	 * Handle module build and dependencies.
+	 * @private
+	 * @param {Module | null} originModule original module
+	 * @param {Module} module module
+	 * @param {boolean} recursive true if make it recursive, otherwise false
+	 * @param {boolean} checkCycle true if need to check cycle, otherwise false
+	 * @param {Dependency[]} dependencies the dependencies that resolved to the module (lazy barrel)
+	 * @param {ModuleCallback} callback callback
+	 * @returns {void}
+	 */
+	_handleModuleBuildAndDependencies(
+		originModule,
+		module,
+		recursive,
+		checkCycle,
+		dependencies,
+		callback
+	) {
+		// Check for cycles when build is trigger inside another build
+		/** @type {Set | undefined} */
+		let creatingModuleDuringBuildSet;
+		if (
+			checkCycle &&
+			this.buildQueue.isProcessing(/** @type {Module} */ (originModule))
+		) {
+			// Track build dependency
+			creatingModuleDuringBuildSet = this.creatingModuleDuringBuild.get(
+				/** @type {Module} */
+				(originModule)
+			);
+			if (creatingModuleDuringBuildSet === undefined) {
+				/** @type {Set} */
+				creatingModuleDuringBuildSet = new Set();
+				this.creatingModuleDuringBuild.set(
+					/** @type {Module} */
+					(originModule),
+					creatingModuleDuringBuildSet
+				);
+			}
+			creatingModuleDuringBuildSet.add(module);
+
+			// When building is blocked by another module
+			// search for a cycle, cancel the cycle by throwing
+			// an error (otherwise this would deadlock)
+			const blockReasons = this.creatingModuleDuringBuild.get(module);
+			if (blockReasons !== undefined) {
+				const set = new Set(blockReasons);
+				for (const item of set) {
+					const blockReasons = this.creatingModuleDuringBuild.get(item);
+					if (blockReasons !== undefined) {
+						for (const m of blockReasons) {
+							if (m === module) {
+								return callback(new (getBuildCycleError())(module));
+							}
+							set.add(m);
 						}
 					}
+				}
+			}
+		}
 
-					if (addModuleResult.build) {
-						this.buildModule(module, false, null, null, err => {
-							if (err) {
-								this.semaphore.release();
-								return errorAndCallback(err);
-							}
+		this.buildModule(module, (err) => {
+			if (creatingModuleDuringBuildSet !== undefined) {
+				creatingModuleDuringBuildSet.delete(module);
+			}
+			if (err) {
+				if (!err.module) {
+					err.module = module;
+				}
+				this.errors.push(err);
 
-							if (currentProfile) {
-								const afterBuilding = Date.now();
-								currentProfile.building = afterBuilding - afterFactory;
-							}
+				return callback(err);
+			}
 
-							this.semaphore.release();
-							afterBuild();
-						});
-					} else {
-						this.semaphore.release();
-						this.waitForBuildingFinished(module, afterBuild);
+			if (!recursive) {
+				this.processModuleDependenciesNonRecursive(module);
+				callback(null, module);
+				return;
+			}
+
+			// lazy barrel:
+			// 1. first call: barrel module is not classified yet, so this records the requested
+			//    ids and returns undefined; the targets get built below when
+			//    `processModuleDependencies` runs `_lazyBarrelController.classify` and replays them.
+			// 2. later call: barrel module is already built, so `processModuleDependencies` won't
+			//    re-walk it; the newly requested targets must be built here.
+			const unlazyItems = this._lazyBarrelController.request(
+				module,
+				dependencies
+			);
+
+			if (unlazyItems !== undefined) {
+				let inProgress = unlazyItems.length + 1; // +1 = the module's own dep processing
+				let errored = false;
+				/**
+				 * @param {(WebpackError | null)=} err error
+				 * @returns {void}
+				 */
+				const onJobDone = (err) => {
+					if (errored) return;
+					if (err) {
+						errored = true;
+						return callback(err);
 					}
+					if (--inProgress === 0) callback(null, module);
+				};
+				for (const item of unlazyItems) {
+					this.handleModuleCreation(item, (err) => {
+						onJobDone(err && this.bail ? err : null);
+					});
 				}
-			);
+				if (this.processDependenciesQueue.isProcessing(module)) {
+					onJobDone();
+				} else {
+					this.processModuleDependencies(module, onJobDone);
+				}
+			} else {
+				// This avoids deadlocks for circular dependencies
+				if (this.processDependenciesQueue.isProcessing(module)) {
+					return callback(null, module);
+				}
+
+				this.processModuleDependencies(module, (err) => {
+					if (err) {
+						return callback(err);
+					}
+					callback(null, module);
+				});
+			}
 		});
 	}
 
-	addEntry(context, entry, name, callback) {
-		const slot = {
-			name: name,
-			request: entry.request,
-			module: null
-		};
-		this._preparedEntrypoints.push(slot);
-		this._addModuleChain(
+	/**
+	 * Adds the provided string to the compilation.
+	 * @param {string} context context string path
+	 * @param {Dependency} dependency dependency used to create Module chain
+	 * @param {ModuleCallback} callback callback for when module chain is complete
+	 * @returns {void} will throw if dependency instance is not a valid Dependency
+	 */
+	addModuleChain(context, dependency, callback) {
+		return this.addModuleTree({ context, dependency }, callback);
+	}
+
+	/**
+	 * Adds the provided object to the compilation.
+	 * @param {object} options options
+	 * @param {string} options.context context string path
+	 * @param {Dependency} options.dependency dependency used to create Module chain
+	 * @param {Partial=} options.contextInfo additional context info for the root module
+	 * @param {ModuleCallback} callback callback for when module chain is complete
+	 * @returns {void} will throw if dependency instance is not a valid Dependency
+	 */
+	addModuleTree({ context, dependency, contextInfo }, callback) {
+		if (
+			typeof dependency !== "object" ||
+			dependency === null ||
+			!dependency.constructor
+		) {
+			return callback(
+				new WebpackError("Parameter 'dependency' must be a Dependency")
+			);
+		}
+		const Dep =
+			/** @type {DependencyConstructor} */
+			(dependency.constructor);
+		const moduleFactory = this.dependencyFactories.get(Dep);
+		if (!moduleFactory) {
+			return callback(
+				new WebpackError(
+					`No dependency factory available for this dependency type: ${dependency.constructor.name}`
+				)
+			);
+		}
+
+		this.handleModuleCreation(
+			{
+				factory: moduleFactory,
+				dependencies: [dependency],
+				originModule: null,
+				contextInfo,
+				context
+			},
+			(err, result) => {
+				if (err && this.bail) {
+					callback(err);
+					this.buildQueue.stop();
+					this.rebuildQueue.stop();
+					this.processDependenciesQueue.stop();
+					this.factorizeQueue.stop();
+				} else if (!err && result) {
+					callback(null, result);
+				} else {
+					callback();
+				}
+			}
+		);
+	}
+
+	/**
+	 * Adds the provided string to the compilation.
+	 * @param {string} context context path for entry
+	 * @param {Dependency} entry entry dependency that should be followed
+	 * @param {string | EntryOptions} optionsOrName options or deprecated name of entry
+	 * @param {ModuleCallback} callback callback function
+	 * @returns {void} returns
+	 */
+	addEntry(context, entry, optionsOrName, callback) {
+		// TODO webpack 6 remove
+		const options =
+			typeof optionsOrName === "object"
+				? optionsOrName
+				: { name: optionsOrName };
+
+		this._addEntryItem(context, entry, "dependencies", options, callback);
+	}
+
+	/**
+	 * Adds the provided string to the compilation.
+	 * @param {string} context context path for entry
+	 * @param {Dependency} dependency dependency that should be followed
+	 * @param {EntryOptions} options options
+	 * @param {ModuleCallback} callback callback function
+	 * @returns {void} returns
+	 */
+	addInclude(context, dependency, options, callback) {
+		this._addEntryItem(
 			context,
-			entry,
-			module => {
-				this.entries.push(module);
+			dependency,
+			"includeDependencies",
+			options,
+			callback
+		);
+	}
+
+	/**
+	 * Adds the provided string to the compilation.
+	 * @param {string} context context path for entry
+	 * @param {Dependency} entry entry dependency that should be followed
+	 * @param {"dependencies" | "includeDependencies"} target type of entry
+	 * @param {EntryOptions} options options
+	 * @param {ModuleCallback} callback callback function
+	 * @returns {void} returns
+	 */
+	_addEntryItem(context, entry, target, options, callback) {
+		const { name } = options;
+		/** @type {EntryData | undefined} */
+		let entryData =
+			name !== undefined ? this.entries.get(name) : this.globalEntry;
+		if (entryData === undefined) {
+			entryData = {
+				dependencies: [],
+				includeDependencies: [],
+				options: {
+					name: undefined,
+					...options
+				}
+			};
+			entryData[target].push(entry);
+			this.entries.set(
+				/** @type {NonNullable} */
+				(name),
+				entryData
+			);
+		} else {
+			entryData[target].push(entry);
+			for (const key_ of Object.keys(options)) {
+				const key = /** @type {keyof EntryOptions} */ (key_);
+				if (options[key] === undefined) continue;
+				if (entryData.options[key] === options[key]) continue;
+				if (
+					Array.isArray(entryData.options[key]) &&
+					Array.isArray(options[key]) &&
+					arrayEquals(entryData.options[key], options[key])
+				) {
+					continue;
+				}
+				if (entryData.options[key] === undefined) {
+					/** @type {EntryOptions[keyof EntryOptions]} */
+					(entryData.options[key]) = options[key];
+				} else {
+					return callback(
+						new WebpackError(
+							`Conflicting entry option ${key} = ${entryData.options[key]} vs ${options[key]}`
+						)
+					);
+				}
+			}
+		}
+
+		this.hooks.addEntry.call(entry, options);
+
+		this.addModuleTree(
+			{
+				context,
+				dependency: entry,
+				contextInfo: entryData.options.layer
+					? { issuerLayer: entryData.options.layer }
+					: undefined
 			},
 			(err, module) => {
 				if (err) {
+					this.hooks.failedEntry.call(entry, options, err);
 					return callback(err);
 				}
+				this.hooks.succeedEntry.call(
+					entry,
+					options,
+					/** @type {Module} */
+					(module)
+				);
+				return callback(null, module);
+			}
+		);
+	}
 
-				if (module) {
-					slot.module = module;
+	/**
+	 * Processes the provided module.
+	 * @param {Module} module module to be rebuilt
+	 * @param {ModuleCallback} callback callback when module finishes rebuilding
+	 * @returns {void}
+	 */
+	rebuildModule(module, callback) {
+		this.rebuildQueue.add(module, callback);
+	}
+
+	/**
+	 * Processes the provided module.
+	 * @param {Module} module module to be rebuilt
+	 * @param {ModuleCallback} callback callback when module finishes rebuilding
+	 * @returns {void}
+	 */
+	_rebuildModule(module, callback) {
+		this.hooks.rebuildModule.call(module);
+		const oldDependencies = [...module.dependencies];
+		const oldBlocks = [...module.blocks];
+		module.invalidateBuild();
+		this.buildQueue.invalidate(module);
+		this.buildModule(module, (err) => {
+			if (err) {
+				return this.hooks.finishRebuildingModule.callAsync(module, (err2) => {
+					if (err2) {
+						callback(
+							makeWebpackError(err2, "Compilation.hooks.finishRebuildingModule")
+						);
+						return;
+					}
+					callback(err);
+				});
+			}
+
+			this.processDependenciesQueue.invalidate(module);
+			this.moduleGraph.unfreeze();
+			this.processModuleDependencies(module, (err) => {
+				if (err) return callback(err);
+				this.removeReasonsOfDependencyBlock(module, {
+					dependencies: oldDependencies,
+					blocks: oldBlocks
+				});
+				this.hooks.finishRebuildingModule.callAsync(module, (err2) => {
+					if (err2) {
+						callback(
+							makeWebpackError(err2, "Compilation.hooks.finishRebuildingModule")
+						);
+						return;
+					}
+					callback(null, module);
+				});
+			});
+		});
+	}
+
+	/**
+	 * Compute affected modules.
+	 * @private
+	 * @param {Set} modules modules
+	 */
+	_computeAffectedModules(modules) {
+		const moduleMemCacheCache = this.compiler.moduleMemCaches;
+		if (!moduleMemCacheCache) return;
+		if (!this.moduleMemCaches) {
+			this.moduleMemCaches = new Map();
+			this.moduleGraph.setModuleMemCaches(this.moduleMemCaches);
+		}
+		const { moduleGraph, moduleMemCaches } = this;
+		/** @type {Set} */
+		const affectedModules = new Set();
+		/** @type {Set} */
+		const infectedModules = new Set();
+		let statNew = 0;
+		let statChanged = 0;
+		let statUnchanged = 0;
+		let statReferencesChanged = 0;
+		let statWithoutBuild = 0;
+
+		/**
+		 * Compute references.
+		 * @param {Module} module module
+		 * @returns {WeakReferences | undefined} references
+		 */
+		const computeReferences = (module) => {
+			/** @type {WeakReferences | undefined} */
+			let references;
+			for (const connection of moduleGraph.getOutgoingConnections(module)) {
+				const d = connection.dependency;
+				const m = connection.module;
+				if (!d || !m || unsafeCacheDependencies.has(d)) continue;
+				if (references === undefined) references = new WeakMap();
+				references.set(d, m);
+			}
+			return references;
+		};
+
+		/**
+		 * Compares references.
+		 * @param {Module} module the module
+		 * @param {WeakReferences | undefined} references references
+		 * @returns {boolean} true, when the references differ
+		 */
+		const compareReferences = (module, references) => {
+			if (references === undefined) return true;
+			for (const connection of moduleGraph.getOutgoingConnections(module)) {
+				const d = connection.dependency;
+				if (!d) continue;
+				const entry = references.get(d);
+				if (entry === undefined) continue;
+				if (entry !== connection.module) return false;
+			}
+			return true;
+		};
+
+		const modulesWithoutCache = new Set(modules);
+		for (const [module, cachedMemCache] of moduleMemCacheCache) {
+			if (modulesWithoutCache.has(module)) {
+				const buildInfo = module.buildInfo;
+				if (buildInfo) {
+					if (cachedMemCache.buildInfo !== buildInfo) {
+						// use a new one
+						/** @type {MemCache} */
+						const memCache = new WeakTupleMap();
+						moduleMemCaches.set(module, memCache);
+						affectedModules.add(module);
+						cachedMemCache.buildInfo = buildInfo;
+						cachedMemCache.references = computeReferences(module);
+						cachedMemCache.memCache = memCache;
+						statChanged++;
+					} else if (!compareReferences(module, cachedMemCache.references)) {
+						// use a new one
+						/** @type {MemCache} */
+						const memCache = new WeakTupleMap();
+						moduleMemCaches.set(module, memCache);
+						affectedModules.add(module);
+						cachedMemCache.references = computeReferences(module);
+						cachedMemCache.memCache = memCache;
+						statReferencesChanged++;
+					} else {
+						// keep the old mem cache
+						moduleMemCaches.set(module, cachedMemCache.memCache);
+						statUnchanged++;
+					}
 				} else {
-					const idx = this._preparedEntrypoints.indexOf(slot);
-					this._preparedEntrypoints.splice(idx, 1);
+					infectedModules.add(module);
+					moduleMemCacheCache.delete(module);
+					statWithoutBuild++;
 				}
-				return callback(null, module);
+				modulesWithoutCache.delete(module);
+			} else {
+				moduleMemCacheCache.delete(module);
 			}
+		}
+
+		for (const module of modulesWithoutCache) {
+			const buildInfo = module.buildInfo;
+			if (buildInfo) {
+				// create a new entry
+				const memCache = new WeakTupleMap();
+				moduleMemCacheCache.set(module, {
+					buildInfo,
+					references: computeReferences(module),
+					memCache
+				});
+				moduleMemCaches.set(module, memCache);
+				affectedModules.add(module);
+				statNew++;
+			} else {
+				infectedModules.add(module);
+				statWithoutBuild++;
+			}
+		}
+
+		/** @type {Set} */
+		const directOnlyInfectedModules = new Set();
+		for (const module of infectedModules) {
+			for (const [
+				referencingModule,
+				connections
+			] of moduleGraph.getIncomingConnectionsByOriginModule(module)) {
+				if (!referencingModule) continue;
+				if (infectedModules.has(referencingModule)) continue;
+				const type = reduceAffectType(connections);
+				if (!type) continue;
+				if (type === true) {
+					directOnlyInfectedModules.add(referencingModule);
+				} else {
+					infectedModules.add(referencingModule);
+				}
+			}
+		}
+		for (const module of directOnlyInfectedModules) infectedModules.add(module);
+		/** @type {Set} */
+		const directOnlyAffectModules = new Set();
+		for (const module of affectedModules) {
+			for (const [
+				referencingModule,
+				connections
+			] of moduleGraph.getIncomingConnectionsByOriginModule(module)) {
+				if (!referencingModule) continue;
+				if (infectedModules.has(referencingModule)) continue;
+				if (affectedModules.has(referencingModule)) continue;
+				const type = reduceAffectType(connections);
+				if (!type) continue;
+				if (type === true) {
+					directOnlyAffectModules.add(referencingModule);
+				} else {
+					affectedModules.add(referencingModule);
+				}
+				/** @type {MemCache} */
+				const memCache = new WeakTupleMap();
+				const cache =
+					/** @type {ModuleMemCachesItem} */
+					(moduleMemCacheCache.get(referencingModule));
+				cache.memCache = memCache;
+				moduleMemCaches.set(referencingModule, memCache);
+			}
+		}
+		for (const module of directOnlyAffectModules) affectedModules.add(module);
+		this.logger.log(
+			`${Math.round(
+				(100 * (affectedModules.size + infectedModules.size)) /
+					this.modules.size
+			)}% (${affectedModules.size} affected + ${
+				infectedModules.size
+			} infected of ${
+				this.modules.size
+			}) modules flagged as affected (${statNew} new modules, ${statChanged} changed, ${statReferencesChanged} references changed, ${statUnchanged} unchanged, ${statWithoutBuild} were not built)`
 		);
 	}
 
-	prefetch(context, dependency, callback) {
-		this._addModuleChain(
-			context,
-			dependency,
-			module => {
-				module.prefetched = true;
-			},
-			callback
+	_computeAffectedModulesWithChunkGraph() {
+		const { moduleMemCaches } = this;
+		if (!moduleMemCaches) return;
+		const moduleMemCaches2 = (this.moduleMemCaches2 = new Map());
+		const { moduleGraph, chunkGraph } = this;
+		const key = "memCache2";
+		let statUnchanged = 0;
+		let statChanged = 0;
+		let statNew = 0;
+		/**
+		 * Compute references.
+		 * @param {Module} module module
+		 * @returns {References} references
+		 */
+		const computeReferences = (module) => {
+			const id = /** @type {ModuleId} */ (chunkGraph.getModuleId(module));
+			/** @type {Map | undefined} */
+			let modules;
+			/** @type {(ChunkId | null)[] | undefined} */
+			let blocks;
+			const outgoing = moduleGraph.getOutgoingConnectionsByModule(module);
+			if (outgoing !== undefined) {
+				for (const m of outgoing.keys()) {
+					if (!m) continue;
+					if (modules === undefined) modules = new Map();
+					modules.set(m, /** @type {ModuleId} */ (chunkGraph.getModuleId(m)));
+				}
+			}
+			if (module.blocks.length > 0) {
+				blocks = [];
+				const queue = [...module.blocks];
+				for (const block of queue) {
+					const chunkGroup = chunkGraph.getBlockChunkGroup(block);
+					if (chunkGroup) {
+						for (const chunk of chunkGroup.chunks) {
+							blocks.push(chunk.id);
+						}
+					} else {
+						blocks.push(null);
+					}
+					// eslint-disable-next-line prefer-spread
+					queue.push.apply(queue, block.blocks);
+				}
+			}
+			return {
+				id,
+				modules,
+				blocks,
+				sourceTypes: module.getReferencedSourceTypes()
+			};
+		};
+		/**
+		 * Compares references.
+		 * @param {Module} module module
+		 * @param {object} references references
+		 * @param {string | number} references.id id
+		 * @param {Map=} references.modules modules
+		 * @param {(string | number | null)[]=} references.blocks blocks
+		 * @param {SourceTypes=} references.sourceTypes connection-dependent source types
+		 * @returns {boolean} ok?
+		 */
+		const compareReferences = (
+			module,
+			{ id, modules, blocks, sourceTypes }
+		) => {
+			if (id !== chunkGraph.getModuleId(module)) return false;
+			if (modules !== undefined) {
+				for (const [module, id] of modules) {
+					if (chunkGraph.getModuleId(module) !== id) return false;
+				}
+			}
+			if (blocks !== undefined) {
+				const queue = [...module.blocks];
+				let i = 0;
+				for (const block of queue) {
+					const chunkGroup = chunkGraph.getBlockChunkGroup(block);
+					if (chunkGroup) {
+						for (const chunk of chunkGroup.chunks) {
+							if (i >= blocks.length || blocks[i++] !== chunk.id) return false;
+						}
+					} else if (i >= blocks.length || blocks[i++] !== null) {
+						return false;
+					}
+					// eslint-disable-next-line prefer-spread
+					queue.push.apply(queue, block.blocks);
+				}
+				if (i !== blocks.length) return false;
+			}
+			// getReferencedSourceTypes() returns interned type sets, compare by reference. #20800
+			if (
+				sourceTypes !== undefined &&
+				module.getReferencedSourceTypes() !== sourceTypes
+			) {
+				return false;
+			}
+			return true;
+		};
+
+		for (const [module, memCache] of moduleMemCaches) {
+			/** @type {{ references: References, memCache: MemCache } | undefined} */
+			const cache = memCache.get(key);
+			if (cache === undefined) {
+				/** @type {WeakTupleMap | undefined} */
+				const memCache2 = new WeakTupleMap();
+				memCache.set(key, {
+					references: computeReferences(module),
+					memCache: memCache2
+				});
+				moduleMemCaches2.set(module, memCache2);
+				statNew++;
+			} else if (!compareReferences(module, cache.references)) {
+				/** @type {WeakTupleMap | undefined} */
+				const memCache = new WeakTupleMap();
+				cache.references = computeReferences(module);
+				cache.memCache = memCache;
+				moduleMemCaches2.set(module, memCache);
+				statChanged++;
+			} else {
+				moduleMemCaches2.set(module, cache.memCache);
+				statUnchanged++;
+			}
+		}
+
+		this.logger.log(
+			`${Math.round(
+				(100 * statChanged) / (statNew + statChanged + statUnchanged)
+			)}% modules flagged as affected by chunk graph (${statNew} new modules, ${statChanged} changed, ${statUnchanged} unchanged)`
 		);
 	}
 
-	rebuildModule(module, thisCallback) {
-		let callbackList = this._rebuildingModules.get(module);
-		if (callbackList) {
-			callbackList.push(thisCallback);
-			return;
+	/**
+	 * Assigns a fresh dependency report cache token to every module whose
+	 * dependency errors/warnings could have changed since the previous
+	 * compilation: built modules and their (transitive) parents.
+	 * @private
+	 * @param {Set} modules modules
+	 * @returns {void}
+	 */
+	_flagModulesForDependencyReporting(modules) {
+		/** @type {DependencyReportCacheToken} */
+		const token = {};
+		const { moduleGraph, builtModules } = this;
+		/** @type {Set} */
+		const updatedModules = new Set();
+		for (const module of modules) {
+			if (
+				builtModules.has(module) ||
+				!dependencyReportCacheTokens.has(module)
+			) {
+				updatedModules.add(module);
+			}
+		}
+		// skip the parent walk when everything is updated anyway (initial build)
+		if (updatedModules.size !== modules.size) {
+			/** @type {Set} */
+			const directOnlyUpdatedModules = new Set();
+			for (const module of updatedModules) {
+				for (const [
+					referencingModule,
+					connections
+				] of moduleGraph.getIncomingConnectionsByOriginModule(module)) {
+					if (!referencingModule) continue;
+					if (updatedModules.has(referencingModule)) continue;
+					const type = reduceAffectType(connections);
+					if (!type) continue;
+					if (type === true) {
+						directOnlyUpdatedModules.add(referencingModule);
+					} else {
+						updatedModules.add(referencingModule);
+					}
+				}
+			}
+			for (const module of directOnlyUpdatedModules) {
+				updatedModules.add(module);
+			}
+		}
+		for (const module of updatedModules) {
+			dependencyReportCacheTokens.set(module, token);
+		}
+	}
+
+	/**
+	 * Processes the provided callback.
+	 * @param {Callback} callback callback
+	 */
+	finish(callback) {
+		this.factorizeQueue.clear();
+		if (this.profile) {
+			this.logger.time("finish module profiles");
+
+			const ParallelismFactorCalculator = require("./util/ParallelismFactorCalculator");
+
+			const p = new ParallelismFactorCalculator();
+			const moduleGraph = this.moduleGraph;
+			/** @type {Map} */
+			const modulesWithProfiles = new Map();
+			for (const module of this.modules) {
+				const profile = moduleGraph.getProfile(module);
+				if (!profile) continue;
+				modulesWithProfiles.set(module, profile);
+				p.range(
+					profile.buildingStartTime,
+					profile.buildingEndTime,
+					(f) => (profile.buildingParallelismFactor = f)
+				);
+				p.range(
+					profile.factoryStartTime,
+					profile.factoryEndTime,
+					(f) => (profile.factoryParallelismFactor = f)
+				);
+				p.range(
+					profile.integrationStartTime,
+					profile.integrationEndTime,
+					(f) => (profile.integrationParallelismFactor = f)
+				);
+				p.range(
+					profile.storingStartTime,
+					profile.storingEndTime,
+					(f) => (profile.storingParallelismFactor = f)
+				);
+				p.range(
+					profile.restoringStartTime,
+					profile.restoringEndTime,
+					(f) => (profile.restoringParallelismFactor = f)
+				);
+				if (profile.additionalFactoryTimes) {
+					for (const { start, end } of profile.additionalFactoryTimes) {
+						const influence = (end - start) / profile.additionalFactories;
+						p.range(
+							start,
+							end,
+							(f) =>
+								(profile.additionalFactoriesParallelismFactor += f * influence)
+						);
+					}
+				}
+			}
+			p.calculate();
+
+			const logger = this.getLogger("webpack.Compilation.ModuleProfile");
+			// Avoid coverage problems due indirect changes
+			/**
+			 * Processes the provided value.
+			 * @param {number} value value
+			 * @param {string} msg message
+			 */
+			/* istanbul ignore next */
+			const logByValue = (value, msg) => {
+				if (value > 1000) {
+					logger.error(msg);
+				} else if (value > 500) {
+					logger.warn(msg);
+				} else if (value > 200) {
+					logger.info(msg);
+				} else if (value > 30) {
+					logger.log(msg);
+				} else {
+					logger.debug(msg);
+				}
+			};
+			/**
+			 * Log normal summary.
+			 * @param {string} category a category
+			 * @param {(profile: ModuleProfile) => number} getDuration get duration callback
+			 * @param {(profile: ModuleProfile) => number} getParallelism get parallelism callback
+			 */
+			const logNormalSummary = (category, getDuration, getParallelism) => {
+				let sum = 0;
+				let max = 0;
+				for (const [module, profile] of modulesWithProfiles) {
+					const p = getParallelism(profile);
+					const d = getDuration(profile);
+					if (d === 0 || p === 0) continue;
+					const t = d / p;
+					sum += t;
+					if (t <= 10) continue;
+					logByValue(
+						t,
+						` | ${Math.round(t)} ms${
+							p >= 1.1 ? ` (parallelism ${Math.round(p * 10) / 10})` : ""
+						} ${category} > ${module.readableIdentifier(this.requestShortener)}`
+					);
+					max = Math.max(max, t);
+				}
+				if (sum <= 10) return;
+				logByValue(
+					Math.max(sum / 10, max),
+					`${Math.round(sum)} ms ${category}`
+				);
+			};
+			/**
+			 * Log by loaders summary.
+			 * @param {string} category a category
+			 * @param {(profile: ModuleProfile) => number} getDuration get duration callback
+			 * @param {(profile: ModuleProfile) => number} getParallelism get parallelism callback
+			 */
+			const logByLoadersSummary = (category, getDuration, getParallelism) => {
+				/** @type {Map} */
+				const map = new Map();
+				for (const [module, profile] of modulesWithProfiles) {
+					const list = getOrInsert(
+						map,
+						`${module.type}!${module.identifier().replace(/(!|^)[^!]*$/, "")}`,
+						() => []
+					);
+					list.push({ module, profile });
+				}
+
+				let sum = 0;
+				let max = 0;
+				for (const [key, modules] of map) {
+					let innerSum = 0;
+					let innerMax = 0;
+					for (const { module, profile } of modules) {
+						const p = getParallelism(profile);
+						const d = getDuration(profile);
+						if (d === 0 || p === 0) continue;
+						const t = d / p;
+						innerSum += t;
+						/* istanbul ignore next -- @preserve: only slow (>10ms) modules are logged, timing-dependent */
+						if (t > 10) {
+							logByValue(
+								t,
+								` |  | ${Math.round(t)} ms${
+									p >= 1.1 ? ` (parallelism ${Math.round(p * 10) / 10})` : ""
+								} ${category} > ${module.readableIdentifier(
+									this.requestShortener
+								)}`
+							);
+							innerMax = Math.max(innerMax, t);
+						}
+					}
+					sum += innerSum;
+					if (innerSum <= 10) continue;
+					const idx = key.indexOf("!");
+					const loaders = key.slice(idx + 1);
+					const moduleType = key.slice(0, idx);
+					const t = Math.max(innerSum / 10, innerMax);
+					logByValue(
+						t,
+						` | ${Math.round(innerSum)} ms ${category} > ${
+							loaders
+								? `${
+										modules.length
+									} x ${moduleType} with ${this.requestShortener.shorten(
+										loaders
+									)}`
+								: `${modules.length} x ${moduleType}`
+						}`
+					);
+					max = Math.max(max, t);
+				}
+				if (sum <= 10) return;
+				logByValue(
+					Math.max(sum / 10, max),
+					`${Math.round(sum)} ms ${category}`
+				);
+			};
+			logNormalSummary(
+				"resolve to new modules",
+				(p) => p.factory,
+				(p) => p.factoryParallelismFactor
+			);
+			logNormalSummary(
+				"resolve to existing modules",
+				(p) => p.additionalFactories,
+				(p) => p.additionalFactoriesParallelismFactor
+			);
+			logNormalSummary(
+				"integrate modules",
+				(p) => p.restoring,
+				(p) => p.restoringParallelismFactor
+			);
+			logByLoadersSummary(
+				"build modules",
+				(p) => p.building,
+				(p) => p.buildingParallelismFactor
+			);
+			logNormalSummary(
+				"store modules",
+				(p) => p.storing,
+				(p) => p.storingParallelismFactor
+			);
+			logNormalSummary(
+				"restore modules",
+				(p) => p.restoring,
+				(p) => p.restoringParallelismFactor
+			);
+			this.logger.timeEnd("finish module profiles");
 		}
-		this._rebuildingModules.set(module, (callbackList = [thisCallback]));
-
-		const callback = err => {
-			this._rebuildingModules.delete(module);
-			for (const cb of callbackList) {
-				cb(err);
+		this.logger.time("compute affected modules");
+		this._computeAffectedModules(this.modules);
+		this.logger.timeEnd("compute affected modules");
+		this.logger.time("finish modules");
+		const { modules, moduleMemCaches } = this;
+		this.hooks.finishModules.callAsync(modules, (err) => {
+			this.logger.timeEnd("finish modules");
+			if (err) return callback(/** @type {WebpackError} */ (err));
+
+			// extract warnings and errors from modules
+			this.moduleGraph.freeze("dependency errors");
+			this.logger.time("report dependency errors and warnings");
+			// moduleMemCaches has its own invalidation (incl. reference changes),
+			// so cache tokens are only used without it
+			const useReportCacheTokens = moduleMemCaches === undefined;
+			if (useReportCacheTokens) {
+				this._flagModulesForDependencyReporting(modules);
 			}
-		};
-
-		this.hooks.rebuildModule.call(module);
-		const oldDependencies = module.dependencies.slice();
-		const oldVariables = module.variables.slice();
-		const oldBlocks = module.blocks.slice();
-		module.unbuild();
-		this.buildModule(module, false, module, null, err => {
-			if (err) {
-				this.hooks.finishRebuildingModule.call(module);
-				return callback(err);
+			for (const module of modules) {
+				const memCache = moduleMemCaches && moduleMemCaches.get(module);
+				if (memCache && memCache.get("noWarningsOrErrors")) continue;
+				let token;
+				if (useReportCacheTokens) {
+					token = dependencyReportCacheTokens.get(module);
+					const cleanModules = modulesWithoutProblems.get(
+						/** @type {DependencyReportCacheToken} */ (token)
+					);
+					if (cleanModules !== undefined && cleanModules.has(module)) continue;
+				}
+				let hasProblems = this.reportDependencyErrorsAndWarnings(module, [
+					module
+				]);
+				const errors = /** @type {WebpackError[]} */ (module.getErrors());
+				if (errors !== undefined) {
+					for (const error of errors) {
+						if (!error.module) {
+							error.module = module;
+						}
+						this.errors.push(error);
+						hasProblems = true;
+					}
+				}
+				const warnings = /** @type {WebpackError[]} */ (module.getWarnings());
+				if (warnings !== undefined) {
+					for (const warning of warnings) {
+						if (!warning.module) {
+							warning.module = module;
+						}
+						this.warnings.push(warning);
+						hasProblems = true;
+					}
+				}
+				if (!hasProblems) {
+					if (memCache) memCache.set("noWarningsOrErrors", true);
+					if (useReportCacheTokens) {
+						let cleanModules = modulesWithoutProblems.get(
+							/** @type {DependencyReportCacheToken} */ (token)
+						);
+						if (cleanModules === undefined) {
+							modulesWithoutProblems.set(
+								/** @type {DependencyReportCacheToken} */ (token),
+								(cleanModules = new WeakSet())
+							);
+						}
+						cleanModules.add(module);
+					}
+				}
 			}
+			this.moduleGraph.unfreeze();
+			this.logger.timeEnd("report dependency errors and warnings");
 
-			this.processModuleDependencies(module, err => {
-				if (err) return callback(err);
-				this.removeReasonsOfDependencyBlock(module, {
-					dependencies: oldDependencies,
-					variables: oldVariables,
-					blocks: oldBlocks
-				});
-				this.hooks.finishRebuildingModule.call(module);
-				callback();
-			});
+			callback();
 		});
 	}
 
-	finish() {
-		const modules = this.modules;
-		this.hooks.finishModules.call(modules);
-
-		for (let index = 0; index < modules.length; index++) {
-			const module = modules[index];
-			this.reportDependencyErrorsAndWarnings(module, [module]);
-		}
-	}
-
 	unseal() {
 		this.hooks.unseal.call();
-		this.chunks.length = 0;
+		this.chunks.clear();
 		this.chunkGroups.length = 0;
 		this.namedChunks.clear();
 		this.namedChunkGroups.clear();
+		this.entrypoints.clear();
 		this.additionalChunkAssets.length = 0;
 		this.assets = {};
-		for (const module of this.modules) {
-			module.unseal();
-		}
+		this.assetsInfo.clear();
+		this._assetToChunkIndex = undefined;
+		this._assetToChunkAuxiliaryIndex = undefined;
+		this.moduleGraph.removeAllModuleAttributes();
+		this.moduleGraph.unfreeze();
+		this.moduleMemCaches2 = undefined;
 	}
 
+	/**
+	 * Processes the provided callback.
+	 * @param {Callback} callback signals when the call finishes
+	 * @returns {void}
+	 */
 	seal(callback) {
+		/**
+		 * Processes the provided err.
+		 * @param {WebpackError=} err err
+		 * @returns {void}
+		 */
+		const finalCallback = (err) => {
+			this.factorizeQueue.clear();
+			this.buildQueue.clear();
+			this.rebuildQueue.clear();
+			this.processDependenciesQueue.clear();
+			this.addModuleQueue.clear();
+			// lazy barrel only acts during make; release its bookkeeping now
+			this._lazyBarrelController.clear();
+			// unsafe-cache restore tables are only read during make; release them
+			// so they stop pinning restored modules for the rest of the compilation
+			this._restoredUnsafeCacheModuleEntries.clear();
+			this._restoredUnsafeCacheEntries.clear();
+			return callback(err);
+		};
+
+		if (this._backCompat) {
+			for (const module of this.modules) {
+				ChunkGraph.setChunkGraphForModule(module, this.chunkGraph);
+			}
+		}
+
 		this.hooks.seal.call();
 
-		while (
-			this.hooks.optimizeDependenciesBasic.call(this.modules) ||
-			this.hooks.optimizeDependencies.call(this.modules) ||
-			this.hooks.optimizeDependenciesAdvanced.call(this.modules)
-		) {
+		this.logger.time("optimize dependencies");
+		while (this.hooks.optimizeDependencies.call(this.modules)) {
 			/* empty */
 		}
 		this.hooks.afterOptimizeDependencies.call(this.modules);
-
-		this.nextFreeModuleIndex = 0;
-		this.nextFreeModuleIndex2 = 0;
-		for (const preparedEntrypoint of this._preparedEntrypoints) {
-			const module = preparedEntrypoint.module;
-			const name = preparedEntrypoint.name;
+		this.logger.timeEnd("optimize dependencies");
+
+		this.logger.time("create chunks");
+		this.hooks.beforeChunks.call();
+		this.moduleGraph.freeze("seal");
+		/** @type {Map} */
+		const chunkGraphInit = new Map();
+		for (const [name, { dependencies, includeDependencies, options }] of this
+			.entries) {
 			const chunk = this.addChunk(name);
-			const entrypoint = new Entrypoint(name);
-			entrypoint.setRuntimeChunk(chunk);
-			entrypoint.addOrigin(null, name, preparedEntrypoint.request);
+			if (options.filename) {
+				chunk.filenameTemplate = options.filename;
+			}
+			const entrypoint = new Entrypoint(options);
+			if (!options.dependOn && !options.runtime) {
+				entrypoint.setRuntimeChunk(chunk);
+			}
+			entrypoint.setEntrypointChunk(chunk);
 			this.namedChunkGroups.set(name, entrypoint);
 			this.entrypoints.set(name, entrypoint);
 			this.chunkGroups.push(entrypoint);
 
-			GraphHelpers.connectChunkGroupAndChunk(entrypoint, chunk);
-			GraphHelpers.connectChunkAndModule(chunk, module);
+			if (entrypoint.pushChunk(chunk)) {
+				chunk.addGroup(entrypoint);
+			}
+
+			/** @type {Set} */
+			const entryModules = new Set();
+			for (const dep of [...this.globalEntry.dependencies, ...dependencies]) {
+				entrypoint.addOrigin(
+					null,
+					{ name },
+					/** @type {Dependency & { request: string }} */
+					(dep).request
+				);
+
+				const module = this.moduleGraph.getModule(dep);
+				if (module) {
+					this.chunkGraph.connectChunkAndEntryModule(chunk, module, entrypoint);
+					entryModules.add(module);
+					const modulesList = chunkGraphInit.get(entrypoint);
+					if (modulesList === undefined) {
+						chunkGraphInit.set(entrypoint, [module]);
+					} else {
+						modulesList.push(module);
+					}
+				}
+			}
+
+			this.assignDepths(entryModules);
+
+			/**
+			 * Returns sorted deps.
+			 * @param {Dependency[]} deps deps
+			 * @returns {Module[]} sorted deps
+			 */
+			const mapAndSort = (deps) =>
+				/** @type {Module[]} */
+				(
+					deps.map((dep) => this.moduleGraph.getModule(dep)).filter(Boolean)
+				).sort(compareModulesByIdentifier);
+			const includedModules = [
+				...mapAndSort(this.globalEntry.includeDependencies),
+				...mapAndSort(includeDependencies)
+			];
+
+			let modulesList = chunkGraphInit.get(entrypoint);
+			if (modulesList === undefined) {
+				chunkGraphInit.set(entrypoint, (modulesList = []));
+			}
+			for (const module of includedModules) {
+				this.assignDepths([module]);
+				modulesList.push(module);
+			}
+		}
+		/** @type {Set} */
+		const runtimeChunks = new Set();
+		outer: for (const [
+			name,
+			{
+				options: { dependOn, runtime }
+			}
+		] of this.entries) {
+			if (dependOn && runtime) {
+				const err =
+					new WebpackError(`Entrypoint '${name}' has 'dependOn' and 'runtime' specified. This is not valid.
+Entrypoints that depend on other entrypoints do not have their own runtime.
+They will use the runtime(s) from referenced entrypoints instead.
+Remove the 'runtime' option from the entrypoint.`);
+				const entry = /** @type {Entrypoint} */ (this.entrypoints.get(name));
+				err.chunk = entry.getEntrypointChunk();
+				this.errors.push(err);
+			}
+			if (dependOn) {
+				const entry = /** @type {Entrypoint} */ (this.entrypoints.get(name));
+				const referencedChunks = entry
+					.getEntrypointChunk()
+					.getAllReferencedChunks();
+				for (const dep of dependOn) {
+					const dependency = this.entrypoints.get(dep);
+					if (!dependency) {
+						throw new Error(
+							`Entry ${name} depends on ${dep}, but this entry was not found`
+						);
+					}
+					if (referencedChunks.has(dependency.getEntrypointChunk())) {
+						const err = new WebpackError(
+							`Entrypoints '${name}' and '${dep}' use 'dependOn' to depend on each other in a circular way.`
+						);
+						const entryChunk = entry.getEntrypointChunk();
+						err.chunk = entryChunk;
+						this.errors.push(err);
+						entry.setRuntimeChunk(entryChunk);
+						continue outer;
+					}
 
-			chunk.entryModule = module;
-			chunk.name = name;
+					entry.addDependOn(dependency);
 
-			this.assignIndex(module);
-			this.assignDepth(module);
+					if (dependency.addChild(entry)) {
+						entry.addParent(dependency);
+					}
+				}
+			} else if (runtime) {
+				const entry = /** @type {Entrypoint} */ (this.entrypoints.get(name));
+				let chunk = this.namedChunks.get(runtime);
+				if (chunk) {
+					if (!runtimeChunks.has(chunk)) {
+						const err =
+							new WebpackError(`Entrypoint '${name}' has a 'runtime' option which points to another entrypoint named '${runtime}'.
+It's not valid to use other entrypoints as runtime chunk.
+Did you mean to use 'dependOn: ${JSON.stringify(
+								runtime
+							)}' instead to allow using entrypoint '${name}' within the runtime of entrypoint '${runtime}'? For this '${runtime}' must always be loaded when '${name}' is used.
+Or do you want to use the entrypoints '${name}' and '${runtime}' independently on the same page with a shared runtime? In this case give them both the same value for the 'runtime' option. It must be a name not already used by an entrypoint.`);
+						const entryChunk =
+							/** @type {Chunk} */
+							(entry.getEntrypointChunk());
+						err.chunk = entryChunk;
+						this.errors.push(err);
+						entry.setRuntimeChunk(entryChunk);
+						continue;
+					}
+				} else {
+					chunk = this.addChunk(runtime);
+					chunk.preventIntegration = true;
+					runtimeChunks.add(chunk);
+				}
+				entry.unshiftChunk(chunk);
+				chunk.addGroup(entry);
+				entry.setRuntimeChunk(chunk);
+			}
 		}
-		this.processDependenciesBlocksForChunkGroups(this.chunkGroups.slice());
-		this.sortModules(this.modules);
+
+		buildChunkGraph(this, chunkGraphInit);
+		this.hooks.afterChunks.call(this.chunks);
+		this.logger.timeEnd("create chunks");
+
+		this.logger.time("optimize");
 		this.hooks.optimize.call();
 
-		while (
-			this.hooks.optimizeModulesBasic.call(this.modules) ||
-			this.hooks.optimizeModules.call(this.modules) ||
-			this.hooks.optimizeModulesAdvanced.call(this.modules)
-		) {
+		while (this.hooks.optimizeModules.call(this.modules)) {
 			/* empty */
 		}
 		this.hooks.afterOptimizeModules.call(this.modules);
 
-		while (
-			this.hooks.optimizeChunksBasic.call(this.chunks, this.chunkGroups) ||
-			this.hooks.optimizeChunks.call(this.chunks, this.chunkGroups) ||
-			this.hooks.optimizeChunksAdvanced.call(this.chunks, this.chunkGroups)
-		) {
+		while (this.hooks.optimizeChunks.call(this.chunks, this.chunkGroups)) {
 			/* empty */
 		}
 		this.hooks.afterOptimizeChunks.call(this.chunks, this.chunkGroups);
 
-		this.hooks.optimizeTree.callAsync(this.chunks, this.modules, err => {
+		this.hooks.optimizeTree.callAsync(this.chunks, this.modules, (err) => {
 			if (err) {
-				return callback(err);
+				return finalCallback(
+					makeWebpackError(err, "Compilation.hooks.optimizeTree")
+				);
 			}
 
 			this.hooks.afterOptimizeTree.call(this.chunks, this.modules);
 
-			while (
-				this.hooks.optimizeChunkModulesBasic.call(this.chunks, this.modules) ||
-				this.hooks.optimizeChunkModules.call(this.chunks, this.modules) ||
-				this.hooks.optimizeChunkModulesAdvanced.call(this.chunks, this.modules)
-			) {
-				/* empty */
-			}
-			this.hooks.afterOptimizeChunkModules.call(this.chunks, this.modules);
+			this.hooks.optimizeChunkModules.callAsync(
+				this.chunks,
+				this.modules,
+				(err) => {
+					if (err) {
+						return finalCallback(
+							makeWebpackError(err, "Compilation.hooks.optimizeChunkModules")
+						);
+					}
 
-			const shouldRecord = this.hooks.shouldRecord.call() !== false;
+					this.hooks.afterOptimizeChunkModules.call(this.chunks, this.modules);
 
-			this.hooks.reviveModules.call(this.modules, this.records);
-			this.hooks.optimizeModuleOrder.call(this.modules);
-			this.hooks.advancedOptimizeModuleOrder.call(this.modules);
-			this.hooks.beforeModuleIds.call(this.modules);
-			this.hooks.moduleIds.call(this.modules);
-			this.applyModuleIds();
-			this.hooks.optimizeModuleIds.call(this.modules);
-			this.hooks.afterOptimizeModuleIds.call(this.modules);
+					const shouldRecord = this.hooks.shouldRecord.call() !== false;
 
-			this.sortItemsWithModuleIds();
+					this.hooks.reviveModules.call(
+						this.modules,
+						/** @type {Records} */
+						(this.records)
+					);
+					this.hooks.beforeModuleIds.call(this.modules);
+					this.hooks.moduleIds.call(this.modules);
+					this.hooks.optimizeModuleIds.call(this.modules);
+					this.hooks.afterOptimizeModuleIds.call(this.modules);
+
+					this.hooks.reviveChunks.call(
+						this.chunks,
+						/** @type {Records} */
+						(this.records)
+					);
+					this.hooks.beforeChunkIds.call(this.chunks);
+					this.hooks.chunkIds.call(this.chunks);
+					this.hooks.optimizeChunkIds.call(this.chunks);
+					this.hooks.afterOptimizeChunkIds.call(this.chunks);
 
-			this.hooks.reviveChunks.call(this.chunks, this.records);
-			this.hooks.optimizeChunkOrder.call(this.chunks);
-			this.hooks.beforeChunkIds.call(this.chunks);
-			this.applyChunkIds();
-			this.hooks.optimizeChunkIds.call(this.chunks);
-			this.hooks.afterOptimizeChunkIds.call(this.chunks);
+					this.assignRuntimeIds();
 
-			this.sortItemsWithChunkIds();
+					this.logger.time("compute affected modules with chunk graph");
+					this._computeAffectedModulesWithChunkGraph();
+					this.logger.timeEnd("compute affected modules with chunk graph");
 
-			if (shouldRecord) {
-				this.hooks.recordModules.call(this.modules, this.records);
-				this.hooks.recordChunks.call(this.chunks, this.records);
-			}
+					this.sortItemsWithChunkIds();
 
-			this.hooks.beforeHash.call();
-			this.createHash();
-			this.hooks.afterHash.call();
+					if (shouldRecord) {
+						this.hooks.recordModules.call(
+							this.modules,
+							/** @type {Records} */
+							(this.records)
+						);
+						this.hooks.recordChunks.call(
+							this.chunks,
+							/** @type {Records} */
+							(this.records)
+						);
+					}
 
-			if (shouldRecord) {
-				this.hooks.recordHash.call(this.records);
-			}
+					this.hooks.optimizeCodeGeneration.call(this.modules);
+					this.logger.timeEnd("optimize");
 
-			this.hooks.beforeModuleAssets.call();
-			this.createModuleAssets();
-			if (this.hooks.shouldGenerateChunkAssets.call() !== false) {
-				this.hooks.beforeChunkAssets.call();
-				this.createChunkAssets();
-			}
-			this.hooks.additionalChunkAssets.call(this.chunks);
-			this.summarizeDependencies();
-			if (shouldRecord) {
-				this.hooks.record.call(this, this.records);
-			}
+					this.logger.time("module hashing");
+					this.hooks.beforeModuleHash.call();
+					this.createModuleHashes();
+					this.hooks.afterModuleHash.call();
+					this.logger.timeEnd("module hashing");
 
-			this.hooks.additionalAssets.callAsync(err => {
-				if (err) {
-					return callback(err);
-				}
-				this.hooks.optimizeChunkAssets.callAsync(this.chunks, err => {
-					if (err) {
-						return callback(err);
-					}
-					this.hooks.afterOptimizeChunkAssets.call(this.chunks);
-					this.hooks.optimizeAssets.callAsync(this.assets, err => {
+					this.logger.time("code generation");
+					this.hooks.beforeCodeGeneration.call();
+					this.codeGeneration((err) => {
 						if (err) {
-							return callback(err);
-						}
-						this.hooks.afterOptimizeAssets.call(this.assets);
-						if (this.hooks.needAdditionalSeal.call()) {
-							this.unseal();
-							return this.seal(callback);
+							return finalCallback(err);
 						}
-						return this.hooks.afterSeal.callAsync(callback);
+						this.hooks.afterCodeGeneration.call();
+						this.logger.timeEnd("code generation");
+
+						this.logger.time("runtime requirements");
+						this.hooks.beforeRuntimeRequirements.call();
+						this.processRuntimeRequirements();
+						this._attachPendingRuntimeModules((err) => {
+							if (err) {
+								return finalCallback(err);
+							}
+							this.hooks.afterRuntimeRequirements.call();
+							this.logger.timeEnd("runtime requirements");
+
+							this.logger.time("hashing");
+							this.hooks.beforeHash.call();
+							const codeGenerationJobs = this.createHash();
+							this.hooks.afterHash.call();
+							this.logger.timeEnd("hashing");
+
+							this._runCodeGenerationJobs(codeGenerationJobs, (err) => {
+								if (err) {
+									return finalCallback(err);
+								}
+
+								if (shouldRecord) {
+									this.logger.time("record hash");
+									this.hooks.recordHash.call(
+										/** @type {Records} */
+										(this.records)
+									);
+									this.logger.timeEnd("record hash");
+								}
+
+								this.logger.time("module assets");
+								this.clearAssets();
+
+								this.hooks.beforeModuleAssets.call();
+								this.createModuleAssets();
+								this.logger.timeEnd("module assets");
+
+								const cont = () => {
+									this.logger.time("process assets");
+									this.hooks.processAssets.callAsync(this.assets, (err) => {
+										if (err) {
+											return finalCallback(
+												makeWebpackError(err, "Compilation.hooks.processAssets")
+											);
+										}
+										this.hooks.afterProcessAssets.call(this.assets);
+										this.logger.timeEnd("process assets");
+										this.assets =
+											/** @type {CompilationAssets} */
+											(
+												this._backCompat
+													? soonFrozenObjectDeprecation(
+															this.assets,
+															"Compilation.assets",
+															"DEP_WEBPACK_COMPILATION_ASSETS",
+															`BREAKING CHANGE: No more changes should happen to Compilation.assets after sealing the Compilation.
+		Do changes to assets earlier, e. g. in Compilation.hooks.processAssets.
+		Make sure to select an appropriate stage from Compilation.PROCESS_ASSETS_STAGE_*.`
+														)
+													: Object.freeze(this.assets)
+											);
+
+										this.summarizeDependencies();
+										if (shouldRecord) {
+											this.hooks.record.call(
+												this,
+												/** @type {Records} */
+												(this.records)
+											);
+										}
+
+										if (this.hooks.needAdditionalSeal.call()) {
+											this.unseal();
+											return this.seal(callback);
+										}
+										return this.hooks.afterSeal.callAsync((err) => {
+											if (err) {
+												return finalCallback(
+													makeWebpackError(err, "Compilation.hooks.afterSeal")
+												);
+											}
+											this.fileSystemInfo.logStatistics();
+											finalCallback();
+										});
+									});
+								};
+
+								this.logger.time("create chunk assets");
+								if (this.hooks.shouldGenerateChunkAssets.call() !== false) {
+									this.hooks.beforeChunkAssets.call();
+									this.createChunkAssets((err) => {
+										this.logger.timeEnd("create chunk assets");
+										if (err) {
+											return finalCallback(err);
+										}
+										cont();
+									});
+								} else {
+									this.logger.timeEnd("create chunk assets");
+									cont();
+								}
+							});
+						});
 					});
-				});
-			});
+				}
+			);
 		});
 	}
 
-	sortModules(modules) {
-		modules.sort(byIndexOrIdentifier);
-	}
-
+	/**
+	 * Report dependency errors and warnings.
+	 * @param {Module} module module to report from
+	 * @param {DependenciesBlock[]} blocks blocks to report from
+	 * @returns {boolean} true, when it has warnings or errors
+	 */
 	reportDependencyErrorsAndWarnings(module, blocks) {
-		for (let indexBlock = 0; indexBlock < blocks.length; indexBlock++) {
-			const block = blocks[indexBlock];
+		let hasProblems = false;
+		for (const block of blocks) {
 			const dependencies = block.dependencies;
 
-			for (let indexDep = 0; indexDep < dependencies.length; indexDep++) {
-				const d = dependencies[indexDep];
-
-				const warnings = d.getWarnings();
+			for (const d of dependencies) {
+				const warnings = d.getWarnings(this.moduleGraph);
 				if (warnings) {
-					for (let indexWar = 0; indexWar < warnings.length; indexWar++) {
-						const w = warnings[indexWar];
-
-						const warning = new ModuleDependencyWarning(module, w, d.loc);
+					for (const w of warnings) {
+						// A consolidated dependency (e.g. CssIcssExportDependency) carries
+						// per-item locations on the error itself; fall back to the
+						// dependency loc for the usual one-loc-per-dependency case.
+						const warning = new (getModuleDependencyWarning())(
+							module,
+							w,
+							/** @type {EXPECTED_ANY} */ (w).loc || d.loc
+						);
 						this.warnings.push(warning);
+						hasProblems = true;
 					}
 				}
-				const errors = d.getErrors();
+				const errors = d.getErrors(this.moduleGraph);
 				if (errors) {
-					for (let indexErr = 0; indexErr < errors.length; indexErr++) {
-						const e = errors[indexErr];
-
-						const error = new ModuleDependencyError(module, e, d.loc);
+					for (const e of errors) {
+						const error = new (getModuleDependencyError())(
+							module,
+							e,
+							/** @type {EXPECTED_ANY} */ (e).loc || d.loc
+						);
 						this.errors.push(error);
+						hasProblems = true;
+					}
+				}
+			}
+
+			if (this.reportDependencyErrorsAndWarnings(module, block.blocks)) {
+				hasProblems = true;
+			}
+		}
+		return hasProblems;
+	}
+
+	/**
+	 * Generates code and runtime requirements for this module.
+	 * @param {Callback} callback callback
+	 */
+	codeGeneration(callback) {
+		const { chunkGraph } = this;
+		this.codeGenerationResults = new CodeGenerationResults(
+			this.outputOptions.hashFunction
+		);
+		/** @type {CodeGenerationJobs} */
+		const jobs = [];
+		for (const module of this.modules) {
+			const runtimes = chunkGraph.getModuleRuntimes(module);
+			if (runtimes.size === 1) {
+				for (const runtime of runtimes) {
+					const hash = chunkGraph.getModuleHash(module, runtime);
+					jobs.push({ module, hash, runtime, runtimes: [runtime] });
+				}
+			} else if (runtimes.size > 1) {
+				/** @type {Map} */
+				const map = new Map();
+				for (const runtime of runtimes) {
+					const hash = chunkGraph.getModuleHash(module, runtime);
+					const job = map.get(hash);
+					if (job === undefined) {
+						const newJob = { module, hash, runtime, runtimes: [runtime] };
+						jobs.push(newJob);
+						map.set(hash, newJob);
+					} else {
+						job.runtimes.push(runtime);
+					}
+				}
+			}
+		}
+
+		this._runCodeGenerationJobs(jobs, callback);
+	}
+
+	/**
+	 * Run code generation jobs.
+	 * @private
+	 * @param {CodeGenerationJobs} jobs code generation jobs
+	 * @param {Callback} callback callback
+	 * @returns {void}
+	 */
+	_runCodeGenerationJobs(jobs, callback) {
+		if (jobs.length === 0) {
+			return callback();
+		}
+		let statModulesFromCache = 0;
+		let statModulesGenerated = 0;
+		const { chunkGraph, moduleGraph, dependencyTemplates, runtimeTemplate } =
+			this;
+		const results =
+			/** @type {CodeGenerationResults} */
+			(this.codeGenerationResults);
+		/** @type {WebpackError[]} */
+		const errors = [];
+		/** @type {NotCodeGeneratedModules | undefined} */
+		let notCodeGeneratedModules;
+		const runIteration = () => {
+			/** @type {CodeGenerationJobs} */
+			let delayedJobs = [];
+			/** @type {Set} */
+			let delayedModules = new Set();
+			asyncLib.eachLimit(
+				jobs,
+				this.options.parallelism,
+				(job, callback) => {
+					const { module } = job;
+					const { codeGenerationDependencies } = module;
+					if (
+						codeGenerationDependencies !== undefined &&
+						(notCodeGeneratedModules === undefined ||
+							codeGenerationDependencies.some((dep) => {
+								const referencedModule = /** @type {Module} */ (
+									moduleGraph.getModule(dep)
+								);
+								return /** @type {NotCodeGeneratedModules} */ (
+									notCodeGeneratedModules
+								).has(referencedModule);
+							}))
+					) {
+						delayedJobs.push(job);
+						delayedModules.add(module);
+						return callback();
+					}
+					const { hash, runtime, runtimes } = job;
+					this._codeGenerationModule(
+						module,
+						runtime,
+						runtimes,
+						hash,
+						dependencyTemplates,
+						chunkGraph,
+						moduleGraph,
+						runtimeTemplate,
+						errors,
+						results,
+						(err, codeGenerated) => {
+							if (codeGenerated) statModulesGenerated++;
+							else statModulesFromCache++;
+							callback(err);
+						}
+					);
+				},
+				(err) => {
+					if (err) return callback(/** @type {WebpackError} */ (err));
+					if (delayedJobs.length > 0) {
+						if (delayedJobs.length === jobs.length) {
+							return callback(
+								/** @type {WebpackError} */ (
+									new Error(
+										`Unable to make progress during code generation because of circular code generation dependency: ${Array.from(
+											delayedModules,
+											(m) => m.identifier()
+										).join(", ")}`
+									)
+								)
+							);
+						}
+						jobs = delayedJobs;
+						delayedJobs = [];
+						notCodeGeneratedModules = delayedModules;
+						delayedModules = new Set();
+						return runIteration();
+					}
+					if (errors.length > 0) {
+						errors.sort(
+							compareSelect((err) => err.module, compareModulesByIdentifier)
+						);
+						for (const error of errors) {
+							this.errors.push(error);
+						}
+					}
+					this.logger.log(
+						`${Math.round(
+							(100 * statModulesGenerated) /
+								(statModulesGenerated + statModulesFromCache)
+						)}% code generated (${statModulesGenerated} generated, ${statModulesFromCache} from cache)`
+					);
+					callback();
+				}
+			);
+		};
+		runIteration();
+	}
+
+	/**
+	 * Code generation module.
+	 * @param {Module} module module
+	 * @param {RuntimeSpec} runtime runtime
+	 * @param {RuntimeSpec[]} runtimes runtimes
+	 * @param {string} hash hash
+	 * @param {DependencyTemplates} dependencyTemplates dependencyTemplates
+	 * @param {ChunkGraph} chunkGraph chunkGraph
+	 * @param {ModuleGraph} moduleGraph moduleGraph
+	 * @param {RuntimeTemplate} runtimeTemplate runtimeTemplate
+	 * @param {WebpackError[]} errors errors
+	 * @param {CodeGenerationResults} results results
+	 * @param {(err?: WebpackError | null, result?: boolean) => void} callback callback
+	 * @returns {void}
+	 */
+	_codeGenerationModule(
+		module,
+		runtime,
+		runtimes,
+		hash,
+		dependencyTemplates,
+		chunkGraph,
+		moduleGraph,
+		runtimeTemplate,
+		errors,
+		results,
+		callback
+	) {
+		let codeGenerated = false;
+		// without a cache backend, skip building the cache keys — the module
+		// identifier strings are large (concatenated modules join all inner ids)
+		if (!this._codeGenerationCache.isEnabled()) {
+			/** @type {CodeGenerationResult} */
+			let result;
+			try {
+				codeGenerated = true;
+				this.codeGeneratedModules.add(module);
+				result = module.codeGeneration({
+					chunkGraph,
+					moduleGraph,
+					dependencyTemplates,
+					runtimeTemplate,
+					runtime,
+					runtimes,
+					codeGenerationResults: results,
+					compilation: this
+				});
+			} catch (err) {
+				errors.push(
+					new (getCodeGenerationError())(module, /** @type {Error} */ (err))
+				);
+				result = {
+					sources: new Map(),
+					runtimeRequirements: null,
+					data: undefined
+				};
+			}
+			for (const runtime of runtimes) {
+				results.add(module, runtime, result);
+			}
+			return callback(null, codeGenerated);
+		}
+		const cache = new MultiItemCache(
+			runtimes.map((runtime) =>
+				this._codeGenerationCache.getItemCache(
+					`${module.identifier()}|${getRuntimeKey(runtime)}`,
+					`${hash}|${dependencyTemplates.getHash()}`
+				)
+			)
+		);
+		cache.get((err, cachedResult) => {
+			if (err) return callback(/** @type {WebpackError} */ (err));
+			/** @type {CodeGenerationResult} */
+			let result;
+			if (!cachedResult) {
+				try {
+					codeGenerated = true;
+					this.codeGeneratedModules.add(module);
+					result = module.codeGeneration({
+						chunkGraph,
+						moduleGraph,
+						dependencyTemplates,
+						runtimeTemplate,
+						runtime,
+						runtimes,
+						codeGenerationResults: results,
+						compilation: this
+					});
+				} catch (err) {
+					errors.push(
+						new (getCodeGenerationError())(module, /** @type {Error} */ (err))
+					);
+					result = cachedResult = {
+						sources: new Map(),
+						runtimeRequirements: null,
+						data: undefined
+					};
+				}
+			} else {
+				result = cachedResult;
+			}
+			for (const runtime of runtimes) {
+				results.add(module, runtime, result);
+			}
+			if (!cachedResult) {
+				cache.store(result, (err) =>
+					callback(/** @type {WebpackError} */ (err), codeGenerated)
+				);
+			} else {
+				callback(null, codeGenerated);
+			}
+		});
+	}
+
+	_getChunkGraphEntries() {
+		/** @type {Set} */
+		const treeEntries = new Set();
+		for (const ep of this.entrypoints.values()) {
+			const chunk = ep.getRuntimeChunk();
+			if (chunk) treeEntries.add(chunk);
+		}
+		for (const ep of this.asyncEntrypoints) {
+			const chunk = ep.getRuntimeChunk();
+			if (chunk) treeEntries.add(chunk);
+		}
+		return treeEntries;
+	}
+
+	/**
+	 * Process runtime requirements.
+	 * @param {object} options options
+	 * @param {ChunkGraph=} options.chunkGraph the chunk graph
+	 * @param {Iterable=} options.modules modules
+	 * @param {Iterable=} options.chunks chunks
+	 * @param {CodeGenerationResults=} options.codeGenerationResults codeGenerationResults
+	 * @param {Iterable=} options.chunkGraphEntries chunkGraphEntries
+	 * @returns {void}
+	 */
+	processRuntimeRequirements({
+		chunkGraph = this.chunkGraph,
+		modules = this.modules,
+		chunks = this.chunks,
+		codeGenerationResults = /** @type {CodeGenerationResults} */ (
+			this.codeGenerationResults
+		),
+		chunkGraphEntries = this._getChunkGraphEntries()
+	} = {}) {
+		const context = { chunkGraph, codeGenerationResults };
+		const { moduleMemCaches2 } = this;
+		this.logger.time("runtime requirements.modules");
+		const additionalModuleRuntimeRequirements =
+			this.hooks.additionalModuleRuntimeRequirements;
+		const runtimeRequirementInModule = this.hooks.runtimeRequirementInModule;
+		for (const module of modules) {
+			if (chunkGraph.getNumberOfModuleChunks(module) > 0) {
+				const memCache = moduleMemCaches2 && moduleMemCaches2.get(module);
+				for (const runtime of chunkGraph.getModuleRuntimes(module)) {
+					if (memCache) {
+						const cached = memCache.get(
+							`moduleRuntimeRequirements-${getRuntimeKey(runtime)}`
+						);
+						if (cached !== undefined) {
+							if (cached !== null) {
+								chunkGraph.addModuleRuntimeRequirements(
+									module,
+									runtime,
+									/** @type {RuntimeRequirements} */
+									(cached),
+									false
+								);
+							}
+							continue;
+						}
+					}
+					/** @type {RuntimeRequirements} */
+					let set;
+					const runtimeRequirements =
+						codeGenerationResults.getRuntimeRequirements(module, runtime);
+					if (runtimeRequirements && runtimeRequirements.size > 0) {
+						set = new Set(runtimeRequirements);
+					} else if (additionalModuleRuntimeRequirements.isUsed()) {
+						set = new Set();
+					} else {
+						if (memCache) {
+							memCache.set(
+								`moduleRuntimeRequirements-${getRuntimeKey(runtime)}`,
+								null
+							);
+						}
+						continue;
+					}
+					additionalModuleRuntimeRequirements.call(module, set, context);
+
+					for (const r of set) {
+						const hook = runtimeRequirementInModule.get(r);
+						if (hook !== undefined) hook.call(module, set, context);
+					}
+					if (set.size === 0) {
+						if (memCache) {
+							memCache.set(
+								`moduleRuntimeRequirements-${getRuntimeKey(runtime)}`,
+								null
+							);
+						}
+					} else if (memCache) {
+						memCache.set(
+							`moduleRuntimeRequirements-${getRuntimeKey(runtime)}`,
+							set
+						);
+						chunkGraph.addModuleRuntimeRequirements(
+							module,
+							runtime,
+							set,
+							false
+						);
+					} else {
+						chunkGraph.addModuleRuntimeRequirements(module, runtime, set);
 					}
 				}
 			}
+		}
+		this.logger.timeEnd("runtime requirements.modules");
+
+		this.logger.time("runtime requirements.chunks");
+		for (const chunk of chunks) {
+			/** @type {RuntimeRequirements} */
+			const set = new Set();
+			for (const module of chunkGraph.getChunkModulesIterable(chunk)) {
+				const runtimeRequirements = chunkGraph.getModuleRuntimeRequirements(
+					module,
+					chunk.runtime
+				);
+				for (const r of runtimeRequirements) set.add(r);
+			}
+			this.hooks.additionalChunkRuntimeRequirements.call(chunk, set, context);
+
+			for (const r of set) {
+				this.hooks.runtimeRequirementInChunk.for(r).call(chunk, set, context);
+			}
+
+			chunkGraph.addChunkRuntimeRequirements(chunk, set);
+		}
+		this.logger.timeEnd("runtime requirements.chunks");
+
+		this.logger.time("runtime requirements.entries");
+		for (const treeEntry of chunkGraphEntries) {
+			/** @type {RuntimeRequirements} */
+			const set = new Set();
+			for (const chunk of treeEntry.getAllReferencedChunks()) {
+				const runtimeRequirements =
+					chunkGraph.getChunkRuntimeRequirements(chunk);
+				for (const r of runtimeRequirements) set.add(r);
+			}
+
+			this.hooks.additionalTreeRuntimeRequirements.call(
+				treeEntry,
+				set,
+				context
+			);
+
+			for (const r of set) {
+				this.hooks.runtimeRequirementInTree
+					.for(r)
+					.call(treeEntry, set, context);
+			}
+
+			chunkGraph.addTreeRuntimeRequirements(treeEntry, set);
+		}
+		this.logger.timeEnd("runtime requirements.entries");
+	}
+
+	/**
+	 * Queues a runtime module whose implementation is loaded on demand, for
+	 * `_attachPendingRuntimeModules` to await and build once the requirement pass
+	 * is over. `runtimeRequirementInTree` is a sync hook, so a tap cannot await
+	 * the load itself; anything the tap reads off the requirement set must be
+	 * captured before queueing, since `create` runs later.
+	 * @param {Chunk} chunk target chunk
+	 * @param {() => Promise} load resolves the runtime module implementation
+	 * @param {(loaded: EXPECTED_ANY) => RuntimeModule} create builds the runtime module from it
+	 * @param {ChunkGraph} chunkGraph the chunk graph
+	 * @returns {void}
+	 */
+	addLazyRuntimeModule(chunk, load, create, chunkGraph = this.chunkGraph) {
+		if (this._pendingRuntimeModules === undefined) {
+			this._pendingRuntimeModules = [];
+		}
+		this._pendingRuntimeModules.push({ chunk, load, create, chunkGraph });
+	}
+
+	/**
+	 * Awaits and attaches everything `addLazyRuntimeModule` queued. The only
+	 * place a deferred runtime module implementation is loaded.
+	 * @param {Callback} callback signals when the call finishes
+	 * @returns {void}
+	 */
+	_attachPendingRuntimeModules(callback) {
+		const pending = this._pendingRuntimeModules;
+		if (pending === undefined) return callback(null);
+		this._pendingRuntimeModules = undefined;
+		Promise.all(pending.map(({ load }) => load()))
+			.then((loaded) => {
+				for (let i = 0; i < pending.length; i++) {
+					const { chunk, create, chunkGraph } = pending[i];
+					this.addRuntimeModule(chunk, create(loaded[i]), chunkGraph);
+				}
+			})
+			// a `runtimeModule` tap may have queued more
+			.then(() => this._attachPendingRuntimeModules(callback), callback);
+	}
+
+	// TODO webpack 6 make chunkGraph argument non-optional
+	/**
+	 * Adds runtime module.
+	 * @param {Chunk} chunk target chunk
+	 * @param {RuntimeModule} module runtime module
+	 * @param {ChunkGraph} chunkGraph the chunk graph
+	 * @returns {void}
+	 */
+	addRuntimeModule(chunk, module, chunkGraph = this.chunkGraph) {
+		// Deprecated ModuleGraph association
+		if (this._backCompat) {
+			ModuleGraph.setModuleGraphForModule(module, this.moduleGraph);
+		}
+
+		// add it to the list
+		this.modules.add(module);
+		this._modules.set(module.identifier(), module);
+
+		// connect to the chunk graph
+		chunkGraph.connectChunkAndModule(chunk, module);
+		chunkGraph.connectChunkAndRuntimeModule(chunk, module);
+		if (module.fullHash) {
+			chunkGraph.addFullHashModuleToChunk(chunk, module);
+		} else if (module.dependentHash) {
+			chunkGraph.addDependentHashModuleToChunk(chunk, module);
+		}
+
+		// attach runtime module
+		module.attach(this, chunk, chunkGraph);
 
-			this.reportDependencyErrorsAndWarnings(module, block.blocks);
+		// Setup internals
+		const exportsInfo = this.moduleGraph.getExportsInfo(module);
+		exportsInfo.setHasProvideInfo();
+		if (typeof chunk.runtime === "string") {
+			exportsInfo.setUsedForSideEffectsOnly(chunk.runtime);
+		} else if (chunk.runtime === undefined) {
+			exportsInfo.setUsedForSideEffectsOnly(undefined);
+		} else {
+			for (const runtime of chunk.runtime) {
+				exportsInfo.setUsedForSideEffectsOnly(runtime);
+			}
 		}
+		chunkGraph.addModuleRuntimeRequirements(
+			module,
+			chunk.runtime,
+			new Set([RuntimeGlobals.requireScope])
+		);
+
+		// runtime modules don't need ids
+		chunkGraph.setModuleId(module, "");
+
+		// Call hook
+		this.hooks.runtimeModule.call(module, chunk);
 	}
 
+	/**
+	 * If `module` is passed, `loc` and `request` must also be passed.
+	 * @param {string | ChunkGroupOptions} groupOptions options for the chunk group
+	 * @param {Module=} module the module the references the chunk group
+	 * @param {DependencyLocation=} loc the location from with the chunk group is referenced (inside of module)
+	 * @param {string=} request the request from which the chunk group is referenced
+	 * @returns {ChunkGroup} the new or existing chunk group
+	 */
 	addChunkInGroup(groupOptions, module, loc, request) {
 		if (typeof groupOptions === "string") {
 			groupOptions = { name: groupOptions };
@@ -1034,18 +4486,33 @@ class Compilation extends Tapable {
 		if (name) {
 			const chunkGroup = this.namedChunkGroups.get(name);
 			if (chunkGroup !== undefined) {
-				chunkGroup.addOptions(groupOptions);
 				if (module) {
-					chunkGroup.addOrigin(module, loc, request);
+					chunkGroup.addOrigin(
+						module,
+						/** @type {DependencyLocation} */
+						(loc),
+						/** @type {string} */
+						(request)
+					);
 				}
 				return chunkGroup;
 			}
 		}
 		const chunkGroup = new ChunkGroup(groupOptions);
-		if (module) chunkGroup.addOrigin(module, loc, request);
+		if (module) {
+			chunkGroup.addOrigin(
+				module,
+				/** @type {DependencyLocation} */
+				(loc),
+				/** @type {string} */
+				(request)
+			);
+		}
 		const chunk = this.addChunk(name);
 
-		GraphHelpers.connectChunkGroupAndChunk(chunkGroup, chunk);
+		if (chunkGroup.pushChunk(chunk)) {
+			chunk.addGroup(chunkGroup);
+		}
 
 		this.chunkGroups.push(chunkGroup);
 		if (name) {
@@ -1054,6 +4521,56 @@ class Compilation extends Tapable {
 		return chunkGroup;
 	}
 
+	/**
+	 * Adds the provided async entrypoint to this chunk group.
+	 * @param {EntryOptions} options options for the entrypoint
+	 * @param {Module} module the module the references the chunk group
+	 * @param {DependencyLocation} loc the location from with the chunk group is referenced (inside of module)
+	 * @param {string} request the request from which the chunk group is referenced
+	 * @returns {Entrypoint} the new or existing entrypoint
+	 */
+	addAsyncEntrypoint(options, module, loc, request) {
+		const name = options.name;
+		if (name) {
+			const entrypoint = this.namedChunkGroups.get(name);
+			if (entrypoint instanceof Entrypoint) {
+				if (module) {
+					entrypoint.addOrigin(module, loc, request);
+				}
+				return entrypoint;
+			} else if (entrypoint) {
+				throw new Error(
+					`Cannot add an async entrypoint with the name '${name}', because there is already an chunk group with this name`
+				);
+			}
+		}
+		const chunk = this.addChunk(name);
+		if (options.filename) {
+			chunk.filenameTemplate = options.filename;
+		}
+		const entrypoint = new Entrypoint(options, false);
+		entrypoint.setRuntimeChunk(chunk);
+		entrypoint.setEntrypointChunk(chunk);
+		if (name) {
+			this.namedChunkGroups.set(name, entrypoint);
+		}
+		this.chunkGroups.push(entrypoint);
+		this.asyncEntrypoints.push(entrypoint);
+		if (entrypoint.pushChunk(chunk)) {
+			chunk.addGroup(entrypoint);
+		}
+		if (module) {
+			entrypoint.addOrigin(module, loc, request);
+		}
+		return entrypoint;
+	}
+
+	/**
+	 * This method first looks to see if a name is provided for a new chunk,
+	 * and first looks to see if any named chunks already exist and reuse that chunk instead.
+	 * @param {ChunkName=} name optional chunk name to be provided
+	 * @returns {Chunk} create a chunk (invoked during seal event)
+	 */
 	addChunk(name) {
 		if (name) {
 			const chunk = this.namedChunks.get(name);
@@ -1061,831 +4578,1328 @@ class Compilation extends Tapable {
 				return chunk;
 			}
 		}
-		const chunk = new Chunk(name);
-		this.chunks.push(chunk);
+		const chunk = new Chunk(name, this._backCompat);
+		this.chunks.add(chunk);
+		if (this._backCompat) {
+			ChunkGraph.setChunkGraphForChunk(chunk, this.chunkGraph);
+		}
 		if (name) {
 			this.namedChunks.set(name, chunk);
 		}
 		return chunk;
 	}
 
-	assignIndex(module) {
-		const assignIndexToModule = module => {
-			// enter module
-			if (typeof module.index !== "number") {
-				module.index = this.nextFreeModuleIndex++;
+	/**
+	 * Processes the provided module.
+	 * @deprecated
+	 * @param {Module} module module to assign depth
+	 * @returns {void}
+	 */
+	assignDepth(module) {
+		const moduleGraph = this.moduleGraph;
 
-				// leave module
-				queue.push(() => (module.index2 = this.nextFreeModuleIndex2++));
+		const queue = new Set([module]);
+		/** @type {number} */
+		let depth;
 
-				// enter it as block
-				assignIndexToDependencyBlock(module);
-			}
-		};
+		moduleGraph.setDepth(module, 0);
 
-		const assignIndexToDependency = dependency => {
-			if (dependency.module) {
-				queue.push(() => assignIndexToModule(dependency.module));
-			}
+		/**
+		 * Processes the provided module.
+		 * @param {Module} module module for processing
+		 * @returns {void}
+		 */
+		const processModule = (module) => {
+			if (!moduleGraph.setDepthIfLower(module, depth)) return;
+			queue.add(module);
 		};
 
-		const assignIndexToDependencyBlock = block => {
-			let allDependencies = [];
-
-			const iteratorDependency = d => allDependencies.push(d);
-
-			const iteratorBlock = b =>
-				queue.push(() => assignIndexToDependencyBlock(b));
-
-			if (block.variables) {
-				iterationBlockVariable(block.variables, iteratorDependency);
-			}
+		for (module of queue) {
+			queue.delete(module);
+			depth = /** @type {number} */ (moduleGraph.getDepth(module)) + 1;
 
-			if (block.dependencies) {
-				iterationOfArrayCallback(block.dependencies, iteratorDependency);
-			}
-			if (block.blocks) {
-				const blocks = block.blocks;
-				let indexBlock = blocks.length;
-				while (indexBlock--) {
-					iteratorBlock(blocks[indexBlock]);
+			for (const connection of moduleGraph.getOutgoingConnections(module)) {
+				const refModule = connection.module;
+				if (refModule) {
+					processModule(refModule);
 				}
 			}
+		}
+	}
 
-			let indexAll = allDependencies.length;
-			while (indexAll--) {
-				iteratorAllDependencies(allDependencies[indexAll]);
+	/**
+	 * Assigns depth values to the provided modules.
+	 * @param {Module[] | Set} modules modules to assign depth
+	 * @returns {void}
+	 */
+	assignDepths(modules) {
+		const moduleGraph = this.moduleGraph;
+
+		/** @type {Set} */
+		const queue = new Set(modules);
+		// Track these in local variables so that queue only has one data type
+		let nextDepthAt = queue.size;
+		let depth = 0;
+
+		let i = 0;
+		for (const module of queue) {
+			moduleGraph.setDepth(module, depth);
+			// Some of these results come from cache, which speeds this up
+			const connections = moduleGraph.getOutgoingConnectionsByModule(module);
+			// connections will be undefined if there are no outgoing connections
+			if (connections) {
+				for (const refModule of connections.keys()) {
+					if (refModule) queue.add(refModule);
+				}
 			}
-		};
-
-		const queue = [
-			() => {
-				assignIndexToModule(module);
+			i++;
+			// Since this is a breadth-first search, all modules added to the queue
+			// while at depth N will be depth N+1
+			if (i >= nextDepthAt) {
+				depth++;
+				nextDepthAt = queue.size;
 			}
-		];
-
-		const iteratorAllDependencies = d => {
-			queue.push(() => assignIndexToDependency(d));
-		};
-
-		while (queue.length) {
-			queue.pop()();
 		}
 	}
 
-	assignDepth(module) {
-		const queue = new Set([module]);
-		let depth;
-
-		module.depth = 0;
-
-		const enqueueJob = module => {
-			const d = module.depth;
-			if (typeof d === "number" && d <= depth) return;
-			queue.add(module);
-			module.depth = depth;
-		};
+	/**
+	 * Gets dependency referenced exports.
+	 * @param {Dependency} dependency the dependency
+	 * @param {RuntimeSpec} runtime the runtime
+	 * @returns {ReferencedExports} referenced exports
+	 */
+	getDependencyReferencedExports(dependency, runtime) {
+		const referencedExports = dependency.getReferencedExports(
+			this.moduleGraph,
+			runtime
+		);
+		return this.hooks.dependencyReferencedExports.call(
+			referencedExports,
+			dependency,
+			runtime
+		);
+	}
 
-		const assignDepthToDependency = (dependency, depth) => {
-			if (dependency.module) {
-				enqueueJob(dependency.module);
+	/**
+	 * Removes reasons of dependency block.
+	 * @param {Module} module module relationship for removal
+	 * @param {DependenciesBlockLike} block dependencies block
+	 * @returns {void}
+	 */
+	removeReasonsOfDependencyBlock(module, block) {
+		if (block.blocks) {
+			for (const b of block.blocks) {
+				this.removeReasonsOfDependencyBlock(module, b);
 			}
-		};
+		}
 
-		const assignDepthToDependencyBlock = block => {
-			if (block.variables) {
-				iterationBlockVariable(block.variables, assignDepthToDependency);
+		if (block.dependencies) {
+			for (const dep of block.dependencies) {
+				const originalModule = this.moduleGraph.getModule(dep);
+				if (originalModule) {
+					this.moduleGraph.removeConnection(dep);
+
+					if (this.chunkGraph) {
+						for (const chunk of this.chunkGraph.getModuleChunks(
+							originalModule
+						)) {
+							this.patchChunksAfterReasonRemoval(originalModule, chunk);
+						}
+					}
+				}
 			}
+		}
+	}
 
-			if (block.dependencies) {
-				iterationOfArrayCallback(block.dependencies, assignDepthToDependency);
-			}
+	/**
+	 * Patch chunks after reason removal.
+	 * @param {Module} module module to patch tie
+	 * @param {Chunk} chunk chunk to patch tie
+	 * @returns {void}
+	 */
+	patchChunksAfterReasonRemoval(module, chunk) {
+		if (!module.hasReasons(this.moduleGraph, chunk.runtime)) {
+			this.removeReasonsOfDependencyBlock(module, module);
+		}
+		if (
+			!module.hasReasonForChunk(chunk, this.moduleGraph, this.chunkGraph) &&
+			this.chunkGraph.isModuleInChunk(module, chunk)
+		) {
+			this.chunkGraph.disconnectChunkAndModule(chunk, module);
+			this.removeChunkFromDependencies(module, chunk);
+		}
+	}
 
-			if (block.blocks) {
-				iterationOfArrayCallback(block.blocks, assignDepthToDependencyBlock);
+	/**
+	 * Removes chunk from dependencies.
+	 * @param {DependenciesBlock} block block tie for Chunk
+	 * @param {Chunk} chunk chunk to remove from dep
+	 * @returns {void}
+	 */
+	removeChunkFromDependencies(block, chunk) {
+		/**
+		 * Iterator dependency.
+		 * @param {Dependency} d dependency to (maybe) patch up
+		 */
+		const iteratorDependency = (d) => {
+			const depModule = this.moduleGraph.getModule(d);
+			if (!depModule) {
+				return;
 			}
+			this.patchChunksAfterReasonRemoval(depModule, chunk);
 		};
 
-		for (module of queue) {
-			queue.delete(module);
-			depth = module.depth;
+		const blocks = block.blocks;
+		for (const asyncBlock of blocks) {
+			const chunkGroup =
+				/** @type {ChunkGroup} */
+				(this.chunkGraph.getBlockChunkGroup(asyncBlock));
+			// Grab all chunks from the first Block's AsyncDepBlock
+			const chunks = chunkGroup.chunks;
+			// For each chunk in chunkGroup
+			for (const iteratedChunk of chunks) {
+				chunkGroup.removeChunk(iteratedChunk);
+				// Recurse
+				this.removeChunkFromDependencies(block, iteratedChunk);
+			}
+		}
 
-			depth++;
-			assignDepthToDependencyBlock(module);
+		if (block.dependencies) {
+			for (const dep of block.dependencies) iteratorDependency(dep);
 		}
 	}
 
-	// This method creates the Chunk graph from the Module graph
-	processDependenciesBlocksForChunkGroups(inputChunkGroups) {
-		// Process is splitting into two parts:
-		// Part one traverse the module graph and builds a very basic chunks graph
-		//   in chunkDependencies.
-		// Part two traverse every possible way through the basic chunk graph and
-		//   tracks the available modules. While traversing it connects chunks with
-		//   eachother and Blocks with Chunks. It stops traversing when all modules
-		//   for a chunk are already available. So it doesn't connect unneeded chunks.
-
-		const chunkDependencies = new Map(); // Map>
-		const allCreatedChunkGroups = new Set();
-
-		// PREPARE
-		const blockInfoMap = new Map();
+	assignRuntimeIds() {
+		const { chunkGraph } = this;
+		/**
+		 * Process entrypoint.
+		 * @param {Entrypoint} ep an entrypoint
+		 */
+		const processEntrypoint = (ep) => {
+			const runtime = /** @type {string} */ (ep.options.runtime || ep.name);
+			const chunk = /** @type {Chunk} */ (ep.getRuntimeChunk());
+			chunkGraph.setRuntimeId(runtime, /** @type {ChunkId} */ (chunk.id));
+		};
+		for (const ep of this.entrypoints.values()) {
+			processEntrypoint(ep);
+		}
+		for (const ep of this.asyncEntrypoints) {
+			processEntrypoint(ep);
+		}
+	}
 
-		const iteratorDependency = d => {
-			// We skip Dependencies without Reference
-			const ref = d.getReference();
-			if (!ref) {
-				return;
-			}
-			// We skip Dependencies without Module pointer
-			const refModule = ref.module;
-			if (!refModule) {
-				return;
-			}
-			// We skip weak Dependencies
-			if (ref.weak) {
-				return;
-			}
+	sortItemsWithChunkIds() {
+		for (const chunkGroup of this.chunkGroups) {
+			chunkGroup.sortItems();
+		}
 
-			blockInfoModules.add(refModule);
-		};
+		this.errors.sort(compareErrors);
+		this.warnings.sort(compareErrors);
+		// `hints` is sorted where stats read it — producers tap `afterSeal` and
+		// `afterEmit`, so they append after this point.
+		this.children.sort(byNameOrHash);
+	}
 
-		const iteratorBlockPrepare = b => {
-			blockInfoBlocks.push(b);
-			blockQueue.push(b);
-		};
+	summarizeDependencies() {
+		for (const child of this.children) {
+			this.fileDependencies.addAll(child.fileDependencies);
+			this.contextDependencies.addAll(child.contextDependencies);
+			this.missingDependencies.addAll(child.missingDependencies);
+			this.buildDependencies.addAll(child.buildDependencies);
+		}
 
-		let block, blockQueue, blockInfoModules, blockInfoBlocks;
 		for (const module of this.modules) {
-			blockQueue = [module];
-			while (blockQueue.length > 0) {
-				block = blockQueue.pop();
-				blockInfoModules = new Set();
-				blockInfoBlocks = [];
-
-				if (block.variables) {
-					iterationBlockVariable(block.variables, iteratorDependency);
-				}
+			module.addCacheDependencies(
+				this.fileDependencies,
+				this.contextDependencies,
+				this.missingDependencies,
+				this.buildDependencies
+			);
+		}
+	}
 
-				if (block.dependencies) {
-					iterationOfArrayCallback(block.dependencies, iteratorDependency);
+	createModuleHashes() {
+		let statModulesHashed = 0;
+		let statModulesFromCache = 0;
+		const { chunkGraph, runtimeTemplate, moduleMemCaches2 } = this;
+		const { hashFunction, hashDigest, hashDigestLength } = this.outputOptions;
+		/** @type {WebpackError[]} */
+		const errors = [];
+		for (const module of this.modules) {
+			const memCache = moduleMemCaches2 && moduleMemCaches2.get(module);
+			for (const runtime of chunkGraph.getModuleRuntimes(module)) {
+				const cacheKey = memCache
+					? `moduleHash-${getRuntimeKey(runtime)}`
+					: undefined;
+				if (memCache) {
+					const digest =
+						/** @type {string} */
+						(memCache.get(/** @type {string} */ (cacheKey)));
+					if (digest !== undefined) {
+						chunkGraph.setModuleHashes(
+							module,
+							runtime,
+							digest,
+							digest.slice(0, hashDigestLength)
+						);
+						statModulesFromCache++;
+						continue;
+					}
 				}
-
-				if (block.blocks) {
-					iterationOfArrayCallback(block.blocks, iteratorBlockPrepare);
+				statModulesHashed++;
+				const digest = this._createModuleHash(
+					module,
+					chunkGraph,
+					runtime,
+					hashFunction,
+					runtimeTemplate,
+					hashDigest,
+					hashDigestLength,
+					errors
+				);
+				if (memCache) {
+					memCache.set(/** @type {string} */ (cacheKey), digest);
 				}
-
-				const blockInfo = {
-					modules: blockInfoModules,
-					blocks: blockInfoBlocks
-				};
-				blockInfoMap.set(block, blockInfo);
 			}
 		}
-
-		// PART ONE
-
-		const blockChunkGroups = new Map();
-
-		// Start with the provided modules/chunks
-		const queue = inputChunkGroups.map(chunkGroup => ({
-			block: chunkGroup.chunks[0].entryModule,
-			module: chunkGroup.chunks[0].entryModule,
-			chunk: chunkGroup.chunks[0],
-			chunkGroup
-		}));
-
-		let module, chunk, chunkGroup;
-
-		// For each async Block in graph
-		const iteratorBlock = b => {
-			// 1. We create a chunk for this Block
-			// but only once (blockChunkGroups map)
-			let c = blockChunkGroups.get(b);
-			if (c === undefined) {
-				c = this.namedChunkGroups.get(b.chunkName);
-				if (c && c.isInitial()) {
-					this.errors.push(
-						new AsyncDependencyToInitialChunkError(b.chunkName, module, b.loc)
-					);
-					c = chunkGroup;
-				} else {
-					c = this.addChunkInGroup(
-						b.groupOptions || b.chunkName,
-						module,
-						b.loc,
-						b.request
-					);
-					blockChunkGroups.set(b, c);
-					allCreatedChunkGroups.add(c);
-				}
-			} else {
-				// TODO webpack 5 remove addOptions check
-				if (c.addOptions) c.addOptions(b.groupOptions);
-				c.addOrigin(module, b.loc, b.request);
+		if (errors.length > 0) {
+			errors.sort(
+				compareSelect((err) => err.module, compareModulesByIdentifier)
+			);
+			for (const error of errors) {
+				this.errors.push(error);
 			}
+		}
+		this.logger.log(
+			`${statModulesHashed} modules hashed, ${statModulesFromCache} from cache (${
+				Math.round(
+					(100 * (statModulesHashed + statModulesFromCache)) / this.modules.size
+				) / 100
+			} variants per module in average)`
+		);
+	}
 
-			// 2. We store the Block+Chunk mapping as dependency for the chunk
-			let deps = chunkDependencies.get(chunkGroup);
-			if (!deps) chunkDependencies.set(chunkGroup, (deps = []));
-			deps.push({
-				block: b,
-				chunkGroup: c
-			});
-
-			// 3. We enqueue the DependenciesBlock for traversal
-			queue.push({
-				block: b,
-				module: module,
-				chunk: c.chunks[0],
-				chunkGroup: c
+	/**
+	 * Create module hash.
+	 * @private
+	 * @param {Module} module module
+	 * @param {ChunkGraph} chunkGraph the chunk graph
+	 * @param {RuntimeSpec} runtime runtime
+	 * @param {HashFunction} hashFunction hash function
+	 * @param {RuntimeTemplate} runtimeTemplate runtime template
+	 * @param {HashDigest} hashDigest hash digest
+	 * @param {HashDigestLength} hashDigestLength hash digest length
+	 * @param {WebpackError[]} errors errors
+	 * @returns {string} module hash digest
+	 */
+	_createModuleHash(
+		module,
+		chunkGraph,
+		runtime,
+		hashFunction,
+		runtimeTemplate,
+		hashDigest,
+		hashDigestLength,
+		errors
+	) {
+		/** @type {string} */
+		let moduleHashDigest;
+		try {
+			const moduleHash = createHash(hashFunction);
+			module.updateHash(moduleHash, {
+				chunkGraph,
+				runtime,
+				runtimeTemplate
 			});
-		};
-
-		// Iterative traversal of the Module graph
-		// Recursive would be simpler to write but could result in Stack Overflows
-		while (queue.length) {
-			const queueItem = queue.pop();
-			module = queueItem.module;
-			block = queueItem.block;
-			chunk = queueItem.chunk;
-			chunkGroup = queueItem.chunkGroup;
-
-			// get prepared block info
-			const blockInfo = blockInfoMap.get(block);
-
-			// Traverse all referenced modules
-			for (const refModule of blockInfo.modules) {
-				// We connect Module and Chunk when not already done
-				if (chunk.addModule(refModule)) {
-					refModule.addChunk(chunk);
-
-					// And enqueue the Module for traversal
-					queue.push({
-						block: refModule,
-						module: refModule,
-						chunk,
-						chunkGroup
-					});
-				}
-			}
-
-			// Traverse all Blocks
-			iterationOfArrayCallback(blockInfo.blocks, iteratorBlock);
+			moduleHashDigest = moduleHash.digest(hashDigest);
+		} catch (err) {
+			errors.push(
+				new (getModuleHashingError())(module, /** @type {Error} */ (err))
+			);
+			moduleHashDigest = "XXXXXX";
 		}
-
-		// PART TWO
-
-		let availableModules;
-		let newAvailableModules;
-		const queue2 = new Queue(
-			inputChunkGroups.map(chunkGroup => ({
-				chunkGroup,
-				availableModules: new Set()
-			}))
+		chunkGraph.setModuleHashes(
+			module,
+			runtime,
+			moduleHashDigest,
+			moduleHashDigest.slice(0, hashDigestLength)
 		);
+		return moduleHashDigest;
+	}
 
-		// Helper function to check if all modules of a chunk are available
-		const areModulesAvailable = (chunkGroup, availableModules) => {
-			for (const chunk of chunkGroup.chunks) {
-				for (const module of chunk.modulesIterable) {
-					if (!availableModules.has(module)) return false;
-				}
+	createHash() {
+		this.logger.time("hashing: initialize hash");
+		const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph);
+		const runtimeTemplate = this.runtimeTemplate;
+		const outputOptions = this.outputOptions;
+		const hashFunction = outputOptions.hashFunction;
+		const hashDigest = outputOptions.hashDigest;
+		const hashDigestLength = outputOptions.hashDigestLength;
+		const hash = createHash(hashFunction);
+		if (outputOptions.hashSalt) {
+			hash.update(outputOptions.hashSalt);
+		}
+		this.logger.timeEnd("hashing: initialize hash");
+		if (this.children.length > 0) {
+			this.logger.time("hashing: hash child compilations");
+			for (const child of this.children) {
+				hash.update(/** @type {string} */ (child.hash));
 			}
-			return true;
-		};
-
-		// For each edge in the basic chunk graph
-		const filterFn = dep => {
-			// Filter egdes that are not needed because all modules are already available
-			// This also filters circular dependencies in the chunks graph
-			const depChunkGroup = dep.chunkGroup;
-			if (areModulesAvailable(depChunkGroup, newAvailableModules)) return false; // break all modules are already available
-			return true;
-		};
+			this.logger.timeEnd("hashing: hash child compilations");
+		}
+		if (this.warnings.length > 0) {
+			this.logger.time("hashing: hash warnings");
+			for (const warning of this.warnings) {
+				hash.update(`${warning.message}`);
+			}
+			this.logger.timeEnd("hashing: hash warnings");
+		}
+		if (this.errors.length > 0) {
+			this.logger.time("hashing: hash errors");
+			for (const error of this.errors) {
+				hash.update(`${error.message}`);
+			}
+			this.logger.timeEnd("hashing: hash errors");
+		}
 
-		const minAvailableModulesMap = new Map();
-
-		// Iterative traversing of the basic chunk graph
-		while (queue2.length) {
-			const queueItem = queue2.dequeue();
-			chunkGroup = queueItem.chunkGroup;
-			availableModules = queueItem.availableModules;
-
-			// 1. Get minimal available modules
-			// It doesn't make sense to traverse a chunk again with more available modules.
-			// This step calculates the minimal available modules and skips traversal when
-			// the list didn't shrink.
-			let minAvailableModules = minAvailableModulesMap.get(chunkGroup);
-			if (minAvailableModules === undefined) {
-				minAvailableModulesMap.set(chunkGroup, new Set(availableModules));
+		this.logger.time("hashing: sort chunks");
+		/*
+		 * Chunks are hashed in 4 categories, in this order:
+		 * 1. Async chunks - no hash dependencies on other chunks
+		 * 2. Non-entry initial chunks (e.g. shared split chunks) - no hash
+		 *    dependencies on other chunks, but runtime chunks may read their
+		 *    hashes via GetChunkFilenameRuntimeModule (dependentHash)
+		 * 3. Runtime chunks - may use hashes of async and non-entry initial
+		 *    chunks (via GetChunkFilenameRuntimeModule). Ordered by references
+		 *    between each other (for async entrypoints)
+		 * 4. Entry chunks - may depend on runtimeChunk.hash (via
+		 *    createChunkHashHandler for ESM/CJS entry importing runtime)
+		 *
+		 * This ordering ensures all hash dependencies flow in one direction:
+		 * async/initial → runtime → entry, with no circular dependencies.
+		 * Chunks within each category are sorted by id for determinism.
+		 */
+		/** @type {Chunk[]} */
+		const unorderedRuntimeChunks = [];
+		/** @type {Chunk[]} */
+		const initialChunks = [];
+		/** @type {Chunk[]} */
+		const entryChunks = [];
+		/** @type {Chunk[]} */
+		const asyncChunks = [];
+		for (const c of this.chunks) {
+			if (c.hasRuntime()) {
+				unorderedRuntimeChunks.push(c);
+			} else if (chunkGraph.getNumberOfEntryModules(c) > 0) {
+				entryChunks.push(c);
+			} else if (c.canBeInitial()) {
+				initialChunks.push(c);
 			} else {
-				let deletedModules = false;
-				for (const m of minAvailableModules) {
-					if (!availableModules.has(m)) {
-						minAvailableModules.delete(m);
-						deletedModules = true;
-					}
-				}
-				if (!deletedModules) continue;
-				availableModules = minAvailableModules;
+				asyncChunks.push(c);
 			}
-
-			// 2. Get the edges at this point of the graph
-			const deps = chunkDependencies.get(chunkGroup);
-			if (!deps) continue;
-			if (deps.length === 0) continue;
-
-			// 3. Create a new Set of available modules at this points
-			newAvailableModules = new Set(availableModules);
-			for (const chunk of chunkGroup.chunks) {
-				for (const m of chunk.modulesIterable) {
-					newAvailableModules.add(m);
+		}
+		unorderedRuntimeChunks.sort(byId);
+		entryChunks.sort(byId);
+		initialChunks.sort(byId);
+		asyncChunks.sort(byId);
+
+		/** @typedef {{ chunk: Chunk, referencedBy: RuntimeChunkInfo[], remaining: number }} RuntimeChunkInfo */
+		/** @type {Map} */
+		const runtimeChunksMap = new Map();
+		for (const chunk of unorderedRuntimeChunks) {
+			runtimeChunksMap.set(chunk, {
+				chunk,
+				referencedBy: [],
+				remaining: 0
+			});
+		}
+		let remaining = 0;
+		for (const info of runtimeChunksMap.values()) {
+			for (const other of new Set(
+				[...info.chunk.getAllReferencedAsyncEntrypoints()].map(
+					(e) => e.chunks[e.chunks.length - 1]
+				)
+			)) {
+				const otherInfo = runtimeChunksMap.get(other);
+				// other may be a non-runtime chunk (e.g. worker chunk)
+				// when you have a worker chunk in your app.js (new Worker(...)) and as a separate entry point
+				if (otherInfo) {
+					otherInfo.referencedBy.push(info);
+					info.remaining++;
+					remaining++;
 				}
 			}
-
-			// 4. Filter edges with available modules
-			const filteredDeps = deps.filter(filterFn);
-
-			// 5. Foreach remaining edge
-			const nextChunkGroups = new Set();
-			for (let i = 0; i < filteredDeps.length; i++) {
-				const dep = filteredDeps[i];
-				const depChunkGroup = dep.chunkGroup;
-				const depBlock = dep.block;
-
-				// 6. Connect block with chunk
-				GraphHelpers.connectDependenciesBlockAndChunkGroup(
-					depBlock,
-					depChunkGroup
-				);
-
-				// 7. Connect chunk with parent
-				GraphHelpers.connectChunkGroupParentAndChild(chunkGroup, depChunkGroup);
-
-				nextChunkGroups.add(depChunkGroup);
+		}
+		/** @type {Chunk[]} */
+		const runtimeChunks = [];
+		for (const info of runtimeChunksMap.values()) {
+			if (info.remaining === 0) {
+				runtimeChunks.push(info.chunk);
 			}
-
-			// 8. Enqueue further traversal
-			for (const nextChunkGroup of nextChunkGroups) {
-				queue2.enqueue({
-					chunkGroup: nextChunkGroup,
-					availableModules: newAvailableModules
-				});
+		}
+		// If there are any references between chunks
+		// make sure to follow these chains
+		if (remaining > 0) {
+			/** @type {Chunk[]} */
+			const readyChunks = [];
+			for (const chunk of runtimeChunks) {
+				const hasFullHashModules =
+					chunkGraph.getNumberOfChunkFullHashModules(chunk) !== 0;
+				const info =
+					/** @type {RuntimeChunkInfo} */
+					(runtimeChunksMap.get(chunk));
+				for (const otherInfo of info.referencedBy) {
+					if (hasFullHashModules) {
+						chunkGraph.upgradeDependentToFullHashModules(otherInfo.chunk);
+					}
+					remaining--;
+					if (--otherInfo.remaining === 0) {
+						readyChunks.push(otherInfo.chunk);
+					}
+				}
+				if (readyChunks.length > 0) {
+					// This ensures deterministic ordering, since referencedBy is non-deterministic
+					readyChunks.sort(byId);
+					for (const c of readyChunks) runtimeChunks.push(c);
+					readyChunks.length = 0;
+				}
 			}
 		}
-
-		// Remove all unconnected chunk groups
-		for (const chunkGroup of allCreatedChunkGroups) {
-			if (chunkGroup.getNumberOfParents() === 0) {
-				for (const chunk of chunkGroup.chunks) {
-					const idx = this.chunks.indexOf(chunk);
-					if (idx >= 0) this.chunks.splice(idx, 1);
-					chunk.remove("unconnected");
+		// If there are still remaining references we have cycles and want to create a warning
+		if (remaining > 0) {
+			/** @type {RuntimeChunkInfo[]} */
+			const circularRuntimeChunkInfo = [];
+			for (const info of runtimeChunksMap.values()) {
+				if (info.remaining !== 0) {
+					circularRuntimeChunkInfo.push(info);
 				}
-				chunkGroup.remove("unconnected");
 			}
+			circularRuntimeChunkInfo.sort(compareSelect((i) => i.chunk, byId));
+			const err =
+				new WebpackError(`Circular dependency between chunks with runtime (${Array.from(
+					circularRuntimeChunkInfo,
+					(c) => c.chunk.name || c.chunk.id
+				).join(", ")})
+This prevents using hashes of each other and should be avoided.`);
+			err.chunk = circularRuntimeChunkInfo[0].chunk;
+			this.warnings.push(err);
+			for (const i of circularRuntimeChunkInfo) runtimeChunks.push(i.chunk);
 		}
-	}
+		this.logger.timeEnd("hashing: sort chunks");
 
-	removeReasonsOfDependencyBlock(module, block) {
-		const iteratorDependency = d => {
-			if (!d.module) {
-				return;
+		/** @type {Set} */
+		const fullHashChunks = new Set();
+		/** @type {CodeGenerationJobs} */
+		const codeGenerationJobs = [];
+		/** @type {Map>} */
+		const codeGenerationJobsMap = new Map();
+		/** @type {WebpackError[]} */
+		const errors = [];
+
+		/**
+		 * Processes the provided chunk.
+		 * @param {Chunk} chunk chunk
+		 */
+		const processChunk = (chunk) => {
+			// Last minute module hash generation for modules that depend on chunk hashes
+			this.logger.time("hashing: hash runtime modules");
+			const runtime = chunk.runtime;
+			for (const module of chunkGraph.getChunkModulesIterable(chunk)) {
+				if (!chunkGraph.hasModuleHashes(module, runtime)) {
+					const hash = this._createModuleHash(
+						module,
+						chunkGraph,
+						runtime,
+						hashFunction,
+						runtimeTemplate,
+						hashDigest,
+						hashDigestLength,
+						errors
+					);
+					let hashMap = codeGenerationJobsMap.get(hash);
+					if (hashMap) {
+						const moduleJob = hashMap.get(module);
+						if (moduleJob) {
+							moduleJob.runtimes.push(runtime);
+							continue;
+						}
+					} else {
+						hashMap = new Map();
+						codeGenerationJobsMap.set(hash, hashMap);
+					}
+					const job = {
+						module,
+						hash,
+						runtime,
+						runtimes: [runtime]
+					};
+					hashMap.set(module, job);
+					codeGenerationJobs.push(job);
+				}
 			}
-			if (d.module.removeReason(module, d)) {
-				for (const chunk of d.module.chunksIterable) {
-					this.patchChunksAfterReasonRemoval(d.module, chunk);
+			this.logger.timeAggregate("hashing: hash runtime modules");
+			try {
+				this.logger.time("hashing: hash chunks");
+				const chunkHash = createHash(hashFunction);
+				if (outputOptions.hashSalt) {
+					chunkHash.update(outputOptions.hashSalt);
 				}
+				chunk.updateHash(chunkHash, chunkGraph);
+				this.hooks.chunkHash.call(chunk, chunkHash, {
+					chunkGraph,
+					codeGenerationResults:
+						/** @type {CodeGenerationResults} */
+						(this.codeGenerationResults),
+					moduleGraph: this.moduleGraph,
+					runtimeTemplate: this.runtimeTemplate
+				});
+				const chunkHashDigest = chunkHash.digest(hashDigest);
+				hash.update(chunkHashDigest);
+				chunk.hash = chunkHashDigest;
+				chunk.renderedHash = chunk.hash.slice(0, hashDigestLength);
+				const fullHashModules =
+					chunkGraph.getChunkFullHashModulesIterable(chunk);
+				if (fullHashModules) {
+					fullHashChunks.add(chunk);
+				} else {
+					this.hooks.contentHash.call(chunk);
+				}
+			} catch (err) {
+				this.errors.push(
+					new (getChunkRenderError())(chunk, "", /** @type {Error} */ (err))
+				);
 			}
+			this.logger.timeAggregate("hashing: hash chunks");
 		};
-
-		if (block.blocks) {
-			iterationOfArrayCallback(block.blocks, block =>
-				this.removeReasonsOfDependencyBlock(module, block)
+		for (const chunk of asyncChunks) processChunk(chunk);
+		for (const chunk of initialChunks) processChunk(chunk);
+		for (const chunk of runtimeChunks) processChunk(chunk);
+		for (const chunk of entryChunks) processChunk(chunk);
+		if (errors.length > 0) {
+			errors.sort(
+				compareSelect((err) => err.module, compareModulesByIdentifier)
 			);
+			for (const error of errors) {
+				this.errors.push(error);
+			}
 		}
 
-		if (block.dependencies) {
-			iterationOfArrayCallback(block.dependencies, iteratorDependency);
-		}
-
-		if (block.variables) {
-			iterationBlockVariable(block.variables, iteratorDependency);
+		this.logger.timeAggregateEnd("hashing: hash runtime modules");
+		this.logger.timeAggregateEnd("hashing: hash chunks");
+		this.logger.time("hashing: hash digest");
+		this.hooks.fullHash.call(hash);
+		this.fullHash = hash.digest(hashDigest);
+		this.hash = this.fullHash.slice(0, hashDigestLength);
+		this.logger.timeEnd("hashing: hash digest");
+
+		this.logger.time("hashing: process full hash modules");
+		for (const chunk of fullHashChunks) {
+			for (const module of /** @type {Iterable} */ (
+				chunkGraph.getChunkFullHashModulesIterable(chunk)
+			)) {
+				const moduleHash = createHash(hashFunction);
+				module.updateHash(moduleHash, {
+					chunkGraph,
+					runtime: chunk.runtime,
+					runtimeTemplate
+				});
+				const moduleHashDigest = moduleHash.digest(hashDigest);
+				const oldHash = chunkGraph.getModuleHash(module, chunk.runtime);
+				chunkGraph.setModuleHashes(
+					module,
+					chunk.runtime,
+					moduleHashDigest,
+					moduleHashDigest.slice(0, hashDigestLength)
+				);
+				/** @type {CodeGenerationJob} */
+				(
+					/** @type {Map} */
+					(codeGenerationJobsMap.get(oldHash)).get(module)
+				).hash = moduleHashDigest;
+			}
+			const chunkHash = createHash(hashFunction);
+			chunkHash.update(/** @type {string} */ (chunk.hash));
+			chunkHash.update(this.hash);
+			const chunkHashDigest = chunkHash.digest(hashDigest);
+			chunk.hash = chunkHashDigest;
+			chunk.renderedHash = chunk.hash.slice(0, hashDigestLength);
+			this.hooks.contentHash.call(chunk);
 		}
+		this.logger.timeEnd("hashing: process full hash modules");
+		return codeGenerationJobs;
 	}
 
-	patchChunksAfterReasonRemoval(module, chunk) {
-		if (!module.hasReasons()) {
-			this.removeReasonsOfDependencyBlock(module, module);
-		}
-		if (!module.hasReasonForChunk(chunk)) {
-			if (module.removeChunk(chunk)) {
-				this.removeChunkFromDependencies(module, chunk);
+	/**
+	 * Processes the provided file.
+	 * @param {string} file file name
+	 * @param {Source} source asset source
+	 * @param {AssetInfo} assetInfo extra asset information
+	 * @returns {void}
+	 */
+	emitAsset(file, source, assetInfo = {}) {
+		// A file may be attached to a chunk right after any emit (including the
+		// re-emit path for assets shared across chunks in createChunkAssets), so
+		// the lazily-built reverse index is stale after every emitAsset.
+		this._assetToChunkIndex = undefined;
+		this._assetToChunkAuxiliaryIndex = undefined;
+		if (this.assets[file]) {
+			if (!isSourceEqual(this.assets[file], source)) {
+				this.errors.push(
+					new WebpackError(
+						`Conflict: Multiple assets emit different content to the same filename ${file}${
+							assetInfo.sourceFilename
+								? `. Original source ${assetInfo.sourceFilename}`
+								: ""
+						}`
+					)
+				);
+				this.assets[file] = source;
+				this._setAssetInfo(file, assetInfo);
+				return;
 			}
+			const oldInfo = this.assetsInfo.get(file);
+			const newInfo = { ...oldInfo, ...assetInfo };
+			this._setAssetInfo(file, newInfo, oldInfo);
+			return;
 		}
+		this.assets[file] = source;
+		this._setAssetInfo(file, assetInfo, undefined);
 	}
 
-	removeChunkFromDependencies(block, chunk) {
-		const iteratorDependency = d => {
-			if (!d.module) {
-				return;
+	/**
+	 * Processes the provided file.
+	 * @private
+	 * @param {string} file file name
+	 * @param {AssetInfo=} newInfo new asset information
+	 * @param {AssetInfo=} oldInfo old asset information
+	 */
+	_setAssetInfo(file, newInfo, oldInfo = this.assetsInfo.get(file)) {
+		if (newInfo === undefined) {
+			this.assetsInfo.delete(file);
+		} else {
+			this.assetsInfo.set(file, newInfo);
+		}
+		const oldRelated = oldInfo && oldInfo.related;
+		const newRelated = newInfo && newInfo.related;
+		if (oldRelated) {
+			for (const key of Object.keys(oldRelated)) {
+				/**
+				 * Processes the provided name.
+				 * @param {string} name name
+				 */
+				const remove = (name) => {
+					const relatedIn = this._assetsRelatedIn.get(name);
+					if (relatedIn === undefined) return;
+					const entry = relatedIn.get(key);
+					if (entry === undefined) return;
+					entry.delete(file);
+					if (entry.size !== 0) return;
+					relatedIn.delete(key);
+					if (relatedIn.size === 0) this._assetsRelatedIn.delete(name);
+				};
+				const entry = oldRelated[key];
+				if (Array.isArray(entry)) {
+					for (const name of entry) {
+						remove(name);
+					}
+				} else if (entry) {
+					remove(entry);
+				}
 			}
-			this.patchChunksAfterReasonRemoval(d.module, chunk);
-		};
-
-		const blocks = block.blocks;
-		for (let indexBlock = 0; indexBlock < blocks.length; indexBlock++) {
-			const chunks = blocks[indexBlock].chunks;
-			for (let indexChunk = 0; indexChunk < chunks.length; indexChunk++) {
-				const blockChunk = chunks[indexChunk];
-				chunk.removeChunk(blockChunk);
-				blockChunk.removeParent(chunk);
-				this.removeChunkFromDependencies(chunks, blockChunk);
+		}
+		if (newRelated) {
+			for (const key of Object.keys(newRelated)) {
+				/**
+				 * Processes the provided name.
+				 * @param {string} name name
+				 */
+				const add = (name) => {
+					let relatedIn = this._assetsRelatedIn.get(name);
+					if (relatedIn === undefined) {
+						this._assetsRelatedIn.set(name, (relatedIn = new Map()));
+					}
+					let entry = relatedIn.get(key);
+					if (entry === undefined) {
+						relatedIn.set(key, (entry = new Set()));
+					}
+					entry.add(file);
+				};
+				const entry = newRelated[key];
+				if (Array.isArray(entry)) {
+					for (const name of entry) {
+						add(name);
+					}
+				} else if (entry) {
+					add(entry);
+				}
 			}
 		}
+	}
 
-		if (block.dependencies) {
-			iterationOfArrayCallback(block.dependencies, iteratorDependency);
+	/**
+	 * Updates asset using the provided file.
+	 * @param {string} file file name
+	 * @param {Source | ((source: Source) => Source)} newSourceOrFunction new asset source or function converting old to new
+	 * @param {(AssetInfo | ((assetInfo?: AssetInfo) => AssetInfo | undefined)) | undefined} assetInfoUpdateOrFunction new asset info or function converting old to new
+	 */
+	updateAsset(
+		file,
+		newSourceOrFunction,
+		assetInfoUpdateOrFunction = undefined
+	) {
+		if (!this.assets[file]) {
+			throw new Error(
+				`Called Compilation.updateAsset for not existing filename ${file}`
+			);
 		}
-
-		if (block.variables) {
-			iterationBlockVariable(block.variables, iteratorDependency);
+		this.assets[file] =
+			typeof newSourceOrFunction === "function"
+				? newSourceOrFunction(this.assets[file])
+				: newSourceOrFunction;
+		if (assetInfoUpdateOrFunction !== undefined) {
+			const oldInfo = this.assetsInfo.get(file) || EMPTY_ASSET_INFO;
+			if (typeof assetInfoUpdateOrFunction === "function") {
+				this._setAssetInfo(file, assetInfoUpdateOrFunction(oldInfo), oldInfo);
+			} else {
+				this._setAssetInfo(
+					file,
+					cachedCleverMerge(oldInfo, assetInfoUpdateOrFunction),
+					oldInfo
+				);
+			}
 		}
 	}
 
-	applyModuleIds() {
-		const unusedIds = [];
-		let nextFreeModuleId = 0;
-		const usedIds = new Set();
-		if (this.usedModuleIds) {
-			for (const id of this.usedModuleIds) {
-				usedIds.add(id);
+	/**
+	 * Processes the provided file.
+	 * @param {string} file file name
+	 * @param {string} newFile the new name of file
+	 */
+	renameAsset(file, newFile) {
+		const source = this.assets[file];
+		if (!source) {
+			throw new Error(
+				`Called Compilation.renameAsset for not existing filename ${file}`
+			);
+		}
+		if (this.assets[newFile] && !isSourceEqual(this.assets[file], source)) {
+			this.errors.push(
+				new WebpackError(
+					`Conflict: Called Compilation.renameAsset for already existing filename ${newFile} with different content`
+				)
+			);
+		}
+		const assetInfo = this.assetsInfo.get(file);
+		// Update related in all other assets
+		const relatedInInfo = this._assetsRelatedIn.get(file);
+		if (relatedInInfo) {
+			for (const [key, assets] of relatedInInfo) {
+				for (const name of assets) {
+					const info = this.assetsInfo.get(name);
+					if (!info) continue;
+					const related = info.related;
+					if (!related) continue;
+					const entry = related[key];
+					/** @type {string | string[]} */
+					let newEntry;
+					if (Array.isArray(entry)) {
+						newEntry = entry.map((x) => (x === file ? newFile : x));
+					} else if (entry === file) {
+						newEntry = newFile;
+					} else {
+						continue;
+					}
+					this.assetsInfo.set(name, {
+						...info,
+						related: {
+							...related,
+							[key]: newEntry
+						}
+					});
+				}
 			}
 		}
-
-		const modules1 = this.modules;
-		for (let indexModule1 = 0; indexModule1 < modules1.length; indexModule1++) {
-			const module1 = modules1[indexModule1];
-			if (module1.id !== null) {
-				usedIds.add(module1.id);
+		this._setAssetInfo(file, undefined, assetInfo);
+		this._setAssetInfo(newFile, assetInfo);
+		delete this.assets[file];
+		this.assets[newFile] = source;
+		this._buildAssetToChunkIndex();
+		const index = /** @type {Map>} */ (
+			this._assetToChunkIndex
+		);
+		const auxiliaryIndex = /** @type {Map>} */ (
+			this._assetToChunkAuxiliaryIndex
+		);
+		const chunks = index.get(file);
+		const auxiliaryChunks = auxiliaryIndex.get(file);
+		if (chunks === undefined && auxiliaryChunks === undefined) {
+			// Not tracked in either index: chunk sets may have been mutated
+			// directly (bypassing emitAsset), so scan all chunks to stay correct.
+			for (const chunk of this.chunks) {
+				this._renameAssetInChunk(chunk, chunk.files, index, file, newFile);
+				this._renameAssetInChunk(
+					chunk,
+					chunk.auxiliaryFiles,
+					auxiliaryIndex,
+					file,
+					newFile
+				);
 			}
+			return;
 		}
-
-		if (usedIds.size > 0) {
-			let usedIdMax = -1;
-			for (const usedIdKey of usedIds) {
-				if (typeof usedIdKey !== "number") {
-					continue;
-				}
-
-				usedIdMax = Math.max(usedIdMax, usedIdKey);
+		if (chunks !== undefined) {
+			index.delete(file);
+			for (const chunk of chunks) {
+				this._renameAssetInChunk(chunk, chunk.files, index, file, newFile);
 			}
-
-			let lengthFreeModules = (nextFreeModuleId = usedIdMax + 1);
-
-			while (lengthFreeModules--) {
-				if (!usedIds.has(lengthFreeModules)) {
-					unusedIds.push(lengthFreeModules);
-				}
+		}
+		if (auxiliaryChunks !== undefined) {
+			auxiliaryIndex.delete(file);
+			for (const chunk of auxiliaryChunks) {
+				this._renameAssetInChunk(
+					chunk,
+					chunk.auxiliaryFiles,
+					auxiliaryIndex,
+					file,
+					newFile
+				);
 			}
 		}
+	}
 
-		const modules2 = this.modules;
-		for (let indexModule2 = 0; indexModule2 < modules2.length; indexModule2++) {
-			const module2 = modules2[indexModule2];
-			if (module2.id === null) {
-				if (unusedIds.length > 0) {
-					module2.id = unusedIds.pop();
-				} else {
-					module2.id = nextFreeModuleId++;
-				}
+	// Lazily build the reverse index file -> chunks. emitAsset() invalidates
+	// it, so chunk.files / chunk.auxiliaryFiles entries added alongside a newly
+	// emitted asset are always picked up on the next rebuild.
+	/**
+	 * @private
+	 * @returns {void}
+	 */
+	_buildAssetToChunkIndex() {
+		if (this._assetToChunkIndex !== undefined) return;
+		/** @type {Map>} */
+		const filesIndex = new Map();
+		/** @type {Map>} */
+		const auxiliaryIndex = new Map();
+		for (const chunk of this.chunks) {
+			for (const file of chunk.files) {
+				let set = filesIndex.get(file);
+				if (set === undefined) filesIndex.set(file, (set = new Set()));
+				set.add(chunk);
+			}
+			for (const file of chunk.auxiliaryFiles) {
+				let set = auxiliaryIndex.get(file);
+				if (set === undefined) auxiliaryIndex.set(file, (set = new Set()));
+				set.add(chunk);
 			}
 		}
+		this._assetToChunkIndex = filesIndex;
+		this._assetToChunkAuxiliaryIndex = auxiliaryIndex;
 	}
 
-	applyChunkIds() {
-		const usedIds = new Set();
+	/**
+	 * @private
+	 * @param {Chunk} chunk the chunk owning the set
+	 * @param {Set} set chunk.files or chunk.auxiliaryFiles
+	 * @param {Map>} index matching reverse index
+	 * @param {string} file old file name
+	 * @param {string} newFile new file name
+	 * @returns {void}
+	 */
+	_renameAssetInChunk(chunk, set, index, file, newFile) {
+		// Only carry the rename to chunks that actually held the old name.
+		if (!set.delete(file)) return;
+		set.add(newFile);
+		let target = index.get(newFile);
+		if (target === undefined) index.set(newFile, (target = new Set()));
+		target.add(chunk);
+	}
 
-		// Get used ids from usedChunkIds property (i. e. from records)
-		if (this.usedChunkIds) {
-			for (const id of this.usedChunkIds) {
-				if (typeof id !== "number") {
-					continue;
+	/**
+	 * Processes the provided file.
+	 * @param {string} file file name
+	 */
+	deleteAsset(file) {
+		if (!this.assets[file]) {
+			return;
+		}
+		delete this.assets[file];
+		const assetInfo = this.assetsInfo.get(file);
+		this._setAssetInfo(file, undefined, assetInfo);
+		const related = assetInfo && assetInfo.related;
+		if (related) {
+			for (const key of Object.keys(related)) {
+				/**
+				 * Checks used and delete.
+				 * @param {string} file file
+				 */
+				const checkUsedAndDelete = (file) => {
+					if (!this._assetsRelatedIn.has(file)) {
+						this.deleteAsset(file);
+					}
+				};
+				const items = related[key];
+				if (Array.isArray(items)) {
+					for (const file of items) {
+						checkUsedAndDelete(file);
+					}
+				} else if (items) {
+					checkUsedAndDelete(items);
 				}
-
-				usedIds.add(id);
 			}
 		}
-
-		// Get used ids from existing chunks
-		const chunks = this.chunks;
-		for (let indexChunk = 0; indexChunk < chunks.length; indexChunk++) {
-			const chunk = chunks[indexChunk];
-			const usedIdValue = chunk.id;
-
-			if (typeof usedIdValue !== "number") {
-				continue;
+		this._buildAssetToChunkIndex();
+		const index = /** @type {Map>} */ (
+			this._assetToChunkIndex
+		);
+		const auxiliaryIndex = /** @type {Map>} */ (
+			this._assetToChunkAuxiliaryIndex
+		);
+		const chunks = index.get(file);
+		const auxiliaryChunks = auxiliaryIndex.get(file);
+		if (chunks === undefined && auxiliaryChunks === undefined) {
+			// Not tracked in either index: chunk sets may have been mutated
+			// directly (bypassing emitAsset), so scan all chunks to stay correct.
+			for (const chunk of this.chunks) {
+				chunk.files.delete(file);
+				chunk.auxiliaryFiles.delete(file);
 			}
-
-			usedIds.add(usedIdValue);
+			return;
 		}
-
-		// Calculate maximum assigned chunk id
-		let nextFreeChunkId = -1;
-		for (const id of usedIds) {
-			nextFreeChunkId = Math.max(nextFreeChunkId, id);
+		if (chunks !== undefined) {
+			for (const chunk of chunks) chunk.files.delete(file);
+			index.delete(file);
 		}
-		nextFreeChunkId++;
-
-		// Determine free chunk ids from 0 to maximum
-		const unusedIds = [];
-		if (nextFreeChunkId > 0) {
-			let index = nextFreeChunkId;
-			while (index--) {
-				if (!usedIds.has(index)) {
-					unusedIds.push(index);
-				}
-			}
+		if (auxiliaryChunks !== undefined) {
+			for (const chunk of auxiliaryChunks) chunk.auxiliaryFiles.delete(file);
+			auxiliaryIndex.delete(file);
 		}
+	}
 
-		// Assign ids to chunk which has no id
-		for (let indexChunk = 0; indexChunk < chunks.length; indexChunk++) {
-			const chunk = chunks[indexChunk];
-			if (chunk.id === null) {
-				if (unusedIds.length > 0) {
-					chunk.id = unusedIds.pop();
-				} else {
-					chunk.id = nextFreeChunkId++;
-				}
-			}
-			if (!chunk.ids) {
-				chunk.ids = [chunk.id];
+	getAssets() {
+		/** @type {Readonly[]} */
+		const array = [];
+		for (const assetName of Object.keys(this.assets)) {
+			if (Object.prototype.hasOwnProperty.call(this.assets, assetName)) {
+				array.push({
+					name: assetName,
+					source: this.assets[assetName],
+					info: this.assetsInfo.get(assetName) || EMPTY_ASSET_INFO
+				});
 			}
 		}
+		return array;
 	}
 
-	sortItemsWithModuleIds() {
-		this.modules.sort(byIdOrIdentifier);
+	/**
+	 * Returns the asset or undefined when not found.
+	 * @param {string} name the name of the asset
+	 * @returns {Readonly | undefined} the asset or undefined when not found
+	 */
+	getAsset(name) {
+		if (!Object.prototype.hasOwnProperty.call(this.assets, name)) return;
+		return {
+			name,
+			source: this.assets[name],
+			info: this.assetsInfo.get(name) || EMPTY_ASSET_INFO
+		};
+	}
 
-		const modules = this.modules;
-		for (let indexModule = 0; indexModule < modules.length; indexModule++) {
-			modules[indexModule].sortItems(false);
+	clearAssets() {
+		for (const chunk of this.chunks) {
+			chunk.files.clear();
+			chunk.auxiliaryFiles.clear();
 		}
+		this._assetToChunkIndex = undefined;
+		this._assetToChunkAuxiliaryIndex = undefined;
+	}
 
-		const chunks = this.chunks;
-		for (let indexChunk = 0; indexChunk < chunks.length; indexChunk++) {
-			chunks[indexChunk].sortItems(false);
+	createModuleAssets() {
+		const { chunkGraph } = this;
+		for (const module of this.modules) {
+			const buildInfo = /** @type {BuildInfo} */ (module.buildInfo);
+			if (buildInfo.assets) {
+				const assetsInfo = buildInfo.assetsInfo;
+				for (const assetName of Object.keys(buildInfo.assets)) {
+					const fileName = this.getPath(assetName, {
+						chunkGraph: this.chunkGraph,
+						module
+					});
+					for (const chunk of chunkGraph.getModuleChunksIterable(module)) {
+						chunk.auxiliaryFiles.add(fileName);
+					}
+					this.emitAsset(
+						fileName,
+						buildInfo.assets[assetName],
+						assetsInfo ? assetsInfo.get(assetName) : undefined
+					);
+					this.hooks.moduleAsset.call(module, fileName);
+				}
+			}
 		}
 	}
 
-	sortItemsWithChunkIds() {
-		for (const chunkGroup of this.chunkGroups) {
-			chunkGroup.sortItems();
-		}
+	/**
+	 * Gets render manifest.
+	 * @param {RenderManifestOptions} options options object
+	 * @returns {RenderManifestEntry[]} manifest entries
+	 */
+	getRenderManifest(options) {
+		return this.hooks.renderManifest.call([], options);
+	}
 
-		this.chunks.sort(byId);
+	/**
+	 * Creates a chunk assets.
+	 * @param {Callback} callback signals when the call finishes
+	 * @returns {void}
+	 */
+	createChunkAssets(callback) {
+		const outputOptions = this.outputOptions;
+		/** @type {WeakMap} */
+		const cachedSourceMap = new WeakMap();
+		/** @type {Map} */
+		const alreadyWrittenFiles = new Map();
+
+		asyncLib.forEachLimit(
+			this.chunks,
+			15,
+			(chunk, callback) => {
+				/** @type {RenderManifestEntry[]} */
+				let manifest;
+				try {
+					manifest = this.getRenderManifest({
+						chunk,
+						hash: /** @type {string} */ (this.hash),
+						fullHash: /** @type {string} */ (this.fullHash),
+						outputOptions,
+						codeGenerationResults:
+							/** @type {CodeGenerationResults} */
+							(this.codeGenerationResults),
+						moduleTemplates: this.moduleTemplates,
+						dependencyTemplates: this.dependencyTemplates,
+						chunkGraph: this.chunkGraph,
+						moduleGraph: this.moduleGraph,
+						runtimeTemplate: this.runtimeTemplate
+					});
+				} catch (err) {
+					this.errors.push(
+						new (getChunkRenderError())(chunk, "", /** @type {Error} */ (err))
+					);
+					return callback();
+				}
+				asyncLib.each(
+					manifest,
+					(fileManifest, callback) => {
+						const ident = fileManifest.identifier;
+						const usedHash = /** @type {string} */ (fileManifest.hash);
+
+						const assetCacheItem = this._assetsCache.getItemCache(
+							ident,
+							usedHash
+						);
 
-		for (
-			let indexModule = 0;
-			indexModule < this.modules.length;
-			indexModule++
-		) {
-			this.modules[indexModule].sortItems(true);
-		}
+						assetCacheItem.get((err, sourceFromCache) => {
+							/** @type {string | import("./TemplatedPathPlugin").TemplatePathFn} */
+							let filenameTemplate;
+							/** @type {string} */
+							let file;
+							/** @type {AssetInfo} */
+							let assetInfo;
+
+							let inTry = true;
+							/**
+							 * Error and callback.
+							 * @param {Error} err error
+							 * @returns {void}
+							 */
+							const errorAndCallback = (err) => {
+								const filename =
+									file ||
+									(typeof file === "string"
+										? file
+										: typeof filenameTemplate === "string"
+											? filenameTemplate
+											: "");
+
+								this.errors.push(
+									new (getChunkRenderError())(chunk, filename, err)
+								);
+								inTry = false;
+								return callback();
+							};
 
-		const chunks = this.chunks;
-		for (let indexChunk = 0; indexChunk < chunks.length; indexChunk++) {
-			chunks[indexChunk].sortItems(true);
-		}
+							try {
+								if ("filename" in fileManifest) {
+									file = fileManifest.filename;
+									assetInfo = fileManifest.info;
+								} else {
+									filenameTemplate = fileManifest.filenameTemplate;
+									const pathAndInfo = this.getPathWithInfo(
+										filenameTemplate,
+										fileManifest.pathOptions
+									);
+									file = pathAndInfo.path;
+									assetInfo = fileManifest.info
+										? {
+												...pathAndInfo.info,
+												...fileManifest.info
+											}
+										: pathAndInfo.info;
+								}
 
-		const byMessage = (a, b) => {
-			const ma = `${a.message}`;
-			const mb = `${b.message}`;
-			if (ma < mb) return -1;
-			if (mb < ma) return 1;
-			return 0;
-		};
+								if (err) {
+									return errorAndCallback(err);
+								}
 
-		this.errors.sort(byMessage);
-		this.warnings.sort(byMessage);
-		this.children.sort(byNameOrHash);
+								let source = sourceFromCache;
+
+								// check if the same filename was already written by another chunk
+								const alreadyWritten = alreadyWrittenFiles.get(file);
+								if (alreadyWritten !== undefined) {
+									if (alreadyWritten.hash !== usedHash) {
+										inTry = false;
+										return callback(
+											new WebpackError(
+												`Conflict: Multiple chunks emit assets to the same filename ${file}` +
+													` (chunks ${alreadyWritten.chunk.id} and ${chunk.id})`
+											)
+										);
+									}
+									source = alreadyWritten.source;
+								} else if (!source) {
+									// render the asset
+									source = fileManifest.render();
+
+									// Ensure that source is a cached source to avoid additional cost because of repeated access
+									if (!(source instanceof CachedSource)) {
+										const cacheEntry = cachedSourceMap.get(source);
+										if (cacheEntry) {
+											source = cacheEntry;
+										} else {
+											const cachedSource = new CachedSource(source);
+											cachedSourceMap.set(source, cachedSource);
+											source = cachedSource;
+										}
+									}
+								}
+								this.emitAsset(file, source, assetInfo);
+								if (fileManifest.auxiliary) {
+									chunk.auxiliaryFiles.add(file);
+								} else {
+									chunk.files.add(file);
+								}
+								this.hooks.chunkAsset.call(chunk, file);
+								alreadyWrittenFiles.set(file, {
+									hash: usedHash,
+									source,
+									chunk
+								});
+								if (source !== sourceFromCache) {
+									assetCacheItem.store(source, (err) => {
+										if (err) return errorAndCallback(err);
+										inTry = false;
+										return callback();
+									});
+								} else {
+									inTry = false;
+									callback();
+								}
+							} catch (err) {
+								if (!inTry) throw err;
+								errorAndCallback(/** @type {Error} */ (err));
+							}
+						});
+					},
+					callback
+				);
+			},
+			callback
+		);
 	}
 
-	summarizeDependencies() {
-		this.fileDependencies = new SortableSet(this.compilationDependencies);
-		this.contextDependencies = new SortableSet();
-		this.missingDependencies = new SortableSet();
-
-		for (
-			let indexChildren = 0;
-			indexChildren < this.children.length;
-			indexChildren++
-		) {
-			const child = this.children[indexChildren];
-
-			addAllToSet(this.fileDependencies, child.fileDependencies);
-			addAllToSet(this.contextDependencies, child.contextDependencies);
-			addAllToSet(this.missingDependencies, child.missingDependencies);
-		}
-
-		for (
-			let indexModule = 0;
-			indexModule < this.modules.length;
-			indexModule++
-		) {
-			const module = this.modules[indexModule];
-
-			if (module.buildInfo.fileDependencies) {
-				addAllToSet(this.fileDependencies, module.buildInfo.fileDependencies);
-			}
-			if (module.buildInfo.contextDependencies) {
-				addAllToSet(
-					this.contextDependencies,
-					module.buildInfo.contextDependencies
-				);
-			}
-		}
-		for (const error of this.errors) {
-			if (
-				typeof error.missing === "object" &&
-				error.missing &&
-				error.missing[Symbol.iterator]
-			) {
-				addAllToSet(this.missingDependencies, error.missing);
-			}
+	/**
+	 * Returns interpolated path.
+	 * @template {PathData} [T=PathData]
+	 * @param {string | import("./TemplatedPathPlugin").TemplatePathFn} filename used to get asset path with hash
+	 * @param {T=} data context data
+	 * @returns {string} interpolated path
+	 */
+	getPath(filename, data = /** @type {T} */ ({})) {
+		if (!data.hash) {
+			data = {
+				hash: this.hash,
+				...data
+			};
 		}
-		this.fileDependencies.sort();
-		this.contextDependencies.sort();
-		this.missingDependencies.sort();
+		return this.getAssetPath(filename, data);
 	}
 
-	createHash() {
-		const outputOptions = this.outputOptions;
-		const hashFunction = outputOptions.hashFunction;
-		const hashDigest = outputOptions.hashDigest;
-		const hashDigestLength = outputOptions.hashDigestLength;
-		const hash = createHash(hashFunction);
-		if (outputOptions.hashSalt) {
-			hash.update(outputOptions.hashSalt);
-		}
-		this.mainTemplate.updateHash(hash);
-		this.chunkTemplate.updateHash(hash);
-		for (const key of Object.keys(this.moduleTemplates).sort()) {
-			this.moduleTemplates[key].updateHash(hash);
-		}
-		for (const child of this.children) {
-			hash.update(child.hash);
-		}
-		for (const warning of this.warnings) {
-			hash.update(`${warning.message}`);
-		}
-		for (const error of this.errors) {
-			hash.update(`${error.message}`);
-		}
-		const modules = this.modules;
-		for (let i = 0; i < modules.length; i++) {
-			const module = modules[i];
-			const moduleHash = createHash(hashFunction);
-			module.updateHash(moduleHash);
-			module.hash = moduleHash.digest(hashDigest);
-			module.renderedHash = module.hash.substr(0, hashDigestLength);
-		}
-		// clone needed as sort below is inplace mutation
-		const chunks = this.chunks.slice();
-		/**
-		 * sort here will bring all "falsy" values to the beginning
-		 * this is needed as the "hasRuntime()" chunks are dependent on the
-		 * hashes of the non-runtime chunks.
-		 */
-		chunks.sort((a, b) => {
-			const aEntry = a.hasRuntime();
-			const bEntry = b.hasRuntime();
-			if (aEntry && !bEntry) return 1;
-			if (!aEntry && bEntry) return -1;
-			return byId(a, b);
-		});
-		for (let i = 0; i < chunks.length; i++) {
-			const chunk = chunks[i];
-			const chunkHash = createHash(hashFunction);
-			if (outputOptions.hashSalt) {
-				chunkHash.update(outputOptions.hashSalt);
-			}
-			chunk.updateHash(chunkHash);
-			const template = chunk.hasRuntime()
-				? this.mainTemplate
-				: this.chunkTemplate;
-			template.updateHashForChunk(chunkHash, chunk);
-			this.hooks.chunkHash.call(chunk, chunkHash);
-			chunk.hash = chunkHash.digest(hashDigest);
-			hash.update(chunk.hash);
-			chunk.renderedHash = chunk.hash.substr(0, hashDigestLength);
-			this.hooks.contentHash.call(chunk);
+	/**
+	 * Gets path with info.
+	 * @template {PathData} [T=PathData]
+	 * @param {string | import("./TemplatedPathPlugin").TemplatePathFn} filename used to get asset path with hash
+	 * @param {T=} data context data
+	 * @returns {InterpolatedPathAndAssetInfo} interpolated path and asset info
+	 */
+	getPathWithInfo(filename, data = /** @type {T} */ ({})) {
+		if (!data.hash) {
+			data = {
+				hash: this.hash,
+				...data
+			};
 		}
-		this.fullHash = hash.digest(hashDigest);
-		this.hash = this.fullHash.substr(0, hashDigestLength);
+		return this.getAssetPathWithInfo(filename, data);
 	}
 
-	modifyHash(update) {
-		const outputOptions = this.outputOptions;
-		const hashFunction = outputOptions.hashFunction;
-		const hashDigest = outputOptions.hashDigest;
-		const hashDigestLength = outputOptions.hashDigestLength;
-		const hash = createHash(hashFunction);
-		hash.update(this.fullHash);
-		hash.update(update);
-		this.fullHash = hash.digest(hashDigest);
-		this.hash = this.fullHash.substr(0, hashDigestLength);
+	/**
+	 * Returns interpolated path.
+	 * @template {PathData} [T=PathData]
+	 * @param {string | import("./TemplatedPathPlugin").TemplatePathFn} filename used to get asset path with hash
+	 * @param {T} data context data
+	 * @returns {string} interpolated path
+	 */
+	getAssetPath(filename, data) {
+		return this.hooks.assetPath.call(
+			typeof filename === "function" ? filename(data) : filename,
+			data,
+			undefined
+		);
 	}
 
-	createModuleAssets() {
-		for (let i = 0; i < this.modules.length; i++) {
-			const module = this.modules[i];
-			if (module.buildInfo.assets) {
-				for (const assetName of Object.keys(module.buildInfo.assets)) {
-					const fileName = this.getPath(assetName);
-					this.assets[fileName] = module.buildInfo.assets[assetName];
-					this.hooks.moduleAsset.call(module, fileName);
-				}
-			}
-		}
+	/**
+	 * Gets asset path with info.
+	 * @template {PathData} [T=PathData]
+	 * @param {string | import("./TemplatedPathPlugin").TemplatePathFn} filename used to get asset path with hash
+	 * @param {T} data context data
+	 * @returns {InterpolatedPathAndAssetInfo} interpolated path and asset info
+	 */
+	getAssetPathWithInfo(filename, data) {
+		const assetInfo = {};
+		// TODO webpack 5: refactor assetPath hook to receive { path, info } object
+		const newPath = this.hooks.assetPath.call(
+			typeof filename === "function" ? filename(data, assetInfo) : filename,
+			data,
+			assetInfo
+		);
+		return { path: newPath, info: assetInfo };
 	}
 
-	createChunkAssets() {
-		const outputOptions = this.outputOptions;
-		const cachedSourceMap = new Map();
-		for (let i = 0; i < this.chunks.length; i++) {
-			const chunk = this.chunks[i];
-			chunk.files = [];
-			let source;
-			let file;
-			let filenameTemplate;
-			try {
-				const template = chunk.hasRuntime()
-					? this.mainTemplate
-					: this.chunkTemplate;
-				const manifest = template.getRenderManifest({
-					chunk,
-					hash: this.hash,
-					fullHash: this.fullHash,
-					outputOptions,
-					moduleTemplates: this.moduleTemplates,
-					dependencyTemplates: this.dependencyTemplates
-				}); // [{ render(), filenameTemplate, pathOptions, identifier, hash }]
-				for (const fileManifest of manifest) {
-					const cacheName = fileManifest.identifier;
-					const usedHash = fileManifest.hash;
-					filenameTemplate = fileManifest.filenameTemplate;
-					if (
-						this.cache &&
-						this.cache[cacheName] &&
-						this.cache[cacheName].hash === usedHash
-					) {
-						source = this.cache[cacheName].source;
-					} else {
-						source = fileManifest.render();
-						// Ensure that source is a cached source to avoid additional cost because of repeated access
-						if (!(source instanceof CachedSource)) {
-							const cacheEntry = cachedSourceMap.get(source);
-							if (cacheEntry) {
-								source = cacheEntry;
-							} else {
-								const cachedSource = new CachedSource(source);
-								cachedSourceMap.set(source, cachedSource);
-								source = cachedSource;
-							}
-						}
-						if (this.cache) {
-							this.cache[cacheName] = {
-								hash: usedHash,
-								source
-							};
-						}
-					}
-					file = this.getPath(filenameTemplate, fileManifest.pathOptions);
-					if (this.assets[file] && this.assets[file] !== source) {
-						throw new Error(
-							`Conflict: Multiple assets emit to the same filename ${file}`
-						);
-					}
-					this.assets[file] = source;
-					chunk.files.push(file);
-					this.hooks.chunkAsset.call(chunk, file);
-				}
-			} catch (err) {
-				this.errors.push(
-					new ChunkRenderError(chunk, file || filenameTemplate, err)
-				);
-			}
-		}
+	getWarnings() {
+		return this.hooks.processWarnings.call(this.warnings);
 	}
 
-	getPath(filename, data) {
-		data = data || {};
-		data.hash = data.hash || this.hash;
-		return this.mainTemplate.getAssetPath(filename, data);
+	getErrors() {
+		return this.hooks.processErrors.call(this.errors);
 	}
 
+	/**
+	 * This function allows you to run another instance of webpack inside of webpack however as
+	 * a child with different settings and configurations (if desired) applied. It copies all hooks, plugins
+	 * from parent (or top level compiler) and creates a child Compilation
+	 * @param {string} name name of the child compiler
+	 * @param {Partial=} outputOptions // Need to convert config schema to types for this
+	 * @param {Plugins=} plugins webpack plugins that will be applied
+	 * @returns {Compiler} creates a child Compiler instance
+	 */
 	createChildCompiler(name, outputOptions, plugins) {
 		const idx = this.childrenCounters[name] || 0;
 		this.childrenCounters[name] = idx + 1;
@@ -1898,12 +5912,437 @@ class Compilation extends Tapable {
 		);
 	}
 
+	/**
+	 * Processes the provided module.
+	 * @param {Module} module the module
+	 * @param {ExecuteModuleOptions} options options
+	 * @param {ExecuteModuleCallback} callback callback
+	 */
+	executeModule(module, options, callback) {
+		// Aggregate all referenced modules and ensure they are ready
+		const modules = new Set([module]);
+		processAsyncTree(
+			modules,
+			10,
+			(module, push, callback) => {
+				this.buildQueue.waitFor(module, (err) => {
+					if (err) return callback(err);
+					this.processDependenciesQueue.waitFor(module, (err) => {
+						if (err) return callback(err);
+						for (const { module: m } of this.moduleGraph.getOutgoingConnections(
+							module
+						)) {
+							const size = modules.size;
+							modules.add(m);
+							if (modules.size !== size) push(m);
+						}
+						callback();
+					});
+				});
+			},
+			(err) => {
+				if (err) return callback(/** @type {WebpackError} */ (err));
+
+				// Create new chunk graph, chunk and entrypoint for the build time execution
+				const chunkGraph = new ChunkGraph(
+					this.moduleGraph,
+					this.outputOptions.hashFunction
+				);
+				chunkGraph.buildTimeExecution = true;
+				const runtime = "build time";
+				const { hashFunction, hashDigest, hashDigestLength } =
+					this.outputOptions;
+				const runtimeTemplate = this.runtimeTemplate;
+
+				const chunk = new Chunk("build time chunk", this._backCompat);
+				chunk.id = /** @type {ChunkId} */ (chunk.name);
+				chunk.ids = [chunk.id];
+				chunk.runtime = runtime;
+
+				const entrypoint = new Entrypoint({
+					runtime,
+					chunkLoading: false,
+					...options.entryOptions
+				});
+				chunkGraph.connectChunkAndEntryModule(chunk, module, entrypoint);
+				if (entrypoint.pushChunk(chunk)) {
+					chunk.addGroup(entrypoint);
+				}
+				entrypoint.setRuntimeChunk(chunk);
+				entrypoint.setEntrypointChunk(chunk);
+
+				const chunks = new Set([chunk]);
+
+				// Assign ids to modules and modules to the chunk
+				for (const module of modules) {
+					const id = module.identifier();
+					chunkGraph.setModuleId(module, id);
+					chunkGraph.connectChunkAndModule(chunk, module);
+				}
+
+				/** @type {WebpackError[]} */
+				const errors = [];
+
+				// Hash modules
+				for (const module of modules) {
+					this._createModuleHash(
+						module,
+						chunkGraph,
+						runtime,
+						hashFunction,
+						runtimeTemplate,
+						hashDigest,
+						hashDigestLength,
+						errors
+					);
+				}
+
+				const codeGenerationResults = new CodeGenerationResults(
+					this.outputOptions.hashFunction
+				);
+				/**
+				 * Processes the provided module.
+				 * @param {Module} module the module
+				 * @param {Callback} callback callback
+				 * @returns {void}
+				 */
+				const codeGen = (module, callback) => {
+					this._codeGenerationModule(
+						module,
+						runtime,
+						[runtime],
+						chunkGraph.getModuleHash(module, runtime),
+						this.dependencyTemplates,
+						chunkGraph,
+						this.moduleGraph,
+						runtimeTemplate,
+						errors,
+						codeGenerationResults,
+						(err, _codeGenerated) => {
+							callback(err);
+						}
+					);
+				};
+
+				const reportErrors = () => {
+					if (errors.length > 0) {
+						errors.sort(
+							compareSelect((err) => err.module, compareModulesByIdentifier)
+						);
+						for (const error of errors) {
+							this.errors.push(error);
+						}
+						errors.length = 0;
+					}
+				};
+
+				// Generate code for all aggregated modules
+				asyncLib.eachLimit(
+					/** @type {import("neo-async").IterableCollection} */ (
+						/** @type {unknown} */ (modules)
+					),
+					10,
+					codeGen,
+					(err) => {
+						if (err) return callback(err);
+						reportErrors();
+
+						// for backward-compat temporary set the chunk graph
+						// TODO webpack 6
+						const old = this.chunkGraph;
+						this.chunkGraph = chunkGraph;
+						this.processRuntimeRequirements({
+							chunkGraph,
+							modules,
+							chunks,
+							codeGenerationResults,
+							chunkGraphEntries: chunks
+						});
+						// restored before the deferred attach so no concurrent `make`
+						// task observes the build-time graph
+						this.chunkGraph = old;
+						this._attachPendingRuntimeModules((err) => {
+							if (err) return callback(err);
+
+							const runtimeModules =
+								chunkGraph.getChunkRuntimeModulesIterable(chunk);
+
+							// Hash runtime modules
+							for (const module of runtimeModules) {
+								modules.add(module);
+								this._createModuleHash(
+									module,
+									chunkGraph,
+									runtime,
+									hashFunction,
+									runtimeTemplate,
+									hashDigest,
+									hashDigestLength,
+									errors
+								);
+							}
+
+							// Generate code for all runtime modules
+							asyncLib.eachLimit(
+								/** @type {import("neo-async").IterableCollection} */ (
+									runtimeModules
+								),
+								10,
+								codeGen,
+								(err) => {
+									if (err) return callback(err);
+									reportErrors();
+
+									/** @type {Map} */
+									const moduleArgumentsMap = new Map();
+									/** @type {Map} */
+									const moduleArgumentsById = new Map();
+
+									/** @type {ExecuteModuleResult["fileDependencies"]} */
+									const fileDependencies = new LazySet();
+									/** @type {ExecuteModuleResult["contextDependencies"]} */
+									const contextDependencies = new LazySet();
+									/** @type {ExecuteModuleResult["missingDependencies"]} */
+									const missingDependencies = new LazySet();
+									/** @type {ExecuteModuleResult["buildDependencies"]} */
+									const buildDependencies = new LazySet();
+
+									/** @type {ExecuteModuleResult["assets"]} */
+									const assets = new Map();
+
+									let cacheable = true;
+									/** @type {ExecuteModuleResult["notCacheableReasons"]} */
+									const notCacheableReasons = [];
+
+									/** @type {ExecuteModuleContext} */
+									const context = {
+										assets,
+										__webpack_require__: undefined,
+										chunk,
+										chunkGraph
+									};
+
+									// Prepare execution
+									asyncLib.eachLimit(
+										modules,
+										10,
+										(module, callback) => {
+											const codeGenerationResult = codeGenerationResults.get(
+												module,
+												runtime
+											);
+											/** @type {ExecuteModuleArgument} */
+											const moduleArgument = {
+												module,
+												codeGenerationResult,
+												moduleObject: undefined
+											};
+											moduleArgumentsMap.set(module, moduleArgument);
+											moduleArgumentsById.set(
+												module.identifier(),
+												moduleArgument
+											);
+											module.addCacheDependencies(
+												fileDependencies,
+												contextDependencies,
+												missingDependencies,
+												buildDependencies
+											);
+											const buildInfo = /** @type {BuildInfo} */ (
+												module.buildInfo
+											);
+											if (buildInfo.cacheable === false) {
+												cacheable = false;
+												if (buildInfo.notCacheableReasons) {
+													for (const reason of buildInfo.notCacheableReasons) {
+														if (!notCacheableReasons.includes(reason)) {
+															notCacheableReasons.push(reason);
+														}
+													}
+												}
+											}
+											if (module.buildInfo && module.buildInfo.assets) {
+												const { assets: moduleAssets, assetsInfo } =
+													module.buildInfo;
+												for (const assetName of Object.keys(moduleAssets)) {
+													assets.set(assetName, {
+														source: moduleAssets[assetName],
+														info: assetsInfo
+															? assetsInfo.get(assetName)
+															: undefined
+													});
+												}
+											}
+											this.hooks.prepareModuleExecution.callAsync(
+												moduleArgument,
+												context,
+												callback
+											);
+										},
+										(err) => {
+											if (err) {
+												return callback(/** @type {WebpackError} */ (err));
+											}
+
+											/** @type {ExecuteModuleExports | undefined} */
+											let exports;
+											try {
+												const {
+													strictModuleErrorHandling,
+													strictModuleExceptionHandling
+												} = this.outputOptions;
+
+												/** @type {WebpackRequire} */
+												const __webpack_require__ = (id) => {
+													const cached = moduleCache[id];
+													if (cached !== undefined) {
+														if (cached.error) throw cached.error;
+														return cached.exports;
+													}
+													const moduleArgument = moduleArgumentsById.get(id);
+													return __webpack_require_module__(
+														/** @type {ExecuteModuleArgument} */
+														(moduleArgument),
+														id
+													);
+												};
+												const interceptModuleExecution = (__webpack_require__[
+													/** @type {"i"} */
+													(
+														RuntimeGlobals.interceptModuleExecution.replace(
+															`${RuntimeGlobals.require}.`,
+															""
+														)
+													)
+												] =
+													/** @type {NonNullable} */ ([]));
+												const moduleCache = (__webpack_require__[
+													/** @type {"c"} */ (
+														RuntimeGlobals.moduleCache.replace(
+															`${RuntimeGlobals.require}.`,
+															""
+														)
+													)
+												] =
+													/** @type {NonNullable} */ ({}));
+
+												context.__webpack_require__ = __webpack_require__;
+
+												/**
+												 * Webpack require module.
+												 * @param {ExecuteModuleArgument} moduleArgument the module argument
+												 * @param {string=} id id
+												 * @returns {ExecuteModuleExports} exports
+												 */
+												const __webpack_require_module__ = (
+													moduleArgument,
+													id
+												) => {
+													/** @type {ExecuteOptions} */
+													const execOptions = {
+														id,
+														module: {
+															id,
+															exports: {},
+															loaded: false,
+															error: undefined
+														},
+														require: __webpack_require__
+													};
+													for (const handler of interceptModuleExecution) {
+														handler(execOptions);
+													}
+													const module = moduleArgument.module;
+													this.buildTimeExecutedModules.add(module);
+													const moduleObject = execOptions.module;
+													moduleArgument.moduleObject = moduleObject;
+													try {
+														if (id) moduleCache[id] = moduleObject;
+
+														tryRunOrWebpackError(
+															() =>
+																this.hooks.executeModule.call(
+																	moduleArgument,
+																	context
+																),
+															"Compilation.hooks.executeModule"
+														);
+														moduleObject.loaded = true;
+														return moduleObject.exports;
+													} catch (execErr) {
+														if (strictModuleExceptionHandling) {
+															if (id) delete moduleCache[id];
+														} else if (strictModuleErrorHandling) {
+															moduleObject.error =
+																/** @type {WebpackError} */
+																(execErr);
+														}
+														if (
+															!(/** @type {WebpackError} */ (execErr).module)
+														) {
+															/** @type {WebpackError} */
+															(execErr).module = module;
+														}
+														throw execErr;
+													}
+												};
+
+												for (const runtimeModule of chunkGraph.getChunkRuntimeModulesInOrder(
+													chunk
+												)) {
+													__webpack_require_module__(
+														/** @type {ExecuteModuleArgument} */
+														(moduleArgumentsMap.get(runtimeModule))
+													);
+												}
+
+												exports = __webpack_require__(module.identifier());
+											} catch (execErr) {
+												const { message, stack, module } =
+													/** @type {WebpackError} */
+													(execErr);
+												const err = new WebpackError(
+													`Execution of module code from module graph (${
+														/** @type {Module} */
+														(module).readableIdentifier(this.requestShortener)
+													}) failed: ${message}`,
+													{ cause: execErr }
+												);
+												err.stack = stack;
+												err.module = module;
+												return callback(err);
+											}
+
+											callback(null, {
+												exports,
+												assets,
+												cacheable,
+												notCacheableReasons,
+												fileDependencies,
+												contextDependencies,
+												missingDependencies,
+												buildDependencies
+											});
+										}
+									);
+								}
+							);
+						});
+					}
+				);
+			}
+		);
+	}
+
 	checkConstraints() {
+		const chunkGraph = this.chunkGraph;
+
+		/** @type {Set} */
 		const usedIds = new Set();
 
-		const modules = this.modules;
-		for (let indexModule = 0; indexModule < modules.length; indexModule++) {
-			const moduleId = modules[indexModule].id;
+		for (const module of this.modules) {
+			if (module.type === WEBPACK_MODULE_TYPE_RUNTIME) continue;
+			const moduleId = chunkGraph.getModuleId(module);
 			if (moduleId === null) continue;
 			if (usedIds.has(moduleId)) {
 				throw new Error(`checkConstraints: duplicate module id ${moduleId}`);
@@ -1911,13 +6350,22 @@ class Compilation extends Tapable {
 			usedIds.add(moduleId);
 		}
 
-		const chunks = this.chunks;
-		for (let indexChunk = 0; indexChunk < chunks.length; indexChunk++) {
-			const chunk = chunks[indexChunk];
-			if (chunks.indexOf(chunk) !== indexChunk) {
-				throw new Error(
-					`checkConstraints: duplicate chunk in compilation ${chunk.debugId}`
-				);
+		for (const chunk of this.chunks) {
+			for (const module of chunkGraph.getChunkModulesIterable(chunk)) {
+				if (!this.modules.has(module)) {
+					throw new Error(
+						"checkConstraints: module in chunk but not in compilation " +
+							` ${chunk.debugId} ${module.debugId}`
+					);
+				}
+			}
+			for (const module of chunkGraph.getChunkEntryModulesIterable(chunk)) {
+				if (!this.modules.has(module)) {
+					throw new Error(
+						"checkConstraints: entry module in chunk but not in compilation " +
+							` ${chunk.debugId} ${module.debugId}`
+					);
+				}
 			}
 		}
 
@@ -1927,22 +6375,146 @@ class Compilation extends Tapable {
 	}
 }
 
-// TODO remove in webpack 5
-Compilation.prototype.applyPlugins = util.deprecate(function(name, ...args) {
-	this.hooks[
-		name.replace(/[- ]([a-z])/g, match => match[1].toUpperCase())
-	].call(...args);
-}, "Compilation.applyPlugins is deprecated. Use new API on `.hooks` instead");
+/**
+ * Defines the factorize module options type used by this module.
+ * @typedef {object} FactorizeModuleOptions
+ * @property {ModuleProfile=} currentProfile
+ * @property {ModuleFactory} factory
+ * @property {Dependency[]} dependencies
+ * @property {boolean=} factoryResult return full ModuleFactoryResult instead of only module
+ * @property {Module | null} originModule
+ * @property {Partial=} contextInfo
+ * @property {string=} context
+ */
+
+/**
+ * Processes the provided factorize module option.
+ * @param {FactorizeModuleOptions} options options object
+ * @param {ModuleCallback | ModuleFactoryResultCallback} callback callback
+ * @returns {void}
+ */
+
+// Hide from typescript
+const compilationPrototype = Compilation.prototype;
+
+// TODO webpack 6 remove
+Object.defineProperty(compilationPrototype, "modifyHash", {
+	writable: false,
+	enumerable: false,
+	configurable: false,
+	value: () => {
+		throw new Error(
+			"Compilation.modifyHash was removed in favor of Compilation.hooks.fullHash"
+		);
+	}
+});
 
-// TODO remove in webpack 5
-Object.defineProperty(Compilation.prototype, "moduleTemplate", {
+// TODO webpack 6 remove
+Object.defineProperty(compilationPrototype, "cache", {
+	enumerable: false,
 	configurable: false,
-	get: util.deprecate(function() {
-		return this.moduleTemplates.javascript;
-	}, "Compilation.moduleTemplate: Use Compilation.moduleTemplates.javascript instead"),
-	set: util.deprecate(function(value) {
-		this.moduleTemplates.javascript = value;
-	}, "Compilation.moduleTemplate: Use Compilation.moduleTemplates.javascript instead.")
+	get: util.deprecate(
+		/**
+		 * Returns the cache.
+		 * @this {Compilation} the compilation
+		 * @returns {Cache} the cache
+		 */
+		function cache() {
+			return this.compiler.cache;
+		},
+		"Compilation.cache was removed in favor of Compilation.getCache()",
+		"DEP_WEBPACK_COMPILATION_CACHE"
+	),
+	set: util.deprecate(
+		/**
+		 * Handles the value callback for this hook.
+		 * @param {EXPECTED_ANY} _v value
+		 */
+		(_v) => {},
+		"Compilation.cache was removed in favor of Compilation.getCache()",
+		"DEP_WEBPACK_COMPILATION_CACHE"
+	)
 });
 
+/**
+ * Add additional assets to the compilation.
+ */
+Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL = -2000;
+
+/**
+ * Basic preprocessing of assets.
+ */
+Compilation.PROCESS_ASSETS_STAGE_PRE_PROCESS = -1000;
+
+/**
+ * Derive new assets from existing assets.
+ * Existing assets should not be treated as complete.
+ */
+Compilation.PROCESS_ASSETS_STAGE_DERIVED = -200;
+
+/**
+ * Add additional sections to existing assets, like a banner or initialization code.
+ */
+Compilation.PROCESS_ASSETS_STAGE_ADDITIONS = -100;
+
+/**
+ * Optimize existing assets in a general way.
+ */
+Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE = 100;
+
+/**
+ * Optimize the count of existing assets, e. g. by merging them.
+ * Only assets of the same type should be merged.
+ * For assets of different types see PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE.
+ */
+Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_COUNT = 200;
+
+/**
+ * Optimize the compatibility of existing assets, e. g. add polyfills or vendor-prefixes.
+ */
+Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_COMPATIBILITY = 300;
+
+/**
+ * Optimize the size of existing assets, e. g. by minimizing or omitting whitespace.
+ */
+Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE = 400;
+
+/**
+ * Add development tooling to assets, e. g. by extracting a SourceMap.
+ */
+Compilation.PROCESS_ASSETS_STAGE_DEV_TOOLING = 500;
+
+/**
+ * Optimize the count of existing assets, e. g. by inlining assets of into other assets.
+ * Only assets of different types should be inlined.
+ * For assets of the same type see PROCESS_ASSETS_STAGE_OPTIMIZE_COUNT.
+ */
+Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE = 700;
+
+/**
+ * Summarize the list of existing assets
+ * e. g. creating an assets manifest of Service Workers.
+ */
+Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE = 1000;
+
+/**
+ * Optimize the hashes of the assets, e. g. by generating real hashes of the asset content.
+ */
+Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_HASH = 2500;
+
+/**
+ * Optimize the transfer of existing assets, e. g. by preparing a compressed (gzip) file as separate asset.
+ */
+Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER = 3000;
+
+/**
+ * Analyse existing assets.
+ */
+Compilation.PROCESS_ASSETS_STAGE_ANALYSE = 4000;
+
+/**
+ * Creating assets for reporting purposes.
+ */
+Compilation.PROCESS_ASSETS_STAGE_REPORT = 5000;
+
 module.exports = Compilation;
diff --git a/lib/Compiler.js b/lib/Compiler.js
index 11d51471bc7..3b4001eceb7 100644
--- a/lib/Compiler.js
+++ b/lib/Compiler.js
@@ -2,245 +2,761 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
-const parseJson = require("json-parse-better-errors");
 const asyncLib = require("neo-async");
-const path = require("path");
-const util = require("util");
+
 const {
-	Tapable,
-	SyncHook,
-	SyncBailHook,
 	AsyncParallelHook,
-	AsyncSeriesHook
+	AsyncSeriesHook,
+	SyncBailHook,
+	SyncHook
 } = require("tapable");
-
+const { SizeOnlySource } = require("webpack-sources");
+const Cache = require("./Cache");
+const CacheFacade = require("./CacheFacade");
+const ChunkGraph = require("./ChunkGraph");
 const Compilation = require("./Compilation");
-const Stats = require("./Stats");
-const Watching = require("./Watching");
-const NormalModuleFactory = require("./NormalModuleFactory");
 const ContextModuleFactory = require("./ContextModuleFactory");
-const ResolverFactory = require("./ResolverFactory");
-
+const ModuleGraph = require("./ModuleGraph");
+const NormalModuleFactory = require("./NormalModuleFactory");
 const RequestShortener = require("./RequestShortener");
-const { makePathsRelative } = require("./util/identifier");
-const ConcurrentCompilationError = require("./ConcurrentCompilationError");
-
-class Compiler extends Tapable {
-	constructor(context) {
-		super();
-		this.hooks = {
+const ResolverFactory = require("./ResolverFactory");
+const Stats = require("./Stats");
+const WebpackError = require("./errors/WebpackError");
+const { Logger } = require("./logging/Logger");
+const { dirname, join, mkdirp } = require("./util/fs");
+const {
+	WINDOWS_ABS_PATH_REGEXP,
+	makePathsRelative
+} = require("./util/identifier");
+const memoize = require("./util/memoize");
+const parseJson = require("./util/parseJson");
+const { isSourceEqual } = require("./util/source");
+const webpack = require(".");
+
+const getWatching = memoize(() => require("./Watching"));
+
+const getValidate = memoize(() => require("schema-utils").validate);
+
+const getConcurrentCompilationError = memoize(() =>
+	require("./errors/ConcurrentCompilationError")
+);
+
+/** @typedef {import("webpack-sources").Source} Source */
+/** @typedef {import("../declarations/WebpackOptions").EntryNormalized} Entry */
+/** @typedef {import("../declarations/WebpackOptions").OutputNormalized} OutputOptions */
+/** @typedef {import("../declarations/WebpackOptions").WatchOptions} WatchOptions */
+/** @typedef {import("../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */
+/** @typedef {import("../declarations/WebpackOptions").Plugins} Plugins */
+/** @typedef {import("./webpack").WebpackPluginFunction} WebpackPluginFunction */
+/** @typedef {import("./Chunk")} Chunk */
+/** @typedef {import("./Dependency")} Dependency */
+/** @typedef {import("./HotModuleReplacementPlugin").ChunkHashes} ChunkHashes */
+/** @typedef {import("./HotModuleReplacementPlugin").ChunkModuleHashes} ChunkModuleHashes */
+/** @typedef {import("./HotModuleReplacementPlugin").ChunkModuleIds} ChunkModuleIds */
+/** @typedef {import("./HotModuleReplacementPlugin").ChunkRuntime} ChunkRuntime */
+/** @typedef {import("./HotModuleReplacementPlugin").FullHashChunkModuleHashes} FullHashChunkModuleHashes */
+/** @typedef {import("./HotModuleReplacementPlugin").HotIndex} HotIndex */
+/** @typedef {import("./Module")} Module */
+/** @typedef {import("./Module").BuildInfo} BuildInfo */
+/** @typedef {import("./Watching")} Watching */
+/** @typedef {import("./RecordIdsPlugin").RecordsChunks} RecordsChunks */
+/** @typedef {import("./RecordIdsPlugin").RecordsModules} RecordsModules */
+/** @typedef {import("./config/target").PlatformTargetProperties} PlatformTargetProperties */
+/** @typedef {import("./logging/createConsoleLogger").LoggingFunction} LoggingFunction */
+/** @typedef {import("./optimize/AggressiveSplittingPlugin").SplitData} SplitData */
+/** @typedef {import("./util/fs").IStats} IStats */
+/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
+/** @typedef {import("./util/fs").IntermediateFileSystem} IntermediateFileSystem */
+/** @typedef {import("./util/fs").OutputFileSystem} OutputFileSystem */
+/** @typedef {import("./util/fs").TimeInfoEntries} TimeInfoEntries */
+/** @typedef {import("./util/fs").WatchFileSystem} WatchFileSystem */
+/** @typedef {import("schema-utils").validate} Validate */
+/** @typedef {import("schema-utils").Schema} Schema */
+/** @typedef {import("schema-utils").ValidationErrorConfiguration} ValidationErrorConfiguration */
+
+/**
+ * Defines the compilation params type used by this module.
+ * @typedef {object} CompilationParams
+ * @property {NormalModuleFactory} normalModuleFactory
+ * @property {ContextModuleFactory} contextModuleFactory
+ */
+
+/**
+ * Defines the callback type used by this module.
+ * @template T
+ * @template [R=void]
+ * @typedef {import("./webpack").Callback} Callback
+ */
+
+/** @typedef {import("./webpack").ErrorCallback} ErrorCallback */
+
+/**
+ * Defines the run as child callback callback.
+ * @callback RunAsChildCallback
+ * @param {Error | null} err
+ * @param {Chunk[]=} entries
+ * @param {Compilation=} compilation
+ * @returns {void}
+ */
+
+/**
+ * Defines the known records type used by this module.
+ * @typedef {object} KnownRecords
+ * @property {SplitData[]=} aggressiveSplits
+ * @property {RecordsChunks=} chunks
+ * @property {RecordsModules=} modules
+ * @property {string=} hash
+ * @property {HotIndex=} hotIndex
+ * @property {FullHashChunkModuleHashes=} fullHashChunkModuleHashes
+ * @property {ChunkModuleHashes=} chunkModuleHashes
+ * @property {ChunkHashes=} chunkHashes
+ * @property {ChunkRuntime=} chunkRuntime
+ * @property {ChunkModuleIds=} chunkModuleIds
+ */
+
+/** @typedef {KnownRecords & Record & Record} Records */
+
+/**
+ * Defines the asset emitted info type used by this module.
+ * @typedef {object} AssetEmittedInfo
+ * @property {Buffer} content
+ * @property {Source} source
+ * @property {Compilation} compilation
+ * @property {string} outputPath
+ * @property {string} targetPath
+ */
+
+/** @typedef {{ sizeOnlySource: SizeOnlySource | undefined, writtenTo: Map }} CacheEntry */
+/** @typedef {{ path: string, source: Source, size: number | undefined, waiting: ({ cacheEntry: CacheEntry, file: string }[] | undefined) }} SimilarEntry */
+
+/** @typedef {WeakMap} WeakReferences */
+/** @typedef {import("./util/WeakTupleMap")} MemCache */
+/** @typedef {{ buildInfo: BuildInfo, references: WeakReferences | undefined, memCache: MemCache }} ModuleMemCachesItem */
+
+/**
+ * Checks whether this object is sorted.
+ * @template T
+ * @param {T[]} array an array
+ * @returns {boolean} true, if the array is sorted
+ */
+const isSorted = (array) => {
+	for (let i = 1; i < array.length; i++) {
+		if (array[i - 1] > array[i]) return false;
+	}
+	return true;
+};
+
+/**
+ * Returns the object with properties sorted by property name.
+ * @template {object} T
+ * @param {T} obj an object
+ * @param {(keyof T)[]} keys the keys of the object
+ * @returns {T} the object with properties sorted by property name
+ */
+const sortObject = (obj, keys) => {
+	const o = /** @type {T} */ ({});
+	for (const k of keys.sort()) {
+		o[k] = obj[k];
+	}
+	return o;
+};
+
+/**
+ * Returns true, if the filename contains any hash.
+ * @param {string} filename filename
+ * @param {string | string[] | undefined} hashes list of hashes
+ * @returns {boolean} true, if the filename contains any hash
+ */
+const includesHash = (filename, hashes) => {
+	if (!hashes) return false;
+	if (Array.isArray(hashes)) {
+		return hashes.some((hash) => filename.includes(hash));
+	}
+	return filename.includes(hashes);
+};
+
+// only reached by `watch()`, so a one-shot build never loads the watcher
+
+class Compiler {
+	/**
+	 * Creates an instance of Compiler.
+	 * @param {string} context the compilation path
+	 * @param {WebpackOptions} options options
+	 */
+	constructor(context, options = /** @type {WebpackOptions} */ ({})) {
+		this.hooks = Object.freeze({
+			/** @type {SyncHook<[]>} */
+			initialize: new SyncHook([]),
+
+			/** @type {SyncBailHook<[Compilation], boolean | void>} */
 			shouldEmit: new SyncBailHook(["compilation"]),
+			/** @type {AsyncSeriesHook<[Stats]>} */
 			done: new AsyncSeriesHook(["stats"]),
+			/** @type {SyncHook<[Stats]>} */
+			afterDone: new SyncHook(["stats"]),
+			/** @type {AsyncSeriesHook<[]>} */
 			additionalPass: new AsyncSeriesHook([]),
-			beforeRun: new AsyncSeriesHook(["compilation"]),
-			run: new AsyncSeriesHook(["compilation"]),
+			/** @type {AsyncSeriesHook<[Compiler]>} */
+			beforeRun: new AsyncSeriesHook(["compiler"]),
+			/** @type {AsyncSeriesHook<[Compiler]>} */
+			run: new AsyncSeriesHook(["compiler"]),
+			/** @type {AsyncSeriesHook<[Compilation]>} */
 			emit: new AsyncSeriesHook(["compilation"]),
+			/** @type {AsyncSeriesHook<[string, AssetEmittedInfo]>} */
+			assetEmitted: new AsyncSeriesHook(["file", "info"]),
+			/** @type {AsyncSeriesHook<[Compilation]>} */
 			afterEmit: new AsyncSeriesHook(["compilation"]),
+
+			/** @type {SyncHook<[Compilation, CompilationParams]>} */
 			thisCompilation: new SyncHook(["compilation", "params"]),
+			/** @type {SyncHook<[Compilation, CompilationParams]>} */
 			compilation: new SyncHook(["compilation", "params"]),
+			/** @type {SyncHook<[NormalModuleFactory]>} */
 			normalModuleFactory: new SyncHook(["normalModuleFactory"]),
-			contextModuleFactory: new SyncHook(["contextModulefactory"]),
+			/** @type {SyncHook<[ContextModuleFactory]>}  */
+			contextModuleFactory: new SyncHook(["contextModuleFactory"]),
+
+			/** @type {AsyncSeriesHook<[CompilationParams]>} */
 			beforeCompile: new AsyncSeriesHook(["params"]),
+			/** @type {SyncHook<[CompilationParams]>} */
 			compile: new SyncHook(["params"]),
+			/** @type {AsyncParallelHook<[Compilation]>} */
 			make: new AsyncParallelHook(["compilation"]),
+			/** @type {AsyncParallelHook<[Compilation]>} */
+			finishMake: new AsyncSeriesHook(["compilation"]),
+			/** @type {AsyncSeriesHook<[Compilation]>} */
 			afterCompile: new AsyncSeriesHook(["compilation"]),
+
+			/**
+			 * @type {AsyncSeriesHook<[]>}
+			 * @since 5.67.0
+			 */
+			readRecords: new AsyncSeriesHook([]),
+			/**
+			 * @type {AsyncSeriesHook<[]>}
+			 * @since 5.67.0
+			 */
+			emitRecords: new AsyncSeriesHook([]),
+
+			/** @type {AsyncSeriesHook<[Compiler]>} */
 			watchRun: new AsyncSeriesHook(["compiler"]),
+			/** @type {SyncHook<[Error]>} */
 			failed: new SyncHook(["error"]),
+			/** @type {SyncHook<[string | null, number]>} */
 			invalid: new SyncHook(["filename", "changeTime"]),
+			/** @type {SyncHook<[]>} */
 			watchClose: new SyncHook([]),
+			/**
+			 * @type {AsyncSeriesHook<[]>}
+			 * @since 5.17.0
+			 */
+			shutdown: new AsyncSeriesHook([]),
+
+			/** @type {SyncBailHook<[string, string, EXPECTED_ANY[] | undefined], true | void>} */
+			infrastructureLog: new SyncBailHook(["origin", "type", "args"]),
 
 			// TODO the following hooks are weirdly located here
 			// TODO move them for webpack 5
+			/**
+			 * @type {SyncHook<[]>}
+			 * @since 5.106.0
+			 */
+			validate: new SyncHook([]),
+			/** @type {SyncHook<[]>} */
 			environment: new SyncHook([]),
+			/** @type {SyncHook<[]>} */
 			afterEnvironment: new SyncHook([]),
+			/** @type {SyncHook<[Compiler]>} */
 			afterPlugins: new SyncHook(["compiler"]),
+			/** @type {SyncHook<[Compiler]>} */
 			afterResolvers: new SyncHook(["compiler"]),
+			/** @type {SyncBailHook<[string, Entry], boolean | void>} */
 			entryOption: new SyncBailHook(["context", "entry"])
-		};
-		this._pluginCompat.tap("Compiler", options => {
-			switch (options.name) {
-				case "additional-pass":
-				case "before-run":
-				case "run":
-				case "emit":
-				case "after-emit":
-				case "before-compile":
-				case "make":
-				case "after-compile":
-				case "watch-run":
-					options.async = true;
-					break;
-			}
 		});
 
+		this.webpack = webpack;
+
+		/** @type {string | undefined} */
 		this.name = undefined;
+		/** @type {Compilation | undefined} */
 		this.parentCompilation = undefined;
+		/** @type {Compiler} */
+		this.root = this;
+		/** @type {string} */
 		this.outputPath = "";
+		/** @type {Watching | undefined} */
+		this.watching = undefined;
+
+		/** @type {OutputFileSystem | null} */
 		this.outputFileSystem = null;
+		/** @type {IntermediateFileSystem | null} */
+		this.intermediateFileSystem = null;
+		/** @type {InputFileSystem | null} */
 		this.inputFileSystem = null;
+		/** @type {WatchFileSystem | null} */
+		this.watchFileSystem = null;
 
+		/** @type {string | null} */
 		this.recordsInputPath = null;
+		/** @type {string | null} */
 		this.recordsOutputPath = null;
+		/** @type {Records} */
 		this.records = {};
+		/** @type {Set} */
+		this.managedPaths = new Set();
+		/** @type {Set} */
+		this.unmanagedPaths = new Set();
+		/** @type {Set} */
+		this.immutablePaths = new Set();
+
+		/** @type {ReadonlySet | undefined} */
+		this.modifiedFiles = undefined;
+		/** @type {ReadonlySet | undefined} */
+		this.removedFiles = undefined;
+		/** @type {TimeInfoEntries | undefined} */
+		this.fileTimestamps = undefined;
+		/** @type {TimeInfoEntries | undefined} */
+		this.contextTimestamps = undefined;
+		/** @type {number | undefined} */
+		this.fsStartTime = undefined;
+
+		/** @type {ResolverFactory} */
+		this.resolverFactory = new ResolverFactory();
 
-		this.fileTimestamps = new Map();
-		this.contextTimestamps = new Map();
+		/** @type {LoggingFunction | undefined} */
+		this.infrastructureLogger = undefined;
+
+		/** @type {Readonly} */
+		this.platform = {
+			web: null,
+			browser: null,
+			webworker: null,
+			node: null,
+			deno: null,
+			bun: null,
+			nwjs: null,
+			electron: null,
+			universal: null
+		};
 
-		this.resolverFactory = new ResolverFactory();
+		this.options = options;
 
-		// TODO remove in webpack 5
-		this.resolvers = {
-			normal: {
-				plugins: util.deprecate((hook, fn) => {
-					this.resolverFactory.plugin("resolver normal", resolver => {
-						resolver.plugin(hook, fn);
-					});
-				}, "webpack: Using compiler.resolvers.normal is deprecated.\n" + 'Use compiler.resolverFactory.plugin("resolver normal", resolver => {\n  resolver.plugin(/* … */);\n}); instead.'),
-				apply: util.deprecate((...args) => {
-					this.resolverFactory.plugin("resolver normal", resolver => {
-						resolver.apply(...args);
-					});
-				}, "webpack: Using compiler.resolvers.normal is deprecated.\n" + 'Use compiler.resolverFactory.plugin("resolver normal", resolver => {\n  resolver.apply(/* … */);\n}); instead.')
-			},
-			loader: {
-				plugins: util.deprecate((hook, fn) => {
-					this.resolverFactory.plugin("resolver loader", resolver => {
-						resolver.plugin(hook, fn);
-					});
-				}, "webpack: Using compiler.resolvers.loader is deprecated.\n" + 'Use compiler.resolverFactory.plugin("resolver loader", resolver => {\n  resolver.plugin(/* … */);\n}); instead.'),
-				apply: util.deprecate((...args) => {
-					this.resolverFactory.plugin("resolver loader", resolver => {
-						resolver.apply(...args);
-					});
-				}, "webpack: Using compiler.resolvers.loader is deprecated.\n" + 'Use compiler.resolverFactory.plugin("resolver loader", resolver => {\n  resolver.apply(/* … */);\n}); instead.')
+		/** @type {string} */
+		this.context = context;
+
+		/** @type {RequestShortener} */
+		this.requestShortener = new RequestShortener(context, this.root);
+
+		/** @type {Cache} */
+		this.cache = new Cache();
+
+		/** @type {Map | undefined} */
+		this.moduleMemCaches = undefined;
+
+		/** @type {string} */
+		this.compilerPath = "";
+
+		/** @type {boolean} */
+		this.running = false;
+
+		/** @type {boolean} */
+		this.idle = false;
+
+		/** @type {boolean} */
+		this.watchMode = false;
+
+		/** @type {boolean} */
+		this._backCompat = this.options.experiments.backCompat !== false;
+
+		/** @type {Compilation | undefined} */
+		this._lastCompilation = undefined;
+		/** @type {NormalModuleFactory | undefined} */
+		this._lastNormalModuleFactory = undefined;
+
+		/**
+		 * @private
+		 * @type {WeakMap}
+		 */
+		this._assetEmittingSourceCache = new WeakMap();
+		/**
+		 * @private
+		 * @type {Map}
+		 */
+		this._assetEmittingWrittenFiles = new Map();
+		/**
+		 * @private
+		 * @type {Set}
+		 */
+		this._assetEmittingPreviousFiles = new Set();
+	}
+
+	/**
+	 * Returns the cache facade instance.
+	 * @param {string} name cache name
+	 * @returns {CacheFacade} the cache facade instance
+	 */
+	getCache(name) {
+		return new CacheFacade(
+			this.cache,
+			`${this.compilerPath}${name}`,
+			this.options.output.hashFunction
+		);
+	}
+
+	/**
+	 * Gets infrastructure logger.
+	 * @param {string | (() => string)} name name of the logger, or function called once to get the logger name
+	 * @returns {Logger} a logger with that name
+	 */
+	getInfrastructureLogger(name) {
+		if (!name) {
+			throw new TypeError(
+				"Compiler.getInfrastructureLogger(name) called without a name"
+			);
+		}
+		return new Logger(
+			(type, args) => {
+				if (typeof name === "function") {
+					name = name();
+					if (!name) {
+						throw new TypeError(
+							"Compiler.getInfrastructureLogger(name) called with a function not returning a name"
+						);
+					}
+				}
+				if (
+					this.hooks.infrastructureLog.call(name, type, args) === undefined &&
+					this.infrastructureLogger !== undefined
+				) {
+					this.infrastructureLogger(name, type, args);
+				}
 			},
-			context: {
-				plugins: util.deprecate((hook, fn) => {
-					this.resolverFactory.plugin("resolver context", resolver => {
-						resolver.plugin(hook, fn);
+			(childName) => {
+				if (typeof name === "function") {
+					if (typeof childName === "function") {
+						return this.getInfrastructureLogger(() => {
+							if (typeof name === "function") {
+								name = name();
+								if (!name) {
+									throw new TypeError(
+										"Compiler.getInfrastructureLogger(name) called with a function not returning a name"
+									);
+								}
+							}
+							if (typeof childName === "function") {
+								childName = childName();
+								if (!childName) {
+									throw new TypeError(
+										"Logger.getChildLogger(name) called with a function not returning a name"
+									);
+								}
+							}
+							return `${name}/${childName}`;
+						});
+					}
+					return this.getInfrastructureLogger(() => {
+						if (typeof name === "function") {
+							name = name();
+							if (!name) {
+								throw new TypeError(
+									"Compiler.getInfrastructureLogger(name) called with a function not returning a name"
+								);
+							}
+						}
+						return `${name}/${childName}`;
 					});
-				}, "webpack: Using compiler.resolvers.context is deprecated.\n" + 'Use compiler.resolverFactory.plugin("resolver context", resolver => {\n  resolver.plugin(/* … */);\n}); instead.'),
-				apply: util.deprecate((...args) => {
-					this.resolverFactory.plugin("resolver context", resolver => {
-						resolver.apply(...args);
+				}
+				if (typeof childName === "function") {
+					return this.getInfrastructureLogger(() => {
+						if (typeof childName === "function") {
+							childName = childName();
+							if (!childName) {
+								throw new TypeError(
+									"Logger.getChildLogger(name) called with a function not returning a name"
+								);
+							}
+						}
+						return `${name}/${childName}`;
 					});
-				}, "webpack: Using compiler.resolvers.context is deprecated.\n" + 'Use compiler.resolverFactory.plugin("resolver context", resolver => {\n  resolver.apply(/* … */);\n}); instead.')
+				}
+				return this.getInfrastructureLogger(`${name}/${childName}`);
 			}
-		};
+		);
+	}
 
-		this.options = {};
+	// TODO webpack 6: solve this in a better way
+	// e.g. move compilation specific info from Modules into ModuleGraph
+	_cleanupLastCompilation() {
+		if (this._lastCompilation !== undefined) {
+			for (const childCompilation of this._lastCompilation.children) {
+				for (const module of childCompilation.modules) {
+					ChunkGraph.clearChunkGraphForModule(module);
+					ModuleGraph.clearModuleGraphForModule(module);
+					module.cleanupForCache();
+				}
+				for (const chunk of childCompilation.chunks) {
+					ChunkGraph.clearChunkGraphForChunk(chunk);
+				}
+			}
 
-		this.context = context;
+			for (const module of this._lastCompilation.modules) {
+				ChunkGraph.clearChunkGraphForModule(module);
+				ModuleGraph.clearModuleGraphForModule(module);
+				module.cleanupForCache();
+			}
+			for (const chunk of this._lastCompilation.chunks) {
+				ChunkGraph.clearChunkGraphForChunk(chunk);
+			}
+			this._lastCompilation = undefined;
+		}
+	}
 
-		this.requestShortener = new RequestShortener(context);
+	// TODO webpack 6: solve this in a better way
+	_cleanupLastNormalModuleFactory() {
+		if (this._lastNormalModuleFactory !== undefined) {
+			this._lastNormalModuleFactory.cleanupForCache();
+			this._lastNormalModuleFactory = undefined;
+		}
+	}
 
-		this.running = false;
+	/**
+	 * Release fields on a finished compilation that nothing reads after emit,
+	 * so the heap can shrink while user code still holds the Stats reference.
+	 * Recurses into child compilations. Stats output is preserved — only
+	 * codeGen byproducts are dropped.
+	 * @param {Compilation} compilation finished compilation to slim down
+	 * @returns {void}
+	 */
+	_releaseUnusedCompilationData(compilation) {
+		for (const child of compilation.children) {
+			this._releaseUnusedCompilationData(child);
+		}
+		// Rendered source per (module × runtime) — used only during seal/emit,
+		// never read by Stats, and not serialized to the persistent cache.
+		if (compilation.codeGenerationResults !== undefined) {
+			compilation.codeGenerationResults.map.clear();
+		}
 	}
 
+	/**
+	 * Returns a compiler watcher.
+	 * @param {WatchOptions} watchOptions the watcher's options
+	 * @param {Callback} handler signals when the call finishes
+	 * @returns {Watching | undefined} a compiler watcher
+	 */
 	watch(watchOptions, handler) {
-		if (this.running) return handler(new ConcurrentCompilationError());
+		if (this.running) {
+			handler(new (getConcurrentCompilationError())());
+			return;
+		}
 
 		this.running = true;
-		this.fileTimestamps = new Map();
-		this.contextTimestamps = new Map();
-		return new Watching(this, watchOptions, handler);
+		this.watchMode = true;
+		const Watching = getWatching();
+		this.watching = new Watching(this, watchOptions, handler);
+		return this.watching;
 	}
 
+	/**
+	 * Processes the provided stat.
+	 * @param {Callback} callback signals when the call finishes
+	 * @returns {void}
+	 */
 	run(callback) {
-		if (this.running) return callback(new ConcurrentCompilationError());
+		if (this.running) {
+			callback(new (getConcurrentCompilationError())());
+			return;
+		}
 
+		/** @type {Logger | undefined} */
+		let logger;
+
+		/**
+		 * Processes the provided err.
+		 * @param {Error | null} err error
+		 * @param {Stats=} stats stats
+		 */
 		const finalCallback = (err, stats) => {
+			if (logger) logger.time("beginIdle");
+			this.idle = true;
+			this.cache.beginIdle();
+			if (logger) logger.timeEnd("beginIdle");
 			this.running = false;
-
-			if (callback !== undefined) return callback(err, stats);
+			if (err) {
+				this.hooks.failed.call(err);
+			}
+			if (callback !== undefined) callback(err, stats);
+			this.hooks.afterDone.call(/** @type {Stats} */ (stats));
 		};
 
 		const startTime = Date.now();
 
 		this.running = true;
 
-		const onCompiled = (err, compilation) => {
+		/**
+		 * Processes the provided err.
+		 * @param {Error | null} err error
+		 * @param {Compilation=} _compilation compilation
+		 * @returns {void}
+		 */
+		const onCompiled = (err, _compilation) => {
 			if (err) return finalCallback(err);
 
+			const compilation = /** @type {Compilation} */ (_compilation);
+
 			if (this.hooks.shouldEmit.call(compilation) === false) {
+				compilation.startTime = startTime;
+				compilation.endTime = Date.now();
 				const stats = new Stats(compilation);
-				stats.startTime = startTime;
-				stats.endTime = Date.now();
-				this.hooks.done.callAsync(stats, err => {
+				this.hooks.done.callAsync(stats, (err) => {
 					if (err) return finalCallback(err);
 					return finalCallback(null, stats);
 				});
 				return;
 			}
 
-			this.emitAssets(compilation, err => {
-				if (err) return finalCallback(err);
+			process.nextTick(() => {
+				logger = compilation.getLogger("webpack.Compiler");
+				logger.time("emitAssets");
+				this.emitAssets(compilation, (err) => {
+					/** @type {Logger} */
+					(logger).timeEnd("emitAssets");
+					if (err) return finalCallback(err);
 
-				if (compilation.hooks.needAdditionalPass.call()) {
-					compilation.needAdditionalPass = true;
+					if (compilation.hooks.needAdditionalPass.call()) {
+						compilation.needAdditionalPass = true;
+
+						compilation.startTime = startTime;
+						compilation.endTime = Date.now();
+						/** @type {Logger} */
+						(logger).time("done hook");
+						const stats = new Stats(compilation);
+						this.hooks.done.callAsync(stats, (err) => {
+							/** @type {Logger} */
+							(logger).timeEnd("done hook");
+							if (err) return finalCallback(err);
+
+							this.hooks.additionalPass.callAsync((err) => {
+								if (err) return finalCallback(err);
+								this.compile(onCompiled);
+							});
+						});
+						return;
+					}
 
-					const stats = new Stats(compilation);
-					stats.startTime = startTime;
-					stats.endTime = Date.now();
-					this.hooks.done.callAsync(stats, err => {
+					/** @type {Logger} */
+					(logger).time("emitRecords");
+					this.emitRecords((err) => {
+						/** @type {Logger} */
+						(logger).timeEnd("emitRecords");
 						if (err) return finalCallback(err);
 
-						this.hooks.additionalPass.callAsync(err => {
+						compilation.startTime = startTime;
+						compilation.endTime = Date.now();
+						/** @type {Logger} */
+						(logger).time("done hook");
+						const stats = new Stats(compilation);
+						this.hooks.done.callAsync(stats, (err) => {
+							/** @type {Logger} */
+							(logger).timeEnd("done hook");
 							if (err) return finalCallback(err);
-							this.compile(onCompiled);
+							this.cache.storeBuildDependencies(
+								compilation.buildDependencies,
+								(err) => {
+									if (err) return finalCallback(err);
+									return finalCallback(null, stats);
+								}
+							);
 						});
 					});
-					return;
-				}
+				});
+			});
+		};
+
+		const run = () => {
+			this.hooks.beforeRun.callAsync(this, (err) => {
+				if (err) return finalCallback(err);
 
-				this.emitRecords(err => {
+				this.hooks.run.callAsync(this, (err) => {
 					if (err) return finalCallback(err);
 
-					const stats = new Stats(compilation);
-					stats.startTime = startTime;
-					stats.endTime = Date.now();
-					this.hooks.done.callAsync(stats, err => {
+					this.readRecords((err) => {
 						if (err) return finalCallback(err);
-						return finalCallback(null, stats);
+
+						this.compile(onCompiled);
 					});
 				});
 			});
 		};
 
-		this.hooks.beforeRun.callAsync(this, err => {
-			if (err) return finalCallback(err);
-
-			this.hooks.run.callAsync(this, err => {
+		if (this.idle) {
+			this.cache.endIdle((err) => {
 				if (err) return finalCallback(err);
 
-				this.readRecords(err => {
-					if (err) return finalCallback(err);
-
-					this.compile(onCompiled);
-				});
+				this.idle = false;
+				run();
 			});
-		});
+		} else {
+			run();
+		}
 	}
 
+	/**
+	 * Processes the provided run as child callback.
+	 * @param {RunAsChildCallback} callback signals when the call finishes
+	 * @returns {void}
+	 */
 	runAsChild(callback) {
-		this.compile((err, compilation) => {
-			if (err) return callback(err);
+		const startTime = Date.now();
+
+		/**
+		 * Processes the provided err.
+		 * @param {Error | null} err error
+		 * @param {Chunk[]=} entries entries
+		 * @param {Compilation=} compilation compilation
+		 */
+		const finalCallback = (err, entries, compilation) => {
+			try {
+				callback(err, entries, compilation);
+			} catch (runAsChildErr) {
+				const err = new WebpackError(
+					`compiler.runAsChild callback error: ${runAsChildErr}`,
+					{ cause: runAsChildErr }
+				);
+				err.details = /** @type {Error} */ (runAsChildErr).stack;
+				/** @type {Compilation} */
+				(this.parentCompilation).errors.push(err);
+			}
+		};
+
+		this.compile((err, _compilation) => {
+			if (err) return finalCallback(err);
+
+			const compilation = /** @type {Compilation} */ (_compilation);
+			const parentCompilation = /** @type {Compilation} */ (
+				this.parentCompilation
+			);
+
+			parentCompilation.children.push(compilation);
+
+			for (const { name, source, info } of compilation.getAssets()) {
+				parentCompilation.emitAsset(name, source, info);
+			}
+
+			/** @type {Chunk[]} */
+			const entries = [];
 
-			this.parentCompilation.children.push(compilation);
-			for (const name of Object.keys(compilation.assets)) {
-				this.parentCompilation.assets[name] = compilation.assets[name];
+			for (const ep of compilation.entrypoints.values()) {
+				entries.push(...ep.chunks);
 			}
 
-			const entries = Array.from(
-				compilation.entrypoints.values(),
-				ep => ep.chunks
-			).reduce((array, chunks) => {
-				return array.concat(chunks);
-			}, []);
+			compilation.startTime = startTime;
+			compilation.endTime = Date.now();
 
-			return callback(null, entries, compilation);
+			return finalCallback(null, entries, compilation);
 		});
 	}
 
@@ -250,124 +766,520 @@ class Compiler extends Tapable {
 		}
 	}
 
+	/**
+	 * Processes the provided compilation.
+	 * @param {Compilation} compilation the compilation
+	 * @param {ErrorCallback} callback signals when the assets are emitted
+	 * @returns {void}
+	 */
 	emitAssets(compilation, callback) {
+		/** @type {string} */
 		let outputPath;
 
-		const emitFiles = err => {
+		/**
+		 * Processes the provided err.
+		 * @param {Error=} err error
+		 * @returns {void}
+		 */
+		const emitFiles = (err) => {
 			if (err) return callback(err);
 
-			asyncLib.forEach(
-				compilation.assets,
-				(source, file, callback) => {
+			const assets = compilation.getAssets();
+			compilation.assets = { ...compilation.assets };
+			/** @type {Map} */
+			const caseInsensitiveMap = new Map();
+			/** @type {Set} */
+			const allTargetPaths = new Set();
+			asyncLib.forEachLimit(
+				assets,
+				15,
+				({ name: file, source, info }, callback) => {
 					let targetFile = file;
-					const queryStringIdx = targetFile.indexOf("?");
-					if (queryStringIdx >= 0) {
-						targetFile = targetFile.substr(0, queryStringIdx);
+					let immutable = info.immutable;
+					const queryOrHashStringIdx = targetFile.search(/[?#]/);
+					if (queryOrHashStringIdx >= 0) {
+						targetFile = targetFile.slice(0, queryOrHashStringIdx);
+						// We may remove the hash, which is in the query string
+						// So we recheck if the file is immutable
+						// This doesn't cover all cases, but immutable is only a performance optimization anyway
+						immutable =
+							immutable &&
+							(includesHash(targetFile, info.contenthash) ||
+								includesHash(targetFile, info.chunkhash) ||
+								includesHash(targetFile, info.modulehash) ||
+								includesHash(targetFile, info.fullhash));
 					}
 
-					const writeOut = err => {
+					const fs = /** @type {OutputFileSystem} */ (this.outputFileSystem);
+					// A Windows drive-absolute targetFile is written as-is; joining it onto
+					// outputPath would produce an invalid path (e.g. C:\out\D:\file). A
+					// leading "/" stays relative to outputPath (e.g. entry name "/dir/x").
+					const targetPath = WINDOWS_ABS_PATH_REGEXP.test(targetFile)
+						? targetFile
+						: join(fs, outputPath, targetFile);
+
+					/**
+					 * Processes the provided err.
+					 * @param {Error=} err error
+					 * @returns {void}
+					 */
+					const writeOut = (err) => {
 						if (err) return callback(err);
-						const targetPath = this.outputFileSystem.join(
-							outputPath,
-							targetFile
-						);
-						if (source.existsAt === targetPath) {
-							source.emitted = false;
-							return callback();
+						allTargetPaths.add(targetPath);
+
+						// check if the target file has already been written by this Compiler
+						const targetFileGeneration =
+							this._assetEmittingWrittenFiles.get(targetPath);
+
+						// create an cache entry for this Source if not already existing
+						let cacheEntry = this._assetEmittingSourceCache.get(source);
+						if (cacheEntry === undefined) {
+							cacheEntry = {
+								sizeOnlySource: undefined,
+								/** @type {CacheEntry["writtenTo"]} */
+								writtenTo: new Map()
+							};
+							this._assetEmittingSourceCache.set(source, cacheEntry);
 						}
-						let content = source.source();
 
-						if (!Buffer.isBuffer(content)) {
-							content = Buffer.from(content, "utf8");
+						/** @type {SimilarEntry | undefined} */
+						let similarEntry;
+
+						const checkSimilarFile = () => {
+							const caseInsensitiveTargetPath = targetPath.toLowerCase();
+							similarEntry = caseInsensitiveMap.get(caseInsensitiveTargetPath);
+							if (similarEntry !== undefined) {
+								const { path: other, source: otherSource } = similarEntry;
+								if (isSourceEqual(otherSource, source)) {
+									// Size may or may not be available at this point.
+									// If it's not available add to "waiting" list and it will be updated once available
+									if (similarEntry.size !== undefined) {
+										updateWithReplacementSource(similarEntry.size);
+									} else {
+										if (!similarEntry.waiting) similarEntry.waiting = [];
+										similarEntry.waiting.push({ file, cacheEntry });
+									}
+									alreadyWritten();
+								} else {
+									const err =
+										new WebpackError(`Prevent writing to file that only differs in casing or query string from already written file.
+This will lead to a race-condition and corrupted files on case-insensitive file systems.
+${targetPath}
+${other}`);
+									err.file = file;
+									callback(err);
+								}
+								return true;
+							}
+							caseInsensitiveMap.set(
+								caseInsensitiveTargetPath,
+								(similarEntry = /** @type {SimilarEntry} */ ({
+									path: targetPath,
+									source,
+									size: undefined,
+									waiting: undefined
+								}))
+							);
+							return false;
+						};
+
+						/**
+						 * get the binary (Buffer) content from the Source
+						 * @returns {Buffer} content for the source
+						 */
+						const getContent = () => {
+							if (typeof source.buffer === "function") {
+								return source.buffer();
+							}
+							const bufferOrString = source.source();
+							if (Buffer.isBuffer(bufferOrString)) {
+								return bufferOrString;
+							}
+							return Buffer.from(bufferOrString, "utf8");
+						};
+
+						const alreadyWritten = () => {
+							// cache the information that the Source has been already been written to that location
+							if (targetFileGeneration === undefined) {
+								const newGeneration = 1;
+								this._assetEmittingWrittenFiles.set(targetPath, newGeneration);
+								/** @type {CacheEntry} */
+								(cacheEntry).writtenTo.set(targetPath, newGeneration);
+							} else {
+								/** @type {CacheEntry} */
+								(cacheEntry).writtenTo.set(targetPath, targetFileGeneration);
+							}
+							callback();
+						};
+
+						/**
+						 * Write the file to output file system
+						 * @param {Buffer} content content to be written
+						 * @returns {void}
+						 */
+						const doWrite = (content) => {
+							/** @type {OutputFileSystem} */
+							(this.outputFileSystem).writeFile(targetPath, content, (err) => {
+								if (err) return callback(err);
+
+								// information marker that the asset has been emitted
+								compilation.emittedAssets.add(file);
+
+								// cache the information that the Source has been written to that location
+								const newGeneration =
+									targetFileGeneration === undefined
+										? 1
+										: targetFileGeneration + 1;
+								/** @type {CacheEntry} */
+								(cacheEntry).writtenTo.set(targetPath, newGeneration);
+								this._assetEmittingWrittenFiles.set(targetPath, newGeneration);
+								this.hooks.assetEmitted.callAsync(
+									file,
+									{
+										content,
+										source,
+										outputPath,
+										compilation,
+										targetPath
+									},
+									callback
+								);
+							});
+						};
+
+						/**
+						 * Updates with replacement source.
+						 * @param {number} size size
+						 */
+						const updateWithReplacementSource = (size) => {
+							updateFileWithReplacementSource(
+								file,
+								/** @type {CacheEntry} */ (cacheEntry),
+								size
+							);
+							/** @type {SimilarEntry} */
+							(similarEntry).size = size;
+							if (
+								/** @type {SimilarEntry} */ (similarEntry).waiting !== undefined
+							) {
+								for (const { file, cacheEntry } of /** @type {SimilarEntry} */ (
+									similarEntry
+								).waiting) {
+									updateFileWithReplacementSource(file, cacheEntry, size);
+								}
+							}
+						};
+
+						/**
+						 * Updates file with replacement source.
+						 * @param {string} file file
+						 * @param {CacheEntry} cacheEntry cache entry
+						 * @param {number} size size
+						 */
+						const updateFileWithReplacementSource = (
+							file,
+							cacheEntry,
+							size
+						) => {
+							// Create a replacement resource which only allows to ask for size
+							// This allows to GC all memory allocated by the Source
+							// (expect when the Source is stored in any other cache)
+							if (!cacheEntry.sizeOnlySource) {
+								cacheEntry.sizeOnlySource = new SizeOnlySource(size);
+							}
+							compilation.updateAsset(file, cacheEntry.sizeOnlySource, {
+								size
+							});
+						};
+
+						/**
+						 * Process existing file.
+						 * @param {IStats} stats stats
+						 * @returns {void}
+						 */
+						const processExistingFile = (stats) => {
+							// skip emitting if it's already there and an immutable file
+							if (immutable) {
+								updateWithReplacementSource(/** @type {number} */ (stats.size));
+								return alreadyWritten();
+							}
+
+							const content = getContent();
+
+							updateWithReplacementSource(content.length);
+
+							// if it exists and content on disk matches content
+							// skip writing the same content again
+							// (to keep mtime and don't trigger watchers)
+							// for a fast negative match file size is compared first
+							if (content.length === stats.size) {
+								compilation.comparedForEmitAssets.add(file);
+								return /** @type {OutputFileSystem} */ (
+									this.outputFileSystem
+								).readFile(targetPath, (err, existingContent) => {
+									if (
+										err ||
+										!content.equals(/** @type {Buffer} */ (existingContent))
+									) {
+										return doWrite(content);
+									}
+									return alreadyWritten();
+								});
+							}
+
+							return doWrite(content);
+						};
+
+						const processMissingFile = () => {
+							const content = getContent();
+
+							updateWithReplacementSource(content.length);
+
+							return doWrite(content);
+						};
+
+						// if the target file has already been written
+						if (targetFileGeneration !== undefined) {
+							// check if the Source has been written to this target file
+							const writtenGeneration = /** @type {CacheEntry} */ (
+								cacheEntry
+							).writtenTo.get(targetPath);
+							if (writtenGeneration === targetFileGeneration) {
+								// if yes, we may skip writing the file
+								// if it's already there
+								// (we assume one doesn't modify files while the Compiler is running, other then removing them)
+
+								if (this._assetEmittingPreviousFiles.has(targetPath)) {
+									const sizeOnlySource = /** @type {SizeOnlySource} */ (
+										/** @type {CacheEntry} */ (cacheEntry).sizeOnlySource
+									);
+
+									// We assume that assets from the last compilation say intact on disk (they are not removed)
+									compilation.updateAsset(file, sizeOnlySource, {
+										size: sizeOnlySource.size()
+									});
+
+									return callback();
+								}
+								// Settings immutable will make it accept file content without comparing when file exist
+								immutable = true;
+							} else if (!immutable) {
+								if (checkSimilarFile()) return;
+								// We wrote to this file before which has very likely a different content
+								// skip comparing and assume content is different for performance
+								// This case happens often during watch mode.
+								return processMissingFile();
+							}
 						}
 
-						source.existsAt = targetPath;
-						source.emitted = true;
-						this.outputFileSystem.writeFile(targetPath, content, callback);
+						if (checkSimilarFile()) return;
+						if (this.options.output.compareBeforeEmit) {
+							/** @type {OutputFileSystem} */
+							(this.outputFileSystem).stat(targetPath, (err, stats) => {
+								const exists = !err && /** @type {IStats} */ (stats).isFile();
+
+								if (exists) {
+									processExistingFile(/** @type {IStats} */ (stats));
+								} else {
+									processMissingFile();
+								}
+							});
+						} else {
+							processMissingFile();
+						}
 					};
 
-					if (targetFile.match(/\/|\\/)) {
-						const dir = path.dirname(targetFile);
-						this.outputFileSystem.mkdirp(
-							this.outputFileSystem.join(outputPath, dir),
-							writeOut
-						);
+					if (/\/|\\/.test(targetFile)) {
+						const dir = dirname(fs, targetPath);
+						mkdirp(fs, dir, writeOut);
 					} else {
 						writeOut();
 					}
 				},
-				err => {
-					if (err) return callback(err);
+				(err) => {
+					// Clear map to free up memory
+					caseInsensitiveMap.clear();
+					if (err) {
+						this._assetEmittingPreviousFiles.clear();
+						return callback(err);
+					}
 
-					this.hooks.afterEmit.callAsync(compilation, err => {
+					this._assetEmittingPreviousFiles = allTargetPaths;
+
+					this.hooks.afterEmit.callAsync(compilation, (err) => {
 						if (err) return callback(err);
 
-						return callback();
+						return callback(null);
 					});
 				}
 			);
 		};
 
-		this.hooks.emit.callAsync(compilation, err => {
+		this.hooks.emit.callAsync(compilation, (err) => {
 			if (err) return callback(err);
-			outputPath = compilation.getPath(this.outputPath);
-			this.outputFileSystem.mkdirp(outputPath, emitFiles);
+			outputPath = compilation.getPath(this.outputPath, {});
+			mkdirp(
+				/** @type {OutputFileSystem} */ (this.outputFileSystem),
+				outputPath,
+				emitFiles
+			);
 		});
 	}
 
+	/**
+	 * Processes the provided error callback.
+	 * @param {ErrorCallback} callback signals when the call finishes
+	 * @returns {void}
+	 */
 	emitRecords(callback) {
-		if (!this.recordsOutputPath) return callback();
-		const idx1 = this.recordsOutputPath.lastIndexOf("/");
-		const idx2 = this.recordsOutputPath.lastIndexOf("\\");
-		let recordsOutputPathDirectory = null;
-		if (idx1 > idx2) {
-			recordsOutputPathDirectory = this.recordsOutputPath.substr(0, idx1);
-		} else if (idx1 < idx2) {
-			recordsOutputPathDirectory = this.recordsOutputPath.substr(0, idx2);
+		if (this.hooks.emitRecords.isUsed()) {
+			if (this.recordsOutputPath) {
+				asyncLib.parallel(
+					[
+						(cb) => this.hooks.emitRecords.callAsync(cb),
+						this._emitRecords.bind(this)
+					],
+					(err) => callback(/** @type {Error | null} */ (err))
+				);
+			} else {
+				this.hooks.emitRecords.callAsync(callback);
+			}
+		} else if (this.recordsOutputPath) {
+			this._emitRecords(callback);
+		} else {
+			callback(null);
 		}
+	}
 
+	/**
+	 * Processes the provided error callback.
+	 * @param {ErrorCallback} callback signals when the call finishes
+	 * @returns {void}
+	 */
+	_emitRecords(callback) {
 		const writeFile = () => {
-			this.outputFileSystem.writeFile(
-				this.recordsOutputPath,
-				JSON.stringify(this.records, undefined, 2),
+			/** @type {OutputFileSystem} */
+			(this.outputFileSystem).writeFile(
+				/** @type {string} */ (this.recordsOutputPath),
+				JSON.stringify(
+					this.records,
+					(n, value) => {
+						if (
+							typeof value === "object" &&
+							value !== null &&
+							!Array.isArray(value)
+						) {
+							const keys = Object.keys(value);
+							if (!isSorted(keys)) {
+								return sortObject(value, keys);
+							}
+						}
+						return value;
+					},
+					2
+				),
 				callback
 			);
 		};
 
+		const recordsOutputPathDirectory = dirname(
+			/** @type {OutputFileSystem} */
+			(this.outputFileSystem),
+			/** @type {string} */
+			(this.recordsOutputPath)
+		);
 		if (!recordsOutputPathDirectory) {
 			return writeFile();
 		}
-		this.outputFileSystem.mkdirp(recordsOutputPathDirectory, err => {
-			if (err) return callback(err);
-			writeFile();
-		});
+		mkdirp(
+			/** @type {OutputFileSystem} */ (this.outputFileSystem),
+			recordsOutputPathDirectory,
+			(err) => {
+				if (err) return callback(err);
+				writeFile();
+			}
+		);
 	}
 
+	/**
+	 * Processes the provided error callback.
+	 * @param {ErrorCallback} callback signals when the call finishes
+	 * @returns {void}
+	 */
 	readRecords(callback) {
+		if (this.hooks.readRecords.isUsed()) {
+			if (this.recordsInputPath) {
+				asyncLib.parallel(
+					[
+						(cb) => this.hooks.readRecords.callAsync(cb),
+						this._readRecords.bind(this)
+					],
+					(err) => callback(/** @type {Error | null} */ (err))
+				);
+			} else {
+				this.records = {};
+				this.hooks.readRecords.callAsync(callback);
+			}
+		} else if (this.recordsInputPath) {
+			this._readRecords(callback);
+		} else {
+			this.records = {};
+			callback(null);
+		}
+	}
+
+	/**
+	 * Processes the provided error callback.
+	 * @param {ErrorCallback} callback signals when the call finishes
+	 * @returns {void}
+	 */
+	_readRecords(callback) {
 		if (!this.recordsInputPath) {
 			this.records = {};
-			return callback();
+			return callback(null);
 		}
-		this.inputFileSystem.stat(this.recordsInputPath, err => {
+		/** @type {InputFileSystem} */
+		(this.inputFileSystem).stat(this.recordsInputPath, (err) => {
 			// It doesn't exist
 			// We can ignore this.
-			if (err) return callback();
+			if (err) return callback(null);
 
-			this.inputFileSystem.readFile(this.recordsInputPath, (err, content) => {
-				if (err) return callback(err);
+			/** @type {InputFileSystem} */
+			(this.inputFileSystem).readFile(
+				/** @type {string} */
+				(this.recordsInputPath),
+				(err, content) => {
+					if (err) return callback(err);
 
-				try {
-					this.records = parseJson(content.toString("utf-8"));
-				} catch (e) {
-					e.message = "Cannot parse records: " + e.message;
-					return callback(e);
-				}
+					try {
+						this.records =
+							/** @type {Records} */
+							(parseJson(/** @type {Buffer} */ (content).toString("utf8")));
+					} catch (parseErr) {
+						return callback(
+							new Error(
+								`Cannot parse records: ${
+									/** @type {Error} */ (parseErr).message
+								}`
+							)
+						);
+					}
 
-				return callback();
-			});
+					return callback(null);
+				}
+			);
 		});
 	}
 
+	/**
+	 * Creates a child compiler.
+	 * @param {Compilation} compilation the compilation
+	 * @param {string} compilerName the compiler's name
+	 * @param {number} compilerIndex the compiler's index
+	 * @param {Partial=} outputOptions the output options
+	 * @param {Plugins=} plugins the plugins to apply
+	 * @returns {Compiler} a child compiler
+	 */
 	createChildCompiler(
 		compilation,
 		compilerName,
@@ -375,53 +1287,79 @@ class Compiler extends Tapable {
 		outputOptions,
 		plugins
 	) {
-		const childCompiler = new Compiler(this.context);
-		if (Array.isArray(plugins)) {
-			for (const plugin of plugins) {
-				plugin.apply(childCompiler);
+		const childCompiler = new Compiler(this.context, {
+			...this.options,
+			output: {
+				...this.options.output,
+				...outputOptions
 			}
-		}
-		for (const name in this.hooks) {
-			if (
-				![
-					"make",
-					"compile",
-					"emit",
-					"afterEmit",
-					"invalid",
-					"done",
-					"thisCompilation"
-				].includes(name)
-			) {
-				if (childCompiler.hooks[name]) {
-					childCompiler.hooks[name].taps = this.hooks[name].taps.slice();
-				}
-			}
-		}
+		});
 		childCompiler.name = compilerName;
 		childCompiler.outputPath = this.outputPath;
 		childCompiler.inputFileSystem = this.inputFileSystem;
 		childCompiler.outputFileSystem = null;
 		childCompiler.resolverFactory = this.resolverFactory;
+		childCompiler.modifiedFiles = this.modifiedFiles;
+		childCompiler.removedFiles = this.removedFiles;
 		childCompiler.fileTimestamps = this.fileTimestamps;
 		childCompiler.contextTimestamps = this.contextTimestamps;
+		childCompiler.fsStartTime = this.fsStartTime;
+		childCompiler.cache = this.cache;
+		childCompiler.compilerPath = `${this.compilerPath}${compilerName}|${compilerIndex}|`;
+		childCompiler._backCompat = this._backCompat;
 
-		const relativeCompilerName = makePathsRelative(this.context, compilerName);
+		const relativeCompilerName = makePathsRelative(
+			this.context,
+			compilerName,
+			this.root
+		);
 		if (!this.records[relativeCompilerName]) {
 			this.records[relativeCompilerName] = [];
 		}
 		if (this.records[relativeCompilerName][compilerIndex]) {
-			childCompiler.records = this.records[relativeCompilerName][compilerIndex];
+			childCompiler.records =
+				/** @type {Records} */
+				(this.records[relativeCompilerName][compilerIndex]);
 		} else {
 			this.records[relativeCompilerName].push((childCompiler.records = {}));
 		}
 
-		childCompiler.options = Object.create(this.options);
-		childCompiler.options.output = Object.create(childCompiler.options.output);
-		for (const name in outputOptions) {
-			childCompiler.options.output[name] = outputOptions[name];
-		}
 		childCompiler.parentCompilation = compilation;
+		childCompiler.root = this.root;
+		if (Array.isArray(plugins)) {
+			for (const plugin of plugins) {
+				if (typeof plugin === "function") {
+					/** @type {WebpackPluginFunction} */
+					(plugin).call(childCompiler, childCompiler);
+				} else if (plugin) {
+					plugin.apply(childCompiler);
+				}
+			}
+		}
+		for (const name in this.hooks) {
+			if (
+				![
+					"make",
+					"compile",
+					"emit",
+					"afterEmit",
+					"invalid",
+					"done",
+					"thisCompilation"
+				].includes(name) &&
+				childCompiler.hooks[/** @type {keyof Compiler["hooks"]} */ (name)]
+			) {
+				childCompiler.hooks[
+					/** @type {keyof Compiler["hooks"]} */
+					(name)
+				].taps = [
+					...this.hooks[
+						/** @type {keyof Compiler["hooks"]} */
+						(name)
+					].taps
+				];
+			}
+		}
 
 		compilation.hooks.childCompiler.call(
 			childCompiler,
@@ -433,31 +1371,43 @@ class Compiler extends Tapable {
 	}
 
 	isChild() {
-		return !!this.parentCompilation;
+		return Boolean(this.parentCompilation);
 	}
 
-	createCompilation() {
-		return new Compilation(this);
+	/**
+	 * Creates a compilation.
+	 * @param {CompilationParams} params the compilation parameters
+	 * @returns {Compilation} compilation
+	 */
+	createCompilation(params) {
+		this._cleanupLastCompilation();
+		return (this._lastCompilation = new Compilation(this, params));
 	}
 
+	/**
+	 * Returns the created compilation.
+	 * @param {CompilationParams} params the compilation parameters
+	 * @returns {Compilation} the created compilation
+	 */
 	newCompilation(params) {
-		const compilation = this.createCompilation();
-		compilation.fileTimestamps = this.fileTimestamps;
-		compilation.contextTimestamps = this.contextTimestamps;
+		const compilation = this.createCompilation(params);
 		compilation.name = this.name;
 		compilation.records = this.records;
-		compilation.compilationDependencies = params.compilationDependencies;
 		this.hooks.thisCompilation.call(compilation, params);
 		this.hooks.compilation.call(compilation, params);
 		return compilation;
 	}
 
 	createNormalModuleFactory() {
-		const normalModuleFactory = new NormalModuleFactory(
-			this.options.context,
-			this.resolverFactory,
-			this.options.module || {}
-		);
+		this._cleanupLastNormalModuleFactory();
+		const normalModuleFactory = new NormalModuleFactory({
+			context: this.options.context,
+			fs: /** @type {InputFileSystem} */ (this.inputFileSystem),
+			resolverFactory: this.resolverFactory,
+			options: this.options.module,
+			associatedObjectForCache: this.root
+		});
+		this._lastNormalModuleFactory = normalModuleFactory;
 		this.hooks.normalModuleFactory.call(normalModuleFactory);
 		return normalModuleFactory;
 	}
@@ -471,38 +1421,134 @@ class Compiler extends Tapable {
 	newCompilationParams() {
 		const params = {
 			normalModuleFactory: this.createNormalModuleFactory(),
-			contextModuleFactory: this.createContextModuleFactory(),
-			compilationDependencies: new Set()
+			contextModuleFactory: this.createContextModuleFactory()
 		};
 		return params;
 	}
 
+	/**
+	 * Processes the provided compilation.
+	 * @param {Callback} callback signals when the compilation finishes
+	 * @returns {void}
+	 */
 	compile(callback) {
 		const params = this.newCompilationParams();
-		this.hooks.beforeCompile.callAsync(params, err => {
+		this.hooks.beforeCompile.callAsync(params, (err) => {
 			if (err) return callback(err);
 
 			this.hooks.compile.call(params);
 
 			const compilation = this.newCompilation(params);
 
-			this.hooks.make.callAsync(compilation, err => {
-				if (err) return callback(err);
+			const logger = compilation.getLogger("webpack.Compiler");
 
-				compilation.finish();
+			logger.time("make hook");
+			this.hooks.make.callAsync(compilation, (err) => {
+				logger.timeEnd("make hook");
+				if (err) return callback(err);
 
-				compilation.seal(err => {
+				logger.time("finish make hook");
+				this.hooks.finishMake.callAsync(compilation, (err) => {
+					logger.timeEnd("finish make hook");
 					if (err) return callback(err);
 
-					this.hooks.afterCompile.callAsync(compilation, err => {
-						if (err) return callback(err);
-
-						return callback(null, compilation);
+					process.nextTick(() => {
+						logger.time("finish compilation");
+						compilation.finish((err) => {
+							logger.timeEnd("finish compilation");
+							if (err) return callback(err);
+
+							logger.time("seal compilation");
+							compilation.seal((err) => {
+								logger.timeEnd("seal compilation");
+								if (err) return callback(err);
+
+								logger.time("afterCompile hook");
+								this.hooks.afterCompile.callAsync(compilation, (err) => {
+									logger.timeEnd("afterCompile hook");
+									if (err) return callback(err);
+
+									return callback(null, compilation);
+								});
+							});
+						});
 					});
 				});
 			});
 		});
 	}
+
+	/**
+	 * Processes the provided error callback.
+	 * @param {ErrorCallback} callback signals when the compiler closes
+	 * @returns {void}
+	 */
+	close(callback) {
+		if (this.watching) {
+			// When there is still an active watching, close this first
+			this.watching.close((_err) => {
+				this.close(callback);
+			});
+			return;
+		}
+		this.hooks.shutdown.callAsync((err) => {
+			if (err) return callback(err);
+			// Defer a microtask so a close() made inside the run callback can't
+			// release codeGenerationResults before afterDone fires on the same stack.
+			const lastCompilation = this._lastCompilation;
+			if (lastCompilation !== undefined) {
+				Promise.resolve().then(() => {
+					this._releaseUnusedCompilationData(lastCompilation);
+				});
+			}
+			this._lastCompilation = undefined;
+			this._lastNormalModuleFactory = undefined;
+			this.cache.shutdown(callback);
+		});
+	}
+
+	/**
+	 * Schema validation function with optional pre-compiled check
+	 * @template {EXPECTED_OBJECT | EXPECTED_OBJECT[]} [T=EXPECTED_OBJECT]
+	 * @param {Schema | (() => Schema)} schema schema
+	 * @param {T} value value
+	 * @param {ValidationErrorConfiguration=} options options
+	 * @param {((value: T) => boolean)=} check options
+	 */
+	validate(schema, value, options, check) {
+		// Avoid validation at all when disabled
+		if (this.options.validate === false) {
+			return;
+		}
+
+		/**
+		 * Returns schema.
+		 * @returns {Schema} schema
+		 */
+		const getSchema = () => {
+			if (typeof schema === "function") {
+				return schema();
+			}
+
+			return schema;
+		};
+
+		// // If we have precompiled schema let's use it
+		if (check) {
+			if (!check(value)) {
+				getValidate()(getSchema(), value, options);
+				require("util").deprecate(
+					() => {},
+					"webpack bug: Pre-compiled schema reports error while real schema is happy. This has performance drawbacks.",
+					"DEP_WEBPACK_PRE_COMPILED_SCHEMA_INVALID"
+				)();
+			}
+			return;
+		}
+
+		// Otherwise let's standard validation
+		getValidate()(getSchema(), value, options);
+	}
 }
 
 module.exports = Compiler;
diff --git a/lib/ConcatenationScope.js b/lib/ConcatenationScope.js
new file mode 100644
index 00000000000..c10a66c6fe4
--- /dev/null
+++ b/lib/ConcatenationScope.js
@@ -0,0 +1,317 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const {
+	DEFAULT_EXPORT,
+	NAMESPACE_OBJECT_EXPORT
+} = require("./util/concatenate");
+
+/** @typedef {import("webpack-sources").Source} Source */
+/** @typedef {import("./Chunk")} Chunk */
+/** @typedef {import("./Module")} Module */
+/** @typedef {import("./optimize/ConcatenatedModule").ConcatenatedModuleInfo} ConcatenatedModuleInfo */
+/** @typedef {import("./optimize/ConcatenatedModule").ModuleInfo} ModuleInfo */
+/** @typedef {import("./optimize/ConcatenatedModule").ExportName} Ids */
+/** @typedef {import("./javascript/JavascriptParser").Range} Range */
+
+/** @typedef {{ start: number, end: number, name: string }} ModuleReferenceMatch */
+
+const MODULE_REFERENCE_REGEXP =
+	/^__WEBPACK_MODULE_REFERENCE__(\d+)_([\da-f]+|ns)(_call)?(_directImport)?(_deferredImport)?(_mangleableNs)?(_moduleExportsAccess)?(?:_asiSafe(\d))?__$/;
+
+// unanchored variant: wrapped CJS bodies are not re-parsed, so their
+// references must be found textually
+const MODULE_REFERENCE_SCAN_REGEXP = new RegExp(
+	MODULE_REFERENCE_REGEXP.source.slice(1, -1),
+	"g"
+);
+
+/** @type {WeakMap} */
+const moduleReferencesCache = new WeakMap();
+
+/**
+ * Encodes how a concatenated module reference should be interpreted when it is
+ * later reconstructed from its placeholder identifier.
+ * @typedef {object} ModuleReferenceOptions
+ * @property {Ids} ids the properties or exports selected from the referenced module
+ * @property {boolean} call true, when this referenced export is called
+ * @property {boolean} directImport true, when this referenced export is directly imported (not via property access)
+ * @property {boolean} deferredImport true, when this referenced export is deferred
+ * @property {boolean} mangleableNamespace true, when a whole-namespace reference may use a decoupled namespace object that keeps the original export names
+ * @property {boolean} moduleExportsAccess true, when the reference resolves to the module's own exports value with plain property access, skipping ESM interop (how `require()` and a wrapped module's side-effect init see it)
+ * @property {boolean | undefined} asiSafe if the position is ASI safe or unknown
+ */
+
+/**
+ * Tracks the symbols and cross-module references needed while rendering a
+ * concatenated module.
+ */
+class ConcatenationScope {
+	/**
+	 * Creates the mutable scope object used while rendering a concatenated
+	 * module and its cross-module references.
+	 * @param {ModuleInfo[] | Map} modulesMap all module info by module
+	 * @param {ConcatenatedModuleInfo} currentModule the current module info
+	 * @param {Set} usedNames all used names
+	 */
+	constructor(modulesMap, currentModule, usedNames) {
+		/** @type {ConcatenatedModuleInfo} */
+		this._currentModule = currentModule;
+		if (Array.isArray(modulesMap)) {
+			/** @type {Map} */
+			const map = new Map();
+			for (const info of modulesMap) {
+				map.set(info.module, /** @type {ConcatenatedModuleInfo} */ (info));
+			}
+			modulesMap = map;
+		}
+		/** @type {Set} */
+		this.usedNames = usedNames;
+		/** @type {Map} */
+		this._modulesMap = modulesMap;
+		// written and read by dependency templates within a single code
+		// generation pass, never across passes
+		/** @type {Range[] | undefined} */
+		this._replacedRequireRanges = undefined;
+	}
+
+	/**
+	 * Checks whether a module participates in the current concatenation scope.
+	 * @param {Module} module the referenced module
+	 * @returns {boolean} true, when it's in the scope
+	 */
+	isModuleInScope(module) {
+		return this._modulesMap.has(module);
+	}
+
+	/**
+	 * Whether an in-scope module's body renders inside the lazy wrapper.
+	 * @param {Module} module the referenced module
+	 * @returns {boolean} true, when it's wrapped
+	 */
+	isModuleWrapped(module) {
+		const info = this._modulesMap.get(module);
+		return info !== undefined && info.wrapped;
+	}
+
+	/**
+	 * Marks a wrapped module as eagerly imported, so its accessor is called at its
+	 * own slot in evaluation order instead of from inside the importing body.
+	 * @param {Module} module the referenced module
+	 */
+	registerEagerModule(module) {
+		const info = this._modulesMap.get(module);
+		if (info !== undefined) info.eager = true;
+	}
+
+	/**
+	 * Whether the current module runs inside the lazy wrapper. Exports then live
+	 * on a real exports object, not hoisted bindings, so templates emit runtime
+	 * code.
+	 * @returns {boolean} true, when the current module is wrapped
+	 */
+	isWrapped() {
+		return this._currentModule.wrapped;
+	}
+
+	/**
+	 * Records a `require(...)` call that was replaced as a whole by a
+	 * concatenation reference, so nothing else rewrites a range inside it.
+	 * @param {Range} range source range of the replaced call
+	 */
+	registerReplacedRequire(range) {
+		if (this._replacedRequireRanges === undefined) {
+			this._replacedRequireRanges = [];
+		}
+		this._replacedRequireRanges.push(range);
+	}
+
+	/**
+	 * Checks whether an offset sits inside an already-replaced `require(...)` call.
+	 * Containment, not exact match: `new require(...)` is replaced from `new`.
+	 * @param {number} start start offset to test
+	 * @returns {boolean} true, when the offset was replaced already
+	 */
+	isInsideReplacedRequire(start) {
+		const ranges = this._replacedRequireRanges;
+		if (ranges === undefined) return false;
+		for (const [rangeStart, rangeEnd] of ranges) {
+			if (start >= rangeStart && start < rangeEnd) return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Records the symbol that should be used when the current module exports a
+	 * named binding.
+	 * @param {string} exportName name of the export
+	 * @param {string} symbol identifier of the export in source code
+	 */
+	registerExport(exportName, symbol) {
+		if (!this._currentModule.exportMap) {
+			this._currentModule.exportMap = new Map();
+		}
+		if (!this._currentModule.exportMap.has(exportName)) {
+			this._currentModule.exportMap.set(exportName, symbol);
+		}
+	}
+
+	/**
+	 * Records a raw expression that can be used to reference an export without
+	 * going through the normal symbol map.
+	 * @param {string} exportName name of the export
+	 * @param {string} expression expression to be used
+	 */
+	registerRawExport(exportName, expression) {
+		if (!this._currentModule.rawExportMap) {
+			this._currentModule.rawExportMap = new Map();
+		}
+		if (!this._currentModule.rawExportMap.has(exportName)) {
+			this._currentModule.rawExportMap.set(exportName, expression);
+		}
+	}
+
+	/**
+	 * Returns the raw expression registered for an export, if one exists.
+	 * @param {string} exportName name of the export
+	 * @returns {string | undefined} the expression of the export
+	 */
+	getRawExport(exportName) {
+		if (!this._currentModule.rawExportMap) {
+			return undefined;
+		}
+		return this._currentModule.rawExportMap.get(exportName);
+	}
+
+	/**
+	 * Replaces the raw expression for an export only when that export already
+	 * has an entry in the raw export map.
+	 * @param {string} exportName name of the export
+	 * @param {string} expression expression to be used
+	 */
+	setRawExportMap(exportName, expression) {
+		if (!this._currentModule.rawExportMap) {
+			this._currentModule.rawExportMap = new Map();
+		}
+		if (this._currentModule.rawExportMap.has(exportName)) {
+			this._currentModule.rawExportMap.set(exportName, expression);
+		}
+	}
+
+	/**
+	 * Records the symbol that should be used for the synthetic namespace export.
+	 * @param {string} symbol identifier of the export in source code
+	 */
+	registerNamespaceExport(symbol) {
+		this._currentModule.namespaceExportSymbol = symbol;
+	}
+
+	/**
+	 * Encodes a reference to another concatenated module as a placeholder
+	 * identifier that can be parsed later during code generation.
+	 * @param {Module} module the referenced module
+	 * @param {Partial} options options
+	 * @returns {string} the reference as identifier
+	 */
+	createModuleReference(
+		module,
+		{
+			ids = undefined,
+			call = false,
+			directImport = false,
+			deferredImport = false,
+			mangleableNamespace = false,
+			moduleExportsAccess = false,
+			asiSafe = false
+		}
+	) {
+		const info = /** @type {ModuleInfo} */ (this._modulesMap.get(module));
+		const callFlag = call ? "_call" : "";
+		const directImportFlag = directImport ? "_directImport" : "";
+		const deferredImportFlag = deferredImport ? "_deferredImport" : "";
+		const mangleableNamespaceFlag = mangleableNamespace ? "_mangleableNs" : "";
+		const moduleExportsAccessFlag = moduleExportsAccess
+			? "_moduleExportsAccess"
+			: "";
+		const asiSafeFlag = asiSafe
+			? "_asiSafe1"
+			: asiSafe === false
+				? "_asiSafe0"
+				: "";
+		const exportData = ids
+			? Buffer.from(JSON.stringify(ids), "utf8").toString("hex")
+			: "ns";
+		// a "._" is appended to allow "delete ...", which would cause a SyntaxError in strict mode
+		return `__WEBPACK_MODULE_REFERENCE__${info.index}_${exportData}${callFlag}${directImportFlag}${deferredImportFlag}${mangleableNamespaceFlag}${moduleExportsAccessFlag}${asiSafeFlag}__._`;
+	}
+
+	/**
+	 * Checks whether an identifier is one of webpack's encoded concatenation
+	 * module references.
+	 * @param {string} name the identifier
+	 * @returns {boolean} true, when it's an module reference
+	 */
+	static isModuleReference(name) {
+		return MODULE_REFERENCE_REGEXP.test(name);
+	}
+
+	/**
+	 * Finds all encoded module references in a generated source.
+	 * @param {Source} source generated source
+	 * @returns {ModuleReferenceMatch[]} reference token positions (end is exclusive, excludes the appended "._")
+	 */
+	static findModuleReferences(source) {
+		const cached = moduleReferencesCache.get(source);
+		if (cached !== undefined) return cached;
+		const code = source.source().toString();
+		/** @type {ModuleReferenceMatch[]} */
+		const result = [];
+		MODULE_REFERENCE_SCAN_REGEXP.lastIndex = 0;
+		let match = MODULE_REFERENCE_SCAN_REGEXP.exec(code);
+		while (match !== null) {
+			result.push({
+				start: match.index,
+				end: match.index + match[0].length,
+				name: match[0]
+			});
+			match = MODULE_REFERENCE_SCAN_REGEXP.exec(code);
+		}
+		moduleReferencesCache.set(source, result);
+		return result;
+	}
+
+	/**
+	 * Parses an encoded module reference back into its module index and
+	 * reference flags.
+	 * @param {string} name the identifier
+	 * @returns {ModuleReferenceOptions & { index: number } | null} parsed options and index
+	 */
+	static matchModuleReference(name) {
+		const match = MODULE_REFERENCE_REGEXP.exec(name);
+		if (!match) return null;
+		const index = Number(match[1]);
+		const asiSafe = match[8];
+		return {
+			index,
+			ids:
+				match[2] === "ns"
+					? []
+					: JSON.parse(Buffer.from(match[2], "hex").toString("utf8")),
+			call: Boolean(match[3]),
+			directImport: Boolean(match[4]),
+			deferredImport: Boolean(match[5]),
+			mangleableNamespace: Boolean(match[6]),
+			moduleExportsAccess: Boolean(match[7]),
+			asiSafe: asiSafe ? asiSafe === "1" : undefined
+		};
+	}
+}
+
+ConcatenationScope.DEFAULT_EXPORT = DEFAULT_EXPORT;
+ConcatenationScope.NAMESPACE_OBJECT_EXPORT = NAMESPACE_OBJECT_EXPORT;
+
+module.exports = ConcatenationScope;
diff --git a/lib/ConcurrentCompilationError.js b/lib/ConcurrentCompilationError.js
deleted file mode 100644
index 3b590e72c2c..00000000000
--- a/lib/ConcurrentCompilationError.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Maksim Nazarjev @acupofspirt
-*/
-"use strict";
-
-const WebpackError = require("./WebpackError");
-
-module.exports = class ConcurrentCompilationError extends WebpackError {
-	constructor() {
-		super();
-
-		this.name = "ConcurrentCompilationError";
-		this.message =
-			"You ran Webpack twice. Each instance only supports a single concurrent compilation at a time.";
-
-		Error.captureStackTrace(this, this.constructor);
-	}
-};
diff --git a/lib/ConditionalInitFragment.js b/lib/ConditionalInitFragment.js
new file mode 100644
index 00000000000..f046bec4c9c
--- /dev/null
+++ b/lib/ConditionalInitFragment.js
@@ -0,0 +1,126 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const { ConcatSource, PrefixSource } = require("webpack-sources");
+const InitFragment = require("./InitFragment");
+const Template = require("./Template");
+const { mergeRuntime } = require("./util/runtime");
+
+/** @typedef {import("webpack-sources").Source} Source */
+/** @typedef {import("./Generator").GenerateContext} GenerateContext */
+/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */
+
+/**
+ * Returns wrapped source.
+ * @param {string} condition condition
+ * @param {string | Source} source source
+ * @returns {string | Source} wrapped source
+ */
+const wrapInCondition = (condition, source) => {
+	if (typeof source === "string") {
+		return Template.asString([
+			`if (${condition}) {`,
+			Template.indent(source),
+			"}",
+			""
+		]);
+	}
+	return new ConcatSource(
+		`if (${condition}) {\n`,
+		new PrefixSource("\t", source),
+		"}\n"
+	);
+};
+
+/**
+ * Represents ConditionalInitFragment.
+ * @extends {InitFragment}
+ */
+class ConditionalInitFragment extends InitFragment {
+	/**
+	 * Creates an instance of ConditionalInitFragment.
+	 * @param {string | Source | undefined} content the source code that will be included as initialization code
+	 * @param {number} stage category of initialization code (contribute to order)
+	 * @param {number} position position in the category (contribute to order)
+	 * @param {string | undefined} key unique key to avoid emitting the same initialization code twice
+	 * @param {RuntimeSpec | boolean} runtimeCondition in which runtime this fragment should be executed
+	 * @param {string | Source=} endContent the source code that will be included at the end of the module
+	 */
+	constructor(
+		content,
+		stage,
+		position,
+		key,
+		runtimeCondition = true,
+		endContent = undefined
+	) {
+		super(content, stage, position, key, endContent);
+		this.runtimeCondition = runtimeCondition;
+	}
+
+	/**
+	 * Returns the source code that will be included as initialization code.
+	 * @param {GenerateContext} context context
+	 * @returns {string | Source | undefined} the source code that will be included as initialization code
+	 */
+	getContent(context) {
+		if (this.runtimeCondition === false || !this.content) return "";
+		if (this.runtimeCondition === true) return this.content;
+		const expr = context.runtimeTemplate.runtimeConditionExpression({
+			chunkGraph: context.chunkGraph,
+			runtimeRequirements: context.runtimeRequirements,
+			runtime: context.runtime,
+			runtimeCondition: this.runtimeCondition
+		});
+		if (expr === "true") return this.content;
+		return wrapInCondition(expr, this.content);
+	}
+
+	/**
+	 * Returns the source code that will be included at the end of the module.
+	 * @param {GenerateContext} context context
+	 * @returns {string | Source | undefined} the source code that will be included at the end of the module
+	 */
+	getEndContent(context) {
+		if (this.runtimeCondition === false || !this.endContent) return "";
+		if (this.runtimeCondition === true) return this.endContent;
+		const expr = context.runtimeTemplate.runtimeConditionExpression({
+			chunkGraph: context.chunkGraph,
+			runtimeRequirements: context.runtimeRequirements,
+			runtime: context.runtime,
+			runtimeCondition: this.runtimeCondition
+		});
+		if (expr === "true") return this.endContent;
+		return wrapInCondition(expr, this.endContent);
+	}
+
+	/**
+	 * Returns merged fragment.
+	 * @param {ConditionalInitFragment} other fragment to merge with
+	 * @returns {ConditionalInitFragment} merged fragment
+	 */
+	merge(other) {
+		if (this.runtimeCondition === true) return this;
+		if (other.runtimeCondition === true) return other;
+		if (this.runtimeCondition === false) return other;
+		if (other.runtimeCondition === false) return this;
+		const runtimeCondition = mergeRuntime(
+			this.runtimeCondition,
+			other.runtimeCondition
+		);
+		return new ConditionalInitFragment(
+			this.content,
+			this.stage,
+			this.position,
+			this.key,
+			runtimeCondition,
+			this.endContent
+		);
+	}
+}
+
+module.exports = ConditionalInitFragment;
diff --git a/lib/ConstPlugin.js b/lib/ConstPlugin.js
index 03b279b6490..050e5d34faf 100644
--- a/lib/ConstPlugin.js
+++ b/lib/ConstPlugin.js
@@ -2,20 +2,42 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
+
+const {
+	JAVASCRIPT_MODULE_TYPE_AUTO,
+	JAVASCRIPT_MODULE_TYPE_DYNAMIC,
+	JAVASCRIPT_MODULE_TYPE_ESM
+} = require("./ModuleTypeConstants");
+const CachedConstDependency = require("./dependencies/CachedConstDependency");
 const ConstDependency = require("./dependencies/ConstDependency");
-const NullFactory = require("./NullFactory");
-const ParserHelpers = require("./ParserHelpers");
+const { evaluateToString } = require("./javascript/JavascriptParserHelpers");
+const { parseResource } = require("./util/identifier");
 
-const getQuery = request => {
-	const i = request.indexOf("?");
-	return i !== -1 ? request.substr(i) : "";
-};
+/** @typedef {import("estree").AssignmentProperty} AssignmentProperty */
+/** @typedef {import("estree").Expression} Expression */
+/** @typedef {import("estree").Identifier} Identifier */
+/** @typedef {import("estree").Pattern} Pattern */
+/** @typedef {import("estree").SourceLocation} SourceLocation */
+/** @typedef {import("estree").Statement} Statement */
+/** @typedef {import("estree").Super} Super */
+/** @typedef {import("estree").VariableDeclaration} VariableDeclaration */
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */
+/** @typedef {import("./javascript/JavascriptParser").Range} Range */
 
+/** @typedef {Set} Declarations */
+
+/**
+ * Collect declaration.
+ * @param {Declarations} declarations set of declarations
+ * @param {Identifier | Pattern} pattern pattern to collect declarations from
+ */
 const collectDeclaration = (declarations, pattern) => {
 	const stack = [pattern];
 	while (stack.length > 0) {
-		const node = stack.pop();
+		const node = /** @type {Pattern} */ (stack.pop());
 		switch (node.type) {
 			case "Identifier":
 				declarations.add(node.name);
@@ -32,7 +54,7 @@ const collectDeclaration = (declarations, pattern) => {
 				break;
 			case "ObjectPattern":
 				for (const property of node.properties) {
-					stack.push(property.value);
+					stack.push(/** @type {AssignmentProperty} */ (property).value);
 				}
 				break;
 			case "RestElement":
@@ -42,8 +64,16 @@ const collectDeclaration = (declarations, pattern) => {
 	}
 };
 
+/**
+ * Gets hoisted declarations.
+ * @param {Statement} branch branch to get hoisted declarations from
+ * @param {boolean} includeFunctionDeclarations whether to include function declarations
+ * @returns {string[]} hoisted declarations
+ */
 const getHoistedDeclarations = (branch, includeFunctionDeclarations) => {
+	/** @type {Declarations} */
 	const declarations = new Set();
+	/** @type {(Statement | null | undefined)[]} */
 	const stack = [branch];
 	while (stack.length > 0) {
 		const node = stack.pop();
@@ -62,12 +92,12 @@ const getHoistedDeclarations = (branch, includeFunctionDeclarations) => {
 				stack.push(node.alternate);
 				break;
 			case "ForStatement":
-				stack.push(node.init);
+				stack.push(/** @type {VariableDeclaration} */ (node.init));
 				stack.push(node.body);
 				break;
 			case "ForInStatement":
 			case "ForOfStatement":
-				stack.push(node.left);
+				stack.push(/** @type {VariableDeclaration} */ (node.left));
 				stack.push(node.body);
 				break;
 			case "DoWhileStatement":
@@ -91,7 +121,7 @@ const getHoistedDeclarations = (branch, includeFunctionDeclarations) => {
 				break;
 			case "FunctionDeclaration":
 				if (includeFunctionDeclarations) {
-					collectDeclaration(declarations, node.id);
+					collectDeclaration(declarations, /** @type {Identifier} */ (node.id));
 				}
 				break;
 			case "VariableDeclaration":
@@ -103,99 +133,89 @@ const getHoistedDeclarations = (branch, includeFunctionDeclarations) => {
 				break;
 		}
 	}
-	return Array.from(declarations);
+	return [...declarations];
 };
 
+const PLUGIN_NAME = "ConstPlugin";
+
 class ConstPlugin {
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
 	apply(compiler) {
+		const cachedParseResource = parseResource.bindCache(compiler.root);
 		compiler.hooks.compilation.tap(
-			"ConstPlugin",
+			PLUGIN_NAME,
 			(compilation, { normalModuleFactory }) => {
-				compilation.dependencyFactories.set(ConstDependency, new NullFactory());
 				compilation.dependencyTemplates.set(
 					ConstDependency,
 					new ConstDependency.Template()
 				);
 
-				const handler = parser => {
-					parser.hooks.statementIf.tap("ConstPlugin", statement => {
+				compilation.dependencyTemplates.set(
+					CachedConstDependency,
+					new CachedConstDependency.Template()
+				);
+
+				/**
+				 * Handles the hook callback for this code path.
+				 * @param {JavascriptParser} parser the parser
+				 */
+				const handler = (parser) => {
+					parser.hooks.terminate.tap(PLUGIN_NAME, (_statement) => true);
+					parser.hooks.statementIf.tap(PLUGIN_NAME, (statement) => {
+						if (parser.scope.isAsmJs) return;
 						const param = parser.evaluateExpression(statement.test);
 						const bool = param.asBool();
 						if (typeof bool === "boolean") {
-							if (statement.test.type !== "Literal") {
-								const dep = new ConstDependency(`${bool}`, param.range);
-								dep.loc = statement.loc;
-								parser.state.current.addDependency(dep);
+							if (!param.couldHaveSideEffects()) {
+								const dep = new ConstDependency(
+									`${bool}`,
+									/** @type {Range} */ (param.range)
+								);
+								dep.loc = parser.getLocation(statement);
+								parser.state.module.addPresentationalDependency(dep);
+							} else {
+								parser.walkExpression(statement.test);
 							}
 							const branchToRemove = bool
 								? statement.alternate
 								: statement.consequent;
 							if (branchToRemove) {
-								// Before removing the dead branch, the hoisted declarations
-								// must be collected.
-								//
-								// Given the following code:
-								//
-								//     if (true) f() else g()
-								//     if (false) {
-								//       function f() {}
-								//       const g = function g() {}
-								//       if (someTest) {
-								//         let a = 1
-								//         var x, {y, z} = obj
-								//       }
-								//     } else {
-								//       …
-								//     }
-								//
-								// the generated code is:
-								//
-								//     if (true) f() else {}
-								//     if (false) {
-								//       var f, x, y, z;   (in loose mode)
-								//       var x, y, z;      (in strict mode)
-								//     } else {
-								//       …
-								//     }
-								//
-								// NOTE: When code runs in strict mode, `var` declarations
-								// are hoisted but `function` declarations don't.
-								//
-								let declarations;
-								if (parser.scope.isStrict) {
-									// If the code runs in strict mode, variable declarations
-									// using `var` must be hoisted.
-									declarations = getHoistedDeclarations(branchToRemove, false);
-								} else {
-									// Otherwise, collect all hoisted declaration.
-									declarations = getHoistedDeclarations(branchToRemove, true);
-								}
-								let replacement;
-								if (declarations.length > 0) {
-									replacement = `{ var ${declarations.join(", ")}; }`;
-								} else {
-									replacement = "{}";
-								}
-								const dep = new ConstDependency(
-									replacement,
-									branchToRemove.range
-								);
-								dep.loc = branchToRemove.loc;
-								parser.state.current.addDependency(dep);
+								this.eliminateUnusedStatement(parser, branchToRemove, true);
 							}
 							return bool;
 						}
 					});
+					parser.hooks.unusedStatement.tap(PLUGIN_NAME, (statement) => {
+						if (
+							parser.scope.isAsmJs ||
+							// Check top level scope here again
+							parser.scope.topLevelScope === true
+						) {
+							return;
+						}
+						this.eliminateUnusedStatement(parser, statement, false);
+						return true;
+					});
 					parser.hooks.expressionConditionalOperator.tap(
-						"ConstPlugin",
-						expression => {
+						PLUGIN_NAME,
+						(expression) => {
+							if (parser.scope.isAsmJs) return;
 							const param = parser.evaluateExpression(expression.test);
 							const bool = param.asBool();
 							if (typeof bool === "boolean") {
-								if (expression.test.type !== "Literal") {
-									const dep = new ConstDependency(` ${bool}`, param.range);
-									dep.loc = expression.loc;
-									parser.state.current.addDependency(dep);
+								if (!param.couldHaveSideEffects()) {
+									const dep = new ConstDependency(
+										` ${bool}`,
+										/** @type {Range} */ (param.range)
+									);
+									dep.loc = parser.getLocation(expression);
+									parser.state.module.addPresentationalDependency(dep);
+								} else {
+									parser.walkExpression(expression.test);
 								}
 								// Expressions do not hoist.
 								// It is safe to remove the dead branch.
@@ -206,53 +226,345 @@ class ConstPlugin {
 								//
 								// the generated code is:
 								//
-								//   false ? undefined : otherExpression();
+								//   false ? 0 : otherExpression();
 								//
 								const branchToRemove = bool
 									? expression.alternate
 									: expression.consequent;
 								const dep = new ConstDependency(
-									"undefined",
-									branchToRemove.range
+									"0",
+									/** @type {Range} */ (branchToRemove.range)
 								);
-								dep.loc = branchToRemove.loc;
-								parser.state.current.addDependency(dep);
+								dep.loc = parser.getLocation(branchToRemove);
+								parser.state.module.addPresentationalDependency(dep);
 								return bool;
 							}
 						}
 					);
+					parser.hooks.expressionLogicalOperator.tap(
+						PLUGIN_NAME,
+						(expression) => {
+							if (parser.scope.isAsmJs) return;
+							if (
+								expression.operator === "&&" ||
+								expression.operator === "||"
+							) {
+								const param = parser.evaluateExpression(expression.left);
+								const bool = param.asBool();
+								if (typeof bool === "boolean") {
+									// Expressions do not hoist.
+									// It is safe to remove the dead branch.
+									//
+									// ------------------------------------------
+									//
+									// Given the following code:
+									//
+									//   falsyExpression() && someExpression();
+									//
+									// the generated code is:
+									//
+									//   falsyExpression() && false;
+									//
+									// ------------------------------------------
+									//
+									// Given the following code:
+									//
+									//   truthyExpression() && someExpression();
+									//
+									// the generated code is:
+									//
+									//   true && someExpression();
+									//
+									// ------------------------------------------
+									//
+									// Given the following code:
+									//
+									//   truthyExpression() || someExpression();
+									//
+									// the generated code is:
+									//
+									//   truthyExpression() || false;
+									//
+									// ------------------------------------------
+									//
+									// Given the following code:
+									//
+									//   falsyExpression() || someExpression();
+									//
+									// the generated code is:
+									//
+									//   false && someExpression();
+									//
+									const keepRight =
+										(expression.operator === "&&" && bool) ||
+										(expression.operator === "||" && !bool);
+
+									if (
+										!param.couldHaveSideEffects() &&
+										(param.isBoolean() || keepRight)
+									) {
+										// for case like
+										//
+										//   return'development'===process.env.NODE_ENV&&'foo'
+										//
+										// we need a space before the bool to prevent result like
+										//
+										//   returnfalse&&'foo'
+										//
+										const dep = new ConstDependency(
+											` ${bool}`,
+											/** @type {Range} */ (param.range)
+										);
+										dep.loc = parser.getLocation(expression);
+										parser.state.module.addPresentationalDependency(dep);
+									} else {
+										parser.walkExpression(expression.left);
+									}
+									if (!keepRight) {
+										const dep = new ConstDependency(
+											"0",
+											/** @type {Range} */ (expression.right.range)
+										);
+										dep.loc = parser.getLocation(expression);
+										parser.state.module.addPresentationalDependency(dep);
+									}
+									return keepRight;
+								}
+							} else if (expression.operator === "??") {
+								const param = parser.evaluateExpression(expression.left);
+								const keepRight = param.asNullish();
+								if (typeof keepRight === "boolean") {
+									// ------------------------------------------
+									//
+									// Given the following code:
+									//
+									//   nonNullish ?? someExpression();
+									//
+									// the generated code is:
+									//
+									//   nonNullish ?? 0;
+									//
+									// ------------------------------------------
+									//
+									// Given the following code:
+									//
+									//   nullish ?? someExpression();
+									//
+									// the generated code is:
+									//
+									//   null ?? someExpression();
+									//
+									if (!param.couldHaveSideEffects() && keepRight) {
+										// cspell:word returnnull
+										// for case like
+										//
+										//   return('development'===process.env.NODE_ENV&&null)??'foo'
+										//
+										// we need a space before the bool to prevent result like
+										//
+										//   returnnull??'foo'
+										//
+										const dep = new ConstDependency(
+											" null",
+											/** @type {Range} */ (param.range)
+										);
+										dep.loc = parser.getLocation(expression);
+										parser.state.module.addPresentationalDependency(dep);
+									} else {
+										const dep = new ConstDependency(
+											"0",
+											/** @type {Range} */ (expression.right.range)
+										);
+										dep.loc = parser.getLocation(expression);
+										parser.state.module.addPresentationalDependency(dep);
+										parser.walkExpression(expression.left);
+									}
+
+									return keepRight;
+								}
+							}
+						}
+					);
+					parser.hooks.optionalChaining.tap(PLUGIN_NAME, (expr) => {
+						/** @type {Expression[]} */
+						const optionalExpressionsStack = [];
+						/** @type {Expression | Super} */
+						let next = expr.expression;
+
+						while (
+							next.type === "MemberExpression" ||
+							next.type === "CallExpression"
+						) {
+							if (next.type === "MemberExpression") {
+								if (next.optional) {
+									// SuperNode can not be optional
+									optionalExpressionsStack.push(
+										/** @type {Expression} */ (next.object)
+									);
+								}
+								next = next.object;
+							} else {
+								if (next.optional) {
+									// SuperNode can not be optional
+									optionalExpressionsStack.push(
+										/** @type {Expression} */ (next.callee)
+									);
+								}
+								next = next.callee;
+							}
+						}
+
+						while (optionalExpressionsStack.length) {
+							const expression = optionalExpressionsStack.pop();
+							const evaluated = parser.evaluateExpression(
+								/** @type {Expression} */ (expression)
+							);
+
+							if (evaluated.asNullish()) {
+								// ------------------------------------------
+								//
+								// Given the following code:
+								//
+								//   nullishMemberChain?.a.b();
+								//
+								// the generated code is:
+								//
+								//   undefined;
+								//
+								// ------------------------------------------
+								//
+								const dep = new ConstDependency(
+									" undefined",
+									/** @type {Range} */ (expr.range)
+								);
+								dep.loc = parser.getLocation(expr);
+								parser.state.module.addPresentationalDependency(dep);
+								return true;
+							}
+						}
+					});
 					parser.hooks.evaluateIdentifier
 						.for("__resourceQuery")
-						.tap("ConstPlugin", expr => {
+						.tap(PLUGIN_NAME, (expr) => {
+							if (parser.scope.isAsmJs) return;
 							if (!parser.state.module) return;
-							return ParserHelpers.evaluateToString(
-								getQuery(parser.state.module.resource)
+							return evaluateToString(
+								cachedParseResource(parser.state.module.resource).query
 							)(expr);
 						});
 					parser.hooks.expression
 						.for("__resourceQuery")
-						.tap("ConstPlugin", () => {
+						.tap(PLUGIN_NAME, (expr) => {
+							if (parser.scope.isAsmJs) return;
+							if (!parser.state.module) return;
+							const dep = new CachedConstDependency(
+								JSON.stringify(
+									cachedParseResource(parser.state.module.resource).query
+								),
+								/** @type {Range} */ (expr.range),
+								"__resourceQuery"
+							);
+							dep.loc = parser.getLocation(expr);
+							parser.state.module.addPresentationalDependency(dep);
+							return true;
+						});
+
+					parser.hooks.evaluateIdentifier
+						.for("__resourceFragment")
+						.tap(PLUGIN_NAME, (expr) => {
+							if (parser.scope.isAsmJs) return;
+							if (!parser.state.module) return;
+							return evaluateToString(
+								cachedParseResource(parser.state.module.resource).fragment
+							)(expr);
+						});
+					parser.hooks.expression
+						.for("__resourceFragment")
+						.tap(PLUGIN_NAME, (expr) => {
+							if (parser.scope.isAsmJs) return;
 							if (!parser.state.module) return;
-							parser.state.current.addVariable(
-								"__resourceQuery",
-								JSON.stringify(getQuery(parser.state.module.resource))
+							const dep = new CachedConstDependency(
+								JSON.stringify(
+									cachedParseResource(parser.state.module.resource).fragment
+								),
+								/** @type {Range} */ (expr.range),
+								"__resourceFragment"
 							);
+							dep.loc = parser.getLocation(expr);
+							parser.state.module.addPresentationalDependency(dep);
 							return true;
 						});
 				};
 
 				normalModuleFactory.hooks.parser
-					.for("javascript/auto")
-					.tap("ConstPlugin", handler);
+					.for(JAVASCRIPT_MODULE_TYPE_AUTO)
+					.tap(PLUGIN_NAME, handler);
 				normalModuleFactory.hooks.parser
-					.for("javascript/dynamic")
-					.tap("ConstPlugin", handler);
+					.for(JAVASCRIPT_MODULE_TYPE_DYNAMIC)
+					.tap(PLUGIN_NAME, handler);
 				normalModuleFactory.hooks.parser
-					.for("javascript/esm")
-					.tap("ConstPlugin", handler);
+					.for(JAVASCRIPT_MODULE_TYPE_ESM)
+					.tap(PLUGIN_NAME, handler);
 			}
 		);
 	}
+
+	/**
+	 * Eliminate an unused statement.
+	 * @param {JavascriptParser} parser the parser
+	 * @param {Statement} statement the statement to remove
+	 * @param {boolean} alwaysInBlock whether to always generate curly brackets
+	 * @returns {void}
+	 */
+	eliminateUnusedStatement(parser, statement, alwaysInBlock) {
+		// Before removing the unused branch, the hoisted declarations
+		// must be collected.
+		//
+		// Given the following code:
+		//
+		//     if (true) f() else g()
+		//     if (false) {
+		//       function f() {}
+		//       const g = function g() {}
+		//       if (someTest) {
+		//         let a = 1
+		//         var x, {y, z} = obj
+		//       }
+		//     } else {
+		//       …
+		//     }
+		//
+		// the generated code is:
+		//
+		//     if (true) f() else {}
+		//     if (false) {
+		//       var f, x, y, z;   (in loose mode)
+		//       var x, y, z;      (in strict mode)
+		//     } else {
+		//       …
+		//     }
+		//
+		// NOTE: When code runs in strict mode, `var` declarations
+		// are hoisted but `function` declarations don't.
+		//
+		const declarations = parser.scope.isStrict
+			? getHoistedDeclarations(statement, false)
+			: getHoistedDeclarations(statement, true);
+
+		const inBlock = alwaysInBlock || statement.type === "BlockStatement";
+
+		let replacement = inBlock ? "{" : "";
+		replacement +=
+			declarations.length > 0 ? ` var ${declarations.join(", ")}; ` : "";
+		replacement += inBlock ? "}" : "";
+
+		const dep = new ConstDependency(
+			`// removed by dead control flow\n${replacement}`,
+			/** @type {Range} */ (statement.range)
+		);
+		dep.loc = parser.getLocation(statement);
+		parser.state.module.addPresentationalDependency(dep);
+	}
 }
 
 module.exports = ConstPlugin;
diff --git a/lib/ContextExclusionPlugin.js b/lib/ContextExclusionPlugin.js
index 1333e9dbcfd..e4bdd589c3f 100644
--- a/lib/ContextExclusionPlugin.js
+++ b/lib/ContextExclusionPlugin.js
@@ -1,15 +1,33 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+*/
+
 "use strict";
 
+/** @typedef {import("./Compiler")} Compiler */
+
+const PLUGIN_NAME = "ContextExclusionPlugin";
+
 class ContextExclusionPlugin {
+	/**
+	 * Creates an instance of ContextExclusionPlugin.
+	 * @param {RegExp} negativeMatcher Matcher regular expression
+	 */
 	constructor(negativeMatcher) {
+		/** @type {RegExp} */
 		this.negativeMatcher = negativeMatcher;
 	}
 
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
 	apply(compiler) {
-		compiler.hooks.contextModuleFactory.tap("ContextExclusionPlugin", cmf => {
-			cmf.hooks.contextModuleFiles.tap("ContextExclusionPlugin", files => {
-				return files.filter(filePath => !this.negativeMatcher.test(filePath));
-			});
+		compiler.hooks.contextModuleFactory.tap(PLUGIN_NAME, (cmf) => {
+			cmf.hooks.contextModuleFiles.tap(PLUGIN_NAME, (files) =>
+				files.filter((filePath) => !this.negativeMatcher.test(filePath))
+			);
 		});
 	}
 }
diff --git a/lib/ContextModule.js b/lib/ContextModule.js
index 175187a6973..2d72ce2ef6c 100644
--- a/lib/ContextModule.js
+++ b/lib/ContextModule.js
@@ -2,122 +2,356 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
-const path = require("path");
-const util = require("util");
+
 const { OriginalSource, RawSource } = require("webpack-sources");
-const Module = require("./Module");
 const AsyncDependenciesBlock = require("./AsyncDependenciesBlock");
+const Module = require("./Module");
+const {
+	JAVASCRIPT_TYPE,
+	JAVASCRIPT_TYPES
+} = require("./ModuleSourceTypeConstants");
+const { JAVASCRIPT_MODULE_TYPE_DYNAMIC } = require("./ModuleTypeConstants");
+const RuntimeGlobals = require("./RuntimeGlobals");
 const Template = require("./Template");
+const {
+	getOutgoingAsyncModules
+} = require("./async-modules/AsyncModuleHelpers");
+const { ImportPhase, ImportPhaseUtils } = require("./dependencies/ImportPhase");
+const { makeWebpackError } = require("./errors/HookWebpackError");
+const WebpackError = require("./errors/WebpackError");
+const {
+	compareLocations,
+	compareModulesById,
+	compareSelect,
+	concatComparators,
+	keepOriginalOrder
+} = require("./util/comparators");
+const {
+	contextify,
+	makePathsRelative,
+	parseResource
+} = require("./util/identifier");
+const makeSerializable = require("./util/makeSerializable");
+const { propertyAccess } = require("./util/property");
 
+/** @typedef {import("webpack-sources").Source} Source */
+/** @typedef {import("../declarations/WebpackOptions").ResolveOptions} ResolveOptions */
+/** @typedef {import("./config/defaults").WebpackOptionsNormalizedWithDefaults} WebpackOptions */
+/** @typedef {import("./Chunk")} Chunk */
+/** @typedef {import("./Chunk").ChunkId} ChunkId */
+/** @typedef {import("./Chunk").ChunkName} ChunkName */
+/** @typedef {import("./ChunkGraph")} ChunkGraph */
+/** @typedef {import("./ChunkGraph").ModuleId} ModuleId */
+/** @typedef {import("./ChunkGroup").RawChunkGroupOptions} RawChunkGroupOptions */
+/** @typedef {import("./Compilation")} Compilation */
+/** @typedef {import("./Dependency")} Dependency */
+/** @typedef {import("./Dependency").RawReferencedExports} RawReferencedExports */
+/** @typedef {import("./Generator").SourceTypes} SourceTypes */
+/** @typedef {import("./FileSystemInfo").Snapshot} Snapshot */
+/** @typedef {import("./Module").BuildCallback} BuildCallback */
+/** @typedef {import("./Module").BuildInfo} BuildInfo */
+/** @typedef {import("./Module").FileSystemDependencies} FileSystemDependencies */
+/** @typedef {import("./Module").BuildMeta} BuildMeta */
+/** @typedef {import("./Module").CodeGenerationContext} CodeGenerationContext */
+/** @typedef {import("./Module").CodeGenerationResult} CodeGenerationResult */
+/** @typedef {import("./Module").LibIdentOptions} LibIdentOptions */
+/** @typedef {import("./Module").LibIdent} LibIdent */
+/** @typedef {import("./Module").NeedBuildCallback} NeedBuildCallback */
+/** @typedef {import("./Module").NeedBuildContext} NeedBuildContext */
+/** @typedef {import("./Module").RuntimeRequirements} RuntimeRequirements */
+/** @typedef {import("./Module").Sources} Sources */
+/** @typedef {import("./RequestShortener")} RequestShortener */
+/** @typedef {import("./ResolverFactory").ResolverWithOptions} ResolverWithOptions */
+/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */
 /** @typedef {import("./dependencies/ContextElementDependency")} ContextElementDependency */
+/** @typedef {import("./javascript/JavascriptParser").ImportAttributes} ImportAttributes */
+/** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext<[string, boolean]>} ObjectDeserializerContext */
+/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext<[string, boolean]>} ObjectSerializerContext */
+/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
+/** @typedef {import("./dependencies/ImportPhase").ImportPhaseType} ImportPhaseType */
+
+/** @typedef {"sync" | "eager" | "weak" | "async-weak" | "lazy" | "lazy-once"} ContextMode Context mode */
+
+/**
+ * @typedef {object} ContextOptions
+ * @property {ContextMode} mode
+ * @property {boolean} recursive
+ * @property {RegExp | false | null} regExp
+ * @property {"strict" | boolean=} namespaceObject
+ * @property {string=} addon
+ * @property {ChunkName=} chunkName
+ * @property {RegExp | null=} include
+ * @property {RegExp | null=} exclude
+ * @property {RawChunkGroupOptions=} groupOptions
+ * @property {string=} typePrefix
+ * @property {string=} category
+ * @property {RawReferencedExports | null=} referencedExports exports referenced from modules (won't be mangled)
+ * @property {string | null=} layer
+ * @property {ImportAttributes=} attributes
+ * @property {ImportPhaseType=} phase
+ * @property {string[]=} patterns glob patterns for import.meta.glob
+ * @property {string=} requestContext importer context for import.meta.glob
+ * @property {string=} importName import selection for import.meta.glob
+ * @property {boolean=} exhaustive exhaustive mode for import.meta.glob
+ * @property {boolean=} caseSensitive case-sensitive matching for import.meta.glob
+ */
+
+/**
+ * @typedef {object} ContextModuleOptionsExtras
+ * @property {false | string | string[]} resource
+ * @property {string=} resourceQuery
+ * @property {string=} resourceFragment
+ * @property {ResolveOptions=} resolveOptions
+ */
+
+/** @typedef {ContextOptions & ContextModuleOptionsExtras} ContextModuleOptions */
+
+/**
+ * @callback ResolveDependenciesCallback
+ * @param {Error | null} err
+ * @param {ContextElementDependency[]=} dependencies
+ * @returns {void}
+ */
+
+/**
+ * @callback ResolveDependencies
+ * @param {InputFileSystem} fs
+ * @param {ContextModuleOptions} options
+ * @param {ResolveDependenciesCallback} callback
+ */
+
+/** @typedef {1 | 3 | 7 | 9} FakeMapType */
+
+/** @typedef {Record} FakeMap */
+/** @typedef {Record} UserRequestMap */
+/** @typedef {Record} UserRequestsMap */
+
+/**
+ * Defines the build info properties specific to context modules.
+ * @typedef {object} KnownContextModuleBuildInfo
+ * @property {(Snapshot | null)=} snapshot
+ */
+
+/** @typedef {BuildInfo & KnownContextModuleBuildInfo} ContextModuleBuildInfo */
 
 class ContextModule extends Module {
-	// type ContextMode = "sync" | "eager" | "weak" | "async-weak" | "lazy" | "lazy-once"
-	// type ContextOptions = { resource: string, recursive: boolean, regExp: RegExp, addon?: string, mode?: ContextMode, chunkName?: string, include?: RegExp, exclude?: RegExp, groupOptions?: Object }
-	// resolveDependencies: (fs: FS, options: ContextOptions, (err: Error?, dependencies: Dependency[]) => void) => void
-	// options: ContextOptions
+	/**
+	 * @param {ResolveDependencies} resolveDependencies function to get dependencies in this context
+	 * @param {ContextModuleOptions} options options object
+	 */
 	constructor(resolveDependencies, options) {
-		let resource;
-		let resourceQuery;
-		const queryIdx = options.resource.indexOf("?");
-		if (queryIdx >= 0) {
-			resource = options.resource.substr(0, queryIdx);
-			resourceQuery = options.resource.substr(queryIdx);
+		if (!options || typeof options.resource === "string") {
+			const parsed = parseResource(
+				options ? /** @type {string} */ (options.resource) : ""
+			);
+			const resource = parsed.path;
+			const resourceQuery = (options && options.resourceQuery) || parsed.query;
+			const resourceFragment =
+				(options && options.resourceFragment) || parsed.fragment;
+			const layer = options && options.layer;
+
+			super(JAVASCRIPT_MODULE_TYPE_DYNAMIC, resource, layer);
+			/** @type {ContextModuleOptions} */
+			this.options = {
+				...options,
+				resource,
+				resourceQuery,
+				resourceFragment
+			};
 		} else {
-			resource = options.resource;
-			resourceQuery = "";
+			super(JAVASCRIPT_MODULE_TYPE_DYNAMIC, undefined, options.layer);
+			/** @type {ContextModuleOptions} */
+			this.options = {
+				...options,
+				resource: options.resource,
+				resourceQuery: options.resourceQuery || "",
+				resourceFragment: options.resourceFragment || ""
+			};
 		}
 
-		super("javascript/dynamic", resource);
+		// Redeclared with the context module specific shape
+		/** @type {ContextModuleBuildInfo | undefined} */
+		this.buildInfo = undefined;
 
 		// Info from Factory
+		/** @type {ResolveDependencies | undefined} */
 		this.resolveDependencies = resolveDependencies;
-		this.options = Object.assign({}, options, {
-			resource: resource,
-			resourceQuery: resourceQuery
-		});
-		if (options.resolveOptions !== undefined) {
+		if (options && options.resolveOptions !== undefined) {
+			/** @type {ResolveOptions | undefined} */
 			this.resolveOptions = options.resolveOptions;
 		}
 
-		// Info from Build
-		this._contextDependencies = new Set([this.context]);
-
-		if (typeof options.mode !== "string") {
+		if (options && typeof options.mode !== "string") {
 			throw new Error("options.mode is a required option");
 		}
 
+		/** @type {string} */
 		this._identifier = this._createIdentifier();
+		/** @type {boolean} */
+		this._forceBuild = true;
+	}
+
+	/**
+	 * Returns the source types this module can generate.
+	 * @returns {SourceTypes} types available (do not mutate)
+	 */
+	getSourceTypes() {
+		return JAVASCRIPT_TYPES;
 	}
 
+	/**
+	 * Assuming this module is in the cache. Update the (cached) module with
+	 * the fresh module from the factory. Usually updates internal references
+	 * and properties.
+	 * @param {Module} module fresh module
+	 * @returns {void}
+	 */
 	updateCacheModule(module) {
-		this.resolveDependencies = module.resolveDependencies;
-		this.options = module.options;
-		this.resolveOptions = module.resolveOptions;
+		const m = /** @type {ContextModule} */ (module);
+		this.resolveDependencies = m.resolveDependencies;
+		this.options = m.options;
 	}
 
-	prettyRegExp(regexString) {
-		// remove the "/" at the front and the beginning
-		// "/foo/" -> "foo"
-		return regexString.substring(1, regexString.length - 1);
+	/**
+	 * Assuming this module is in the cache. Remove internal references to allow freeing some memory.
+	 */
+	cleanupForCache() {
+		super.cleanupForCache();
+		this.resolveDependencies = undefined;
 	}
 
-	contextify(context, request) {
-		return request
-			.split("!")
-			.map(subrequest => {
-				let rp = path.relative(context, subrequest);
-				if (path.sep === "\\") rp = rp.replace(/\\/g, "/");
-				if (rp.indexOf("../") !== 0) rp = "./" + rp;
-				return rp;
-			})
-			.join("!");
+	/**
+	 * @private
+	 * @param {RegExp} regexString RegExp as a string
+	 * @param {boolean=} stripSlash do we need to strip a slsh
+	 * @returns {string} pretty RegExp
+	 */
+	_prettyRegExp(regexString, stripSlash = true) {
+		const str = stripSlash
+			? regexString.source + regexString.flags
+			: `${regexString}`;
+		// Escape chars that are structural in the identifier/source-name URL
+		// (`!`/`|` separators, `?` query, `#` fragment) so a regex like
+		// `(?:a|b)` isn't misread and truncated when it becomes a source name.
+		return str
+			.replace(/!/g, "%21")
+			.replace(/\|/g, "%7C")
+			.replace(/\?/g, "%3F")
+			.replace(/#/g, "%23");
 	}
 
 	_createIdentifier() {
-		let identifier = this.context;
+		let identifier =
+			this.context ||
+			(typeof this.options.resource === "string" ||
+			this.options.resource === false
+				? `${this.options.resource}`
+				: this.options.resource.join("|"));
 		if (this.options.resourceQuery) {
-			identifier += ` ${this.options.resourceQuery}`;
+			identifier += `|${this.options.resourceQuery}`;
+		}
+		if (this.options.resourceFragment) {
+			identifier += `|${this.options.resourceFragment}`;
 		}
 		if (this.options.mode) {
-			identifier += ` ${this.options.mode}`;
+			identifier += `|${this.options.mode}`;
 		}
 		if (!this.options.recursive) {
-			identifier += " nonrecursive";
+			identifier += "|nonrecursive";
 		}
 		if (this.options.addon) {
-			identifier += ` ${this.options.addon}`;
+			identifier += `|${this.options.addon}`;
 		}
 		if (this.options.regExp) {
-			identifier += ` ${this.options.regExp}`;
+			identifier += `|${this._prettyRegExp(this.options.regExp, false)}`;
+		} else if (this.options.patterns) {
+			identifier += `|patterns: ${this.options.patterns.join(", ")}`;
+		}
+		if (this.options.requestContext) {
+			identifier += `|requestContext: ${this.options.requestContext}`;
 		}
 		if (this.options.include) {
-			identifier += ` include: ${this.options.include}`;
+			identifier += `|include: ${this._prettyRegExp(
+				this.options.include,
+				false
+			)}`;
 		}
 		if (this.options.exclude) {
-			identifier += ` exclude: ${this.options.exclude}`;
+			identifier += `|exclude: ${this._prettyRegExp(
+				this.options.exclude,
+				false
+			)}`;
+		}
+		if (this.options.referencedExports) {
+			identifier += `|referencedExports: ${JSON.stringify(
+				this.options.referencedExports
+			)}`;
+		}
+		if (this.options.importName) {
+			identifier += `|import: ${this.options.importName}`;
+		}
+		if (this.options.exhaustive) {
+			identifier += "|exhaustive";
+		}
+		if (this.options.caseSensitive === false) {
+			identifier += "|case-insensitive";
+		}
+		if (this.options.chunkName) {
+			identifier += `|chunkName: ${this.options.chunkName}`;
 		}
 		if (this.options.groupOptions) {
-			identifier += ` groupOptions: ${JSON.stringify(
+			identifier += `|groupOptions: ${JSON.stringify(
 				this.options.groupOptions
 			)}`;
 		}
 		if (this.options.namespaceObject === "strict") {
-			identifier += " strict namespace object";
+			identifier += "|strict namespace object";
 		} else if (this.options.namespaceObject) {
-			identifier += " namespace object";
+			identifier += "|namespace object";
+		}
+		if (this.options.attributes) {
+			identifier += `|importAttributes: ${JSON.stringify(this.options.attributes)}`;
+		}
+		if (this.options.phase) {
+			identifier += `|importPhase: ${this.options.phase}`;
+		}
+		if (this.layer) {
+			identifier += `|layer: ${this.layer}`;
 		}
-
 		return identifier;
 	}
 
+	/**
+	 * Returns the unique identifier used to reference this module.
+	 * @returns {string} a unique identifier of the module
+	 */
 	identifier() {
 		return this._identifier;
 	}
 
+	/**
+	 * Returns a human-readable identifier for this module.
+	 * @param {RequestShortener} requestShortener the request shortener
+	 * @returns {string} a user readable identifier of the module
+	 */
 	readableIdentifier(requestShortener) {
-		let identifier = requestShortener.shorten(this.context);
+		/** @type {string} */
+		let identifier;
+
+		if (this.context) {
+			identifier = `${requestShortener.shorten(this.context)}/`;
+		} else if (
+			typeof this.options.resource === "string" ||
+			this.options.resource === false
+		) {
+			identifier = `${requestShortener.shorten(`${this.options.resource}`)}/`;
+		} else {
+			identifier = this.options.resource
+				.map((r) => `${requestShortener.shorten(r)}/`)
+				.join(" ");
+		}
 		if (this.options.resourceQuery) {
 			identifier += ` ${this.options.resourceQuery}`;
 		}
@@ -131,18 +365,39 @@ class ContextModule extends Module {
 			identifier += ` ${requestShortener.shorten(this.options.addon)}`;
 		}
 		if (this.options.regExp) {
-			identifier += ` ${this.prettyRegExp(this.options.regExp + "")}`;
+			identifier += ` ${this._prettyRegExp(this.options.regExp)}`;
+		} else if (this.options.patterns) {
+			identifier += ` patterns: ${this.options.patterns.join(", ")}`;
+		}
+		if (this.options.requestContext) {
+			identifier += ` requestContext: ${this.options.requestContext}`;
+		}
+		if (this.options.exhaustive) {
+			identifier += " exhaustive";
+		}
+		if (this.options.caseSensitive === false) {
+			identifier += " case-insensitive";
 		}
 		if (this.options.include) {
-			identifier += ` include: ${this.prettyRegExp(this.options.include + "")}`;
+			identifier += ` include: ${this._prettyRegExp(this.options.include)}`;
 		}
 		if (this.options.exclude) {
-			identifier += ` exclude: ${this.prettyRegExp(this.options.exclude + "")}`;
+			identifier += ` exclude: ${this._prettyRegExp(this.options.exclude)}`;
+		}
+		if (this.options.referencedExports) {
+			identifier += ` referencedExports: ${this.options.referencedExports
+				.map((e) => e.join("."))
+				.join(", ")}`;
+		}
+		if (this.options.chunkName) {
+			identifier += ` chunkName: ${this.options.chunkName}`;
 		}
 		if (this.options.groupOptions) {
 			const groupOptions = this.options.groupOptions;
 			for (const key of Object.keys(groupOptions)) {
-				identifier += ` ${key}: ${groupOptions[key]}`;
+				identifier += ` ${key}: ${
+					groupOptions[/** @type {keyof RawChunkGroupOptions} */ (key)]
+				}`;
 			}
 		}
 		if (this.options.namespaceObject === "strict") {
@@ -154,8 +409,38 @@ class ContextModule extends Module {
 		return identifier;
 	}
 
+	/**
+	 * Gets the library identifier.
+	 * @param {LibIdentOptions} options options
+	 * @returns {LibIdent | null} an identifier for library inclusion
+	 */
 	libIdent(options) {
-		let identifier = this.contextify(options.context, this.context);
+		/** @type {string} */
+		let identifier;
+
+		if (this.context) {
+			identifier = contextify(
+				options.context,
+				this.context,
+				options.associatedObjectForCache
+			);
+		} else if (typeof this.options.resource === "string") {
+			identifier = contextify(
+				options.context,
+				this.options.resource,
+				options.associatedObjectForCache
+			);
+		} else if (this.options.resource === false) {
+			identifier = "false";
+		} else {
+			identifier = this.options.resource
+				.map((res) =>
+					contextify(options.context, res, options.associatedObjectForCache)
+				)
+				.join(" ");
+		}
+
+		if (this.layer) identifier = `(${this.layer})/${identifier}`;
 		if (this.options.mode) {
 			identifier += ` ${this.options.mode}`;
 		}
@@ -163,39 +448,109 @@ class ContextModule extends Module {
 			identifier += " recursive";
 		}
 		if (this.options.addon) {
-			identifier += ` ${this.contextify(options.context, this.options.addon)}`;
+			identifier += ` ${contextify(
+				options.context,
+				this.options.addon,
+				options.associatedObjectForCache
+			)}`;
 		}
 		if (this.options.regExp) {
-			identifier += ` ${this.prettyRegExp(this.options.regExp + "")}`;
+			identifier += ` ${this._prettyRegExp(this.options.regExp)}`;
+		} else if (this.options.patterns) {
+			identifier += ` patterns: ${this.options.patterns.join(", ")}`;
+		}
+		if (this.options.requestContext) {
+			identifier += ` requestContext: ${this.options.requestContext}`;
+		}
+		if (this.options.exhaustive) {
+			identifier += " exhaustive";
+		}
+		if (this.options.caseSensitive === false) {
+			identifier += " case-insensitive";
 		}
 		if (this.options.include) {
-			identifier += ` include: ${this.prettyRegExp(this.options.include + "")}`;
+			identifier += ` include: ${this._prettyRegExp(this.options.include)}`;
 		}
 		if (this.options.exclude) {
-			identifier += ` exclude: ${this.prettyRegExp(this.options.exclude + "")}`;
+			identifier += ` exclude: ${this._prettyRegExp(this.options.exclude)}`;
+		}
+		if (this.options.referencedExports) {
+			identifier += ` referencedExports: ${this.options.referencedExports
+				.map((e) => e.join("."))
+				.join(", ")}`;
 		}
 
 		return identifier;
 	}
 
-	needRebuild(fileTimestamps, contextTimestamps) {
-		const ts = contextTimestamps.get(this.context);
-		if (!ts) {
-			return true;
+	/**
+	 * Invalidates the cached state associated with this value.
+	 * @returns {void}
+	 */
+	invalidateBuild() {
+		this._forceBuild = true;
+	}
+
+	/**
+	 * Checks whether the module needs to be rebuilt for the current build state.
+	 * @param {NeedBuildContext} context context info
+	 * @param {NeedBuildCallback} callback callback function, returns true, if the module needs a rebuild
+	 * @returns {void}
+	 */
+	needBuild({ fileSystemInfo }, callback) {
+		// build if enforced
+		if (this._forceBuild) return callback(null, true);
+
+		const buildInfo = /** @type {ContextModuleBuildInfo} */ (this.buildInfo);
+
+		// always build when we have no snapshot and context
+		if (!buildInfo.snapshot) {
+			return callback(null, Boolean(this.context || this.options.resource));
 		}
 
-		return ts >= this.buildInfo.builtTime;
+		fileSystemInfo.checkSnapshotValid(buildInfo.snapshot, (err, valid) => {
+			callback(err, !valid);
+		});
 	}
 
+	/**
+	 * Builds the module using the provided compilation context.
+	 * @param {WebpackOptions} options webpack options
+	 * @param {Compilation} compilation the compilation
+	 * @param {ResolverWithOptions} resolver the resolver
+	 * @param {InputFileSystem} fs the file system
+	 * @param {BuildCallback} callback callback function
+	 * @returns {void}
+	 */
 	build(options, compilation, resolver, fs, callback) {
-		this.built = true;
-		this.buildMeta = {};
+		this._forceBuild = false;
+		/** @type {BuildMeta} */
+		this.buildMeta = {
+			exportsType: "default",
+			defaultObject: "redirect-warn"
+		};
+
+		// Respect the global module parser `overrideStrict` option
+		const overrideStrict =
+			options.module.parser &&
+			options.module.parser.javascript &&
+			options.module.parser.javascript.overrideStrict;
+
 		this.buildInfo = {
-			builtTime: Date.now(),
-			contextDependencies: this._contextDependencies
+			strict: overrideStrict ? overrideStrict === "strict" : undefined,
+			snapshot: undefined
 		};
-		this.resolveDependencies(fs, this.options, (err, dependencies) => {
-			if (err) return callback(err);
+
+		this.dependencies.length = 0;
+		this.blocks.length = 0;
+		const startTime = Date.now();
+		/** @type {ResolveDependencies} */
+		(this.resolveDependencies)(fs, this.options, (err, dependencies) => {
+			if (err) {
+				return callback(
+					makeWebpackError(err, "ContextModule.resolveDependencies")
+				);
+			}
 
 			// abort if something failed
 			// this will create an empty context
@@ -206,9 +561,17 @@ class ContextModule extends Module {
 
 			// enhance dependencies with meta info
 			for (const dep of dependencies) {
-				dep.loc = dep.userRequest;
+				dep.loc = {
+					name: dep.userRequest
+				};
 				dep.request = this.options.addon + dep.request;
 			}
+			dependencies.sort(
+				concatComparators(
+					compareSelect((a) => a.loc, compareLocations),
+					keepOriginalOrder(this.dependencies)
+				)
+			);
 
 			if (this.options.mode === "sync" || this.options.mode === "eager") {
 				// if we have an sync or eager context
@@ -218,12 +581,10 @@ class ContextModule extends Module {
 				// for the lazy-once mode create a new async dependency block
 				// and add that block to this context
 				if (dependencies.length > 0) {
-					const block = new AsyncDependenciesBlock(
-						Object.assign({}, this.options.groupOptions, {
-							name: this.options.chunkName
-						}),
-						this
-					);
+					const block = new AsyncDependenciesBlock({
+						...this.options.groupOptions,
+						name: this.options.chunkName
+					});
 					for (const dep of dependencies) {
 						block.addDependency(dep);
 					}
@@ -245,20 +606,20 @@ class ContextModule extends Module {
 				for (const dep of dependencies) {
 					let chunkName = this.options.chunkName;
 					if (chunkName) {
-						if (!/\[(index|request)\]/.test(chunkName)) {
+						if (!/\[(?:index|request)\]/.test(chunkName)) {
 							chunkName += "[index]";
 						}
-						chunkName = chunkName.replace(/\[index\]/g, index++);
+						chunkName = chunkName.replace(/\[index\]/g, `${index++}`);
 						chunkName = chunkName.replace(
 							/\[request\]/g,
 							Template.toPath(dep.userRequest)
 						);
 					}
 					const block = new AsyncDependenciesBlock(
-						Object.assign({}, this.options.groupOptions, {
+						{
+							...this.options.groupOptions,
 							name: chunkName
-						}),
-						dep.module,
+						},
 						dep.loc,
 						dep.userRequest
 					);
@@ -267,118 +628,284 @@ class ContextModule extends Module {
 				}
 			} else {
 				callback(
-					new Error(`Unsupported mode "${this.options.mode}" in context`)
+					new WebpackError(`Unsupported mode "${this.options.mode}" in context`)
 				);
 				return;
 			}
-			callback();
+			if (!this.context && !this.options.resource) return callback();
+
+			const snapshotOptions = compilation.options.snapshot.contextModule;
+
+			compilation.fileSystemInfo.createSnapshot(
+				startTime,
+				null,
+				this.context
+					? [this.context]
+					: typeof this.options.resource === "string"
+						? [this.options.resource]
+						: /** @type {string[]} */ (this.options.resource),
+				null,
+				snapshotOptions,
+				(err, snapshot) => {
+					if (err) return callback(err);
+					/** @type {BuildInfo} */
+					(this.buildInfo).snapshot = snapshot;
+					callback();
+				}
+			);
 		});
 	}
 
-	getUserRequestMap(dependencies) {
+	/**
+	 * Adds the provided file dependencies to the module.
+	 * @param {FileSystemDependencies} fileDependencies set where file dependencies are added to
+	 * @param {FileSystemDependencies} contextDependencies set where context dependencies are added to
+	 * @param {FileSystemDependencies} missingDependencies set where missing dependencies are added to
+	 * @param {FileSystemDependencies} buildDependencies set where build dependencies are added to
+	 */
+	addCacheDependencies(
+		fileDependencies,
+		contextDependencies,
+		missingDependencies,
+		buildDependencies
+	) {
+		if (this.context) {
+			contextDependencies.add(this.context);
+		} else if (typeof this.options.resource === "string") {
+			contextDependencies.add(this.options.resource);
+		} else if (this.options.resource === false) {
+			// Do nothing
+		} else {
+			for (const res of this.options.resource) contextDependencies.add(res);
+		}
+	}
+
+	/**
+	 * @param {Dependency[]} dependencies all dependencies
+	 * @param {ChunkGraph} chunkGraph chunk graph
+	 * @returns {UserRequestMap} map with user requests
+	 */
+	getUserRequestMap(dependencies, chunkGraph) {
+		const moduleGraph = chunkGraph.moduleGraph;
 		// if we filter first we get a new array
-		// therefor we dont need to create a clone of dependencies explicitly
+		// therefore we don't need to create a clone of dependencies explicitly
 		// therefore the order of this is !important!
-		return dependencies
-			.filter(dependency => dependency.module)
-			.sort((a, b) => {
-				if (a.userRequest === b.userRequest) {
-					return 0;
-				}
-				return a.userRequest < b.userRequest ? -1 : 1;
-			})
-			.reduce((map, dep) => {
-				map[dep.userRequest] = dep.module.id;
-				return map;
-			}, Object.create(null));
+		const sortedDependencies =
+			/** @type {ContextElementDependency[]} */
+			(dependencies)
+				.filter((dependency) => moduleGraph.getModule(dependency))
+				.sort((a, b) => {
+					if (a.userRequest === b.userRequest) {
+						return 0;
+					}
+					return a.userRequest < b.userRequest ? -1 : 1;
+				});
+		/** @type {UserRequestMap} */
+		const map = Object.create(null);
+		for (const dep of sortedDependencies) {
+			const module = /** @type {Module} */ (moduleGraph.getModule(dep));
+			map[dep.userRequest] =
+				/** @type {ModuleId} */
+				(chunkGraph.getModuleId(module));
+		}
+		return map;
 	}
 
-	getFakeMap(dependencies) {
+	/**
+	 * @param {Dependency[]} dependencies all dependencies
+	 * @param {ChunkGraph} chunkGraph chunk graph
+	 * @returns {FakeMap | FakeMapType} fake map
+	 */
+	getFakeMap(dependencies, chunkGraph) {
 		if (!this.options.namespaceObject) {
 			return 9;
 		}
+		const moduleGraph = chunkGraph.moduleGraph;
+		// bitfield
+		let hasType = 0;
+		const comparator = compareModulesById(chunkGraph);
 		// if we filter first we get a new array
-		// therefor we dont need to create a clone of dependencies explicitly
+		// therefore we don't need to create a clone of dependencies explicitly
 		// therefore the order of this is !important!
-		let hasNonHarmony = false;
-		let hasNamespace = false;
-		let hasNamed = false;
-		const fakeMap = dependencies
-			.filter(dependency => dependency.module)
-			.sort((a, b) => {
-				return b.module.id - a.module.id;
-			})
-			.reduce((map, dep) => {
-				const exportsType =
-					dep.module.buildMeta && dep.module.buildMeta.exportsType;
-				const id = dep.module.id;
-				if (!exportsType) {
-					map[id] = this.options.namespaceObject === "strict" ? 1 : 7;
-					hasNonHarmony = true;
-				} else if (exportsType === "namespace") {
-					map[id] = 9;
-					hasNamespace = true;
-				} else if (exportsType === "named") {
-					map[id] = 3;
-					hasNamed = true;
-				}
-				return map;
-			}, Object.create(null));
-		if (!hasNamespace && hasNonHarmony && !hasNamed) {
-			return this.options.namespaceObject === "strict" ? 1 : 7;
+		const sortedModules = dependencies
+			.map(
+				(dependency) =>
+					/** @type {Module} */ (moduleGraph.getModule(dependency))
+			)
+			.filter(Boolean)
+			.sort(comparator);
+		/** @type {FakeMap} */
+		const fakeMap = Object.create(null);
+		for (const module of sortedModules) {
+			const exportsType = module.getExportsType(
+				moduleGraph,
+				this.options.namespaceObject === "strict"
+			);
+			const id = /** @type {ModuleId} */ (chunkGraph.getModuleId(module));
+			switch (exportsType) {
+				case "namespace":
+					fakeMap[id] = 9;
+					hasType |= 1;
+					break;
+				case "dynamic":
+					fakeMap[id] = 7;
+					hasType |= 2;
+					break;
+				case "default-only":
+					fakeMap[id] = 1;
+					hasType |= 4;
+					break;
+				case "default-with-named":
+					fakeMap[id] = 3;
+					hasType |= 8;
+					break;
+				default:
+					throw new Error(`Unexpected exports type ${exportsType}`);
+			}
 		}
-		if (hasNamespace && !hasNonHarmony && !hasNamed) {
+		if (hasType === 1) {
 			return 9;
 		}
-		if (!hasNamespace && !hasNonHarmony && hasNamed) {
+		if (hasType === 2) {
+			return 7;
+		}
+		if (hasType === 4) {
+			return 1;
+		}
+		if (hasType === 8) {
 			return 3;
 		}
-		if (!hasNamespace && !hasNonHarmony && !hasNamed) {
+		if (hasType === 0) {
 			return 9;
 		}
 		return fakeMap;
 	}
 
-	getFakeMapInitStatement(fakeMap) {
+	/**
+	 * @param {FakeMap | FakeMapType} fakeMap fake map
+	 * @param {RuntimeTemplate=} runtimeTemplate the runtime template
+	 * @returns {string} fake map init statement
+	 */
+	getFakeMapInitStatement(fakeMap, runtimeTemplate) {
+		const decl = runtimeTemplate ? runtimeTemplate.renderConst() : "var";
 		return typeof fakeMap === "object"
-			? `var fakeMap = ${JSON.stringify(fakeMap, null, "\t")};`
+			? `${decl} fakeMap = ${JSON.stringify(fakeMap, null, "\t")};`
+			: "";
+	}
+
+	/**
+	 * @param {Dependency[]} dependencies all dependencies
+	 * @param {ChunkGraph} chunkGraph chunk graph
+	 * @returns {UserRequestsMap} map with user requests
+	 */
+	getModuleDeferredAsyncDepsMap(dependencies, chunkGraph) {
+		const moduleGraph = chunkGraph.moduleGraph;
+		const comparator = compareModulesById(chunkGraph);
+		// if we filter first we get a new array
+		// therefore we don't need to create a clone of dependencies explicitly
+		// therefore the order of this is !important!
+		const sortedModules = dependencies
+			.map(
+				(dependency) =>
+					/** @type {Module} */ (moduleGraph.getModule(dependency))
+			)
+			.filter(Boolean)
+			.sort(comparator);
+		/** @type {UserRequestsMap} */
+		const map = Object.create(null);
+		for (const module of sortedModules) {
+			if (!(/** @type {BuildMeta} */ (module.buildMeta).async)) {
+				const id = /** @type {ModuleId} */ (chunkGraph.getModuleId(module));
+				map[id] = Array.from(
+					getOutgoingAsyncModules(chunkGraph.moduleGraph, module),
+					(m) => chunkGraph.getModuleId(m)
+				).filter((id) => id !== null);
+			}
+		}
+		return map;
+	}
+
+	/**
+	 * @param {false | UserRequestsMap} asyncDepsMap fake map
+	 * @param {RuntimeTemplate=} runtimeTemplate the runtime template
+	 * @returns {string} async deps map init statement
+	 */
+	getModuleDeferredAsyncDepsMapInitStatement(asyncDepsMap, runtimeTemplate) {
+		const decl = runtimeTemplate ? runtimeTemplate.renderConst() : "var";
+		return typeof asyncDepsMap === "object"
+			? `${decl} asyncDepsMap = ${JSON.stringify(asyncDepsMap, null, "\t")};`
 			: "";
 	}
 
-	getReturn(type) {
+	/**
+	 * @param {FakeMapType} type type
+	 * @param {boolean=} asyncModule is async module
+	 * @returns {string} return result
+	 */
+	getReturn(type, asyncModule) {
 		if (type === 9) {
-			return "__webpack_require__(id)";
+			return `${RuntimeGlobals.require}(id)`;
 		}
-		return `__webpack_require__.t(id, ${type})`;
+		return `${RuntimeGlobals.createFakeNamespaceObject}(id, ${type}${
+			asyncModule ? " | 16" : ""
+		})`;
 	}
 
-	getReturnModuleObjectSource(fakeMap, fakeMapDataExpression = "fakeMap[id]") {
-		if (typeof fakeMap === "number") {
-			return `return ${this.getReturn(fakeMap)};`;
+	/**
+	 * @param {FakeMap | FakeMapType} fakeMap fake map
+	 * @param {boolean=} asyncModule is async module
+	 * @param {string=} asyncDeps async deps for deferred module
+	 * @param {string=} fakeMapDataExpression fake map data expression
+	 * @returns {string} module object source
+	 */
+	getReturnModuleObjectSource(
+		fakeMap,
+		asyncModule,
+		asyncDeps,
+		fakeMapDataExpression = "fakeMap[id]"
+	) {
+		const source =
+			typeof fakeMap === "number"
+				? this.getReturn(fakeMap, asyncModule)
+				: `${RuntimeGlobals.createFakeNamespaceObject}(id, ${fakeMapDataExpression}${asyncModule ? " | 16" : ""})`;
+		if (asyncDeps) {
+			if (!asyncModule) {
+				throw new Error("Must be async when module is deferred");
+			}
+			const type =
+				typeof fakeMap === "number" ? fakeMap : fakeMapDataExpression;
+			return `${asyncDeps} ? ${asyncDeps}.length ? ${RuntimeGlobals.deferredModuleAsyncTransitiveDependencies}(${asyncDeps}).then(${RuntimeGlobals.makeDeferredNamespaceObject}.bind(${RuntimeGlobals.require}, id, ${type} ^ 1, true)) : ${RuntimeGlobals.makeDeferredNamespaceObject}(id, ${type} ^ 1 | 16) : ${source}`;
 		}
-		return `return __webpack_require__.t(id, ${fakeMapDataExpression})`;
+		return source;
 	}
 
-	getSyncSource(dependencies, id) {
-		const map = this.getUserRequestMap(dependencies);
-		const fakeMap = this.getFakeMap(dependencies);
+	/**
+	 * @param {Dependency[]} dependencies dependencies
+	 * @param {ModuleId} id module id
+	 * @param {ChunkGraph} chunkGraph the chunk graph
+	 * @param {RuntimeTemplate=} runtimeTemplate the runtime template
+	 * @returns {string} source code
+	 */
+	getSyncSource(dependencies, id, chunkGraph, runtimeTemplate) {
+		const map = this.getUserRequestMap(dependencies, chunkGraph);
+		const fakeMap = this.getFakeMap(dependencies, chunkGraph);
 		const returnModuleObject = this.getReturnModuleObjectSource(fakeMap);
+		const cst = runtimeTemplate ? runtimeTemplate.renderConst() : "var";
 
-		return `var map = ${JSON.stringify(map, null, "\t")};
-${this.getFakeMapInitStatement(fakeMap)}
+		return `${cst} map = ${JSON.stringify(map, null, "\t")};
+${this.getFakeMapInitStatement(fakeMap, runtimeTemplate)}
 
 function webpackContext(req) {
-	var id = webpackContextResolve(req);
-	${returnModuleObject}
+	${cst} id = webpackContextResolve(req);
+	return ${returnModuleObject};
 }
 function webpackContextResolve(req) {
-	var id = map[req];
-	if(!(id + 1)) { // check for number or string
-		var e = new Error("Cannot find module '" + req + "'");
+	if(!${RuntimeGlobals.hasOwnProperty}(map, req)) {
+		${cst} e = new Error("Cannot find module '" + req + "'");
 		e.code = 'MODULE_NOT_FOUND';
 		throw e;
 	}
-	return id;
+	return map[req];
 }
 webpackContext.keys = function webpackContextKeys() {
 	return Object.keys(map);
@@ -388,31 +915,38 @@ module.exports = webpackContext;
 webpackContext.id = ${JSON.stringify(id)};`;
 	}
 
-	getWeakSyncSource(dependencies, id) {
-		const map = this.getUserRequestMap(dependencies);
-		const fakeMap = this.getFakeMap(dependencies);
+	/**
+	 * @param {Dependency[]} dependencies dependencies
+	 * @param {ModuleId} id module id
+	 * @param {ChunkGraph} chunkGraph the chunk graph
+	 * @param {RuntimeTemplate=} runtimeTemplate the runtime template
+	 * @returns {string} source code
+	 */
+	getWeakSyncSource(dependencies, id, chunkGraph, runtimeTemplate) {
+		const map = this.getUserRequestMap(dependencies, chunkGraph);
+		const fakeMap = this.getFakeMap(dependencies, chunkGraph);
 		const returnModuleObject = this.getReturnModuleObjectSource(fakeMap);
+		const cst = runtimeTemplate ? runtimeTemplate.renderConst() : "var";
 
-		return `var map = ${JSON.stringify(map, null, "\t")};
-${this.getFakeMapInitStatement(fakeMap)}
+		return `${cst} map = ${JSON.stringify(map, null, "\t")};
+${this.getFakeMapInitStatement(fakeMap, runtimeTemplate)}
 
 function webpackContext(req) {
-	var id = webpackContextResolve(req);
-	if(!__webpack_require__.m[id]) {
-		var e = new Error("Module '" + req + "' ('" + id + "') is not available (weak dependency)");
+	${cst} id = webpackContextResolve(req);
+	if(!${RuntimeGlobals.moduleFactories}[id]) {
+		${cst} e = new Error("Module '" + req + "' ('" + id + "') is not available (weak dependency)");
 		e.code = 'MODULE_NOT_FOUND';
 		throw e;
 	}
-	${returnModuleObject}
+	return ${returnModuleObject};
 }
 function webpackContextResolve(req) {
-	var id = map[req];
-	if(!(id + 1)) { // check for number or string
-		var e = new Error("Cannot find module '" + req + "'");
+	if(!${RuntimeGlobals.hasOwnProperty}(map, req)) {
+		${cst} e = new Error("Cannot find module '" + req + "'");
 		e.code = 'MODULE_NOT_FOUND';
 		throw e;
 	}
-	return id;
+	return map[req];
 }
 webpackContext.keys = function webpackContextKeys() {
 	return Object.keys(map);
@@ -422,56 +956,97 @@ webpackContext.id = ${JSON.stringify(id)};
 module.exports = webpackContext;`;
 	}
 
-	getAsyncWeakSource(dependencies, id) {
-		const map = this.getUserRequestMap(dependencies);
-		const fakeMap = this.getFakeMap(dependencies);
-		const returnModuleObject = this.getReturnModuleObjectSource(fakeMap);
+	/**
+	 * @param {Dependency[]} dependencies dependencies
+	 * @param {ModuleId} id module id
+	 * @param {ImportPhaseType} phase import phase
+	 * @param {object} context context
+	 * @param {ChunkGraph} context.chunkGraph the chunk graph
+	 * @param {RuntimeTemplate} context.runtimeTemplate the chunk graph
+	 * @returns {string} source code
+	 */
+	getAsyncWeakSource(dependencies, id, phase, { chunkGraph, runtimeTemplate }) {
+		const map = this.getUserRequestMap(dependencies, chunkGraph);
+		const fakeMap = this.getFakeMap(dependencies, chunkGraph);
+		const asyncDepsMap =
+			ImportPhaseUtils.isDefer(phase) &&
+			this.getModuleDeferredAsyncDepsMap(dependencies, chunkGraph);
+		const returnModuleObject = this.getReturnModuleObjectSource(
+			fakeMap,
+			true,
+			asyncDepsMap ? "asyncDepsMap[id]" : undefined
+		);
+		const cst = runtimeTemplate.renderConst();
 
-		return `var map = ${JSON.stringify(map, null, "\t")};
-${this.getFakeMapInitStatement(fakeMap)}
+		return `${cst} map = ${JSON.stringify(map, null, "\t")};
+${this.getFakeMapInitStatement(fakeMap, runtimeTemplate)}
+${this.getModuleDeferredAsyncDepsMapInitStatement(asyncDepsMap, runtimeTemplate)}
 
 function webpackAsyncContext(req) {
-	return webpackAsyncContextResolve(req).then(function(id) {
-		if(!__webpack_require__.m[id]) {
-			var e = new Error("Module '" + req + "' ('" + id + "') is not available (weak dependency)");
-			e.code = 'MODULE_NOT_FOUND';
-			throw e;
-		}
-		${returnModuleObject}
-	});
+	return webpackAsyncContextResolve(req).then(${runtimeTemplate.basicFunction(
+		"id",
+		[
+			`if(!${RuntimeGlobals.moduleFactories}[id]) {`,
+			Template.indent([
+				`${cst} e = new Error("Module '" + req + "' ('" + id + "') is not available (weak dependency)");`,
+				"e.code = 'MODULE_NOT_FOUND';",
+				"throw e;"
+			]),
+			"}",
+			`return ${returnModuleObject};`
+		]
+	)});
 }
 function webpackAsyncContextResolve(req) {
 	// Here Promise.resolve().then() is used instead of new Promise() to prevent
 	// uncaught exception popping up in devtools
-	return Promise.resolve().then(function() {
-		var id = map[req];
-		if(!(id + 1)) { // check for number or string
-			var e = new Error("Cannot find module '" + req + "'");
-			e.code = 'MODULE_NOT_FOUND';
-			throw e;
-		}
-		return id;
-	});
+	return Promise.resolve().then(${runtimeTemplate.basicFunction("", [
+		`if(!${RuntimeGlobals.hasOwnProperty}(map, req)) {`,
+		Template.indent([
+			`${cst} e = new Error("Cannot find module '" + req + "'");`,
+			"e.code = 'MODULE_NOT_FOUND';",
+			"throw e;"
+		]),
+		"}",
+		"return map[req];"
+	])});
 }
-webpackAsyncContext.keys = function webpackAsyncContextKeys() {
-	return Object.keys(map);
-};
+webpackAsyncContext.keys = ${runtimeTemplate.returningFunction(
+			"Object.keys(map)"
+		)};
 webpackAsyncContext.resolve = webpackAsyncContextResolve;
 webpackAsyncContext.id = ${JSON.stringify(id)};
 module.exports = webpackAsyncContext;`;
 	}
 
-	getEagerSource(dependencies, id) {
-		const map = this.getUserRequestMap(dependencies);
-		const fakeMap = this.getFakeMap(dependencies);
-		const thenFunction =
-			fakeMap !== 9
-				? `function(id) {
-		${this.getReturnModuleObjectSource(fakeMap)}
-	}`
-				: "__webpack_require__";
-		return `var map = ${JSON.stringify(map, null, "\t")};
-${this.getFakeMapInitStatement(fakeMap)}
+	/**
+	 * @param {Dependency[]} dependencies dependencies
+	 * @param {ModuleId} id module id
+	 * @param {ImportPhaseType} phase import phase
+	 * @param {object} context context
+	 * @param {ChunkGraph} context.chunkGraph the chunk graph
+	 * @param {RuntimeTemplate} context.runtimeTemplate the chunk graph
+	 * @returns {string} source code
+	 */
+	getEagerSource(dependencies, id, phase, { chunkGraph, runtimeTemplate }) {
+		const map = this.getUserRequestMap(dependencies, chunkGraph);
+		const fakeMap = this.getFakeMap(dependencies, chunkGraph);
+		const asyncDepsMap =
+			ImportPhaseUtils.isDefer(phase) &&
+			this.getModuleDeferredAsyncDepsMap(dependencies, chunkGraph);
+		const thenFunction = runtimeTemplate.returningFunction(
+			this.getReturnModuleObjectSource(
+				fakeMap,
+				true,
+				asyncDepsMap ? "asyncDepsMap[id]" : undefined
+			),
+			"id"
+		);
+
+		const cst = runtimeTemplate.renderConst();
+		return `${cst} map = ${JSON.stringify(map, null, "\t")};
+${this.getFakeMapInitStatement(fakeMap, runtimeTemplate)}
+${this.getModuleDeferredAsyncDepsMapInitStatement(asyncDepsMap, runtimeTemplate)}
 
 function webpackAsyncContext(req) {
 	return webpackAsyncContextResolve(req).then(${thenFunction});
@@ -479,273 +1054,659 @@ function webpackAsyncContext(req) {
 function webpackAsyncContextResolve(req) {
 	// Here Promise.resolve().then() is used instead of new Promise() to prevent
 	// uncaught exception popping up in devtools
-	return Promise.resolve().then(function() {
-		var id = map[req];
-		if(!(id + 1)) { // check for number or string
-			var e = new Error("Cannot find module '" + req + "'");
-			e.code = 'MODULE_NOT_FOUND';
-			throw e;
-		}
-		return id;
-	});
+	return Promise.resolve().then(${runtimeTemplate.basicFunction("", [
+		`if(!${RuntimeGlobals.hasOwnProperty}(map, req)) {`,
+		Template.indent([
+			`${cst} e = new Error("Cannot find module '" + req + "'");`,
+			"e.code = 'MODULE_NOT_FOUND';",
+			"throw e;"
+		]),
+		"}",
+		"return map[req];"
+	])});
 }
-webpackAsyncContext.keys = function webpackAsyncContextKeys() {
-	return Object.keys(map);
-};
+webpackAsyncContext.keys = ${runtimeTemplate.returningFunction(
+			"Object.keys(map)"
+		)};
 webpackAsyncContext.resolve = webpackAsyncContextResolve;
 webpackAsyncContext.id = ${JSON.stringify(id)};
 module.exports = webpackAsyncContext;`;
 	}
 
-	getLazyOnceSource(block, dependencies, id, runtimeTemplate) {
+	/**
+	 * @param {AsyncDependenciesBlock} block block
+	 * @param {Dependency[]} dependencies dependencies
+	 * @param {ModuleId} id module id
+	 * @param {ImportPhaseType} phase import phase
+	 * @param {object} options options object
+	 * @param {RuntimeTemplate} options.runtimeTemplate the runtime template
+	 * @param {ChunkGraph} options.chunkGraph the chunk graph
+	 * @param {RuntimeRequirements} options.runtimeRequirements runtime requirements
+	 * @returns {string} source code
+	 */
+	getLazyOnceSource(
+		block,
+		dependencies,
+		id,
+		phase,
+		{ runtimeTemplate, chunkGraph, runtimeRequirements }
+	) {
 		const promise = runtimeTemplate.blockPromise({
+			chunkGraph,
 			block,
-			message: "lazy-once context"
+			message: "lazy-once context",
+			runtimeRequirements,
+			originModule: this
 		});
-		const map = this.getUserRequestMap(dependencies);
-		const fakeMap = this.getFakeMap(dependencies);
-		const thenFunction =
-			fakeMap !== 9
-				? `function(id) {
-		${this.getReturnModuleObjectSource(fakeMap)};
-	}`
-				: "__webpack_require__";
-
-		return `var map = ${JSON.stringify(map, null, "\t")};
-${this.getFakeMapInitStatement(fakeMap)}
+		const map = this.getUserRequestMap(dependencies, chunkGraph);
+		const fakeMap = this.getFakeMap(dependencies, chunkGraph);
+		const asyncDepsMap =
+			ImportPhaseUtils.isDefer(phase) &&
+			this.getModuleDeferredAsyncDepsMap(dependencies, chunkGraph);
+		const thenFunction = runtimeTemplate.returningFunction(
+			this.getReturnModuleObjectSource(
+				fakeMap,
+				true,
+				asyncDepsMap ? "asyncDepsMap[id]" : undefined
+			),
+			"id"
+		);
+
+		const cst = runtimeTemplate.renderConst();
+		return `${cst} map = ${JSON.stringify(map, null, "\t")};
+${this.getFakeMapInitStatement(fakeMap, runtimeTemplate)}
+${this.getModuleDeferredAsyncDepsMapInitStatement(asyncDepsMap, runtimeTemplate)}
 
 function webpackAsyncContext(req) {
 	return webpackAsyncContextResolve(req).then(${thenFunction});
 }
 function webpackAsyncContextResolve(req) {
-	return ${promise}.then(function() {
-		var id = map[req];
-		if(!(id + 1)) { // check for number or string
-			var e = new Error("Cannot find module '" + req + "'");
-			e.code = 'MODULE_NOT_FOUND';
-			throw e;
-		}
-		return id;
-	});
+	return ${promise}.then(${runtimeTemplate.basicFunction("", [
+		`if(!${RuntimeGlobals.hasOwnProperty}(map, req)) {`,
+		Template.indent([
+			`${cst} e = new Error("Cannot find module '" + req + "'");`,
+			"e.code = 'MODULE_NOT_FOUND';",
+			"throw e;"
+		]),
+		"}",
+		"return map[req];"
+	])});
 }
-webpackAsyncContext.keys = function webpackAsyncContextKeys() {
-	return Object.keys(map);
-};
+webpackAsyncContext.keys = ${runtimeTemplate.returningFunction(
+			"Object.keys(map)"
+		)};
 webpackAsyncContext.resolve = webpackAsyncContextResolve;
 webpackAsyncContext.id = ${JSON.stringify(id)};
 module.exports = webpackAsyncContext;`;
 	}
 
-	getLazySource(blocks, id) {
+	/**
+	 * @param {AsyncDependenciesBlock[]} blocks blocks
+	 * @param {ModuleId} id module id
+	 * @param {ImportPhaseType} phase import phase
+	 * @param {object} context context
+	 * @param {ChunkGraph} context.chunkGraph the chunk graph
+	 * @param {RuntimeTemplate} context.runtimeTemplate the chunk graph
+	 * @param {RuntimeRequirements} context.runtimeRequirements runtime requirements
+	 * @returns {string} source code
+	 */
+	getLazySource(
+		blocks,
+		id,
+		phase,
+		{ chunkGraph, runtimeTemplate, runtimeRequirements }
+	) {
+		const moduleGraph = chunkGraph.moduleGraph;
 		let hasMultipleOrNoChunks = false;
-		const fakeMap = this.getFakeMap(blocks.map(b => b.dependencies[0]));
-		const map = blocks
-			.filter(block => block.dependencies[0].module)
-			.map(block => ({
-				dependency: block.dependencies[0],
-				block: block,
-				userRequest: block.dependencies[0].userRequest
-			}))
-			.sort((a, b) => {
-				if (a.userRequest === b.userRequest) return 0;
-				return a.userRequest < b.userRequest ? -1 : 1;
+		let hasNoChunk = true;
+		let hasNoModuleDeferred = true;
+		const fakeMap = this.getFakeMap(
+			blocks.map((b) => b.dependencies[0]),
+			chunkGraph
+		);
+		const hasFakeMap = typeof fakeMap === "object";
+		/** @typedef {{ userRequest: string, dependency: ContextElementDependency, chunks: undefined | Chunk[], module: Module, block: AsyncDependenciesBlock, asyncDeps: undefined | ModuleId[] }} Item */
+		/**
+		 * @type {Item[]}
+		 */
+		const items = blocks
+			.map((block) => {
+				const dependency =
+					/** @type {ContextElementDependency} */
+					(block.dependencies[0]);
+				return {
+					dependency,
+					module: /** @type {Module} */ (moduleGraph.getModule(dependency)),
+					block,
+					userRequest: dependency.userRequest,
+					chunks: undefined,
+					asyncDeps: undefined
+				};
 			})
-			.reduce((map, item) => {
-				const chunks =
-					(item.block.chunkGroup && item.block.chunkGroup.chunks) || [];
-				if (chunks.length !== 1) {
-					hasMultipleOrNoChunks = true;
+			.filter((item) => item.module);
+		for (const item of items) {
+			const chunkGroup = chunkGraph.getBlockChunkGroup(item.block);
+			const chunks = (chunkGroup && chunkGroup.chunks) || [];
+			item.chunks = chunks;
+			if (chunks.length > 0) {
+				hasNoChunk = false;
+			}
+			if (chunks.length !== 1) {
+				hasMultipleOrNoChunks = true;
+			}
+			const isModuleDeferred =
+				ImportPhaseUtils.isDefer(phase) &&
+				!(/** @type {BuildMeta} */ (item.module.buildMeta).async);
+			if (isModuleDeferred) {
+				const asyncDeps = Array.from(
+					getOutgoingAsyncModules(chunkGraph.moduleGraph, item.module),
+					(m) => chunkGraph.getModuleId(m)
+				).filter((id) => id !== null);
+				item.asyncDeps = asyncDeps;
+				hasNoModuleDeferred = false;
+			}
+		}
+		const shortMode = hasNoChunk && hasNoModuleDeferred && !hasFakeMap;
+		const sortedItems = items.sort((a, b) => {
+			if (a.userRequest === b.userRequest) return 0;
+			return a.userRequest < b.userRequest ? -1 : 1;
+		});
+		/** @type {Record} */
+		const map = Object.create(null);
+		for (const item of sortedItems) {
+			const moduleId =
+				/** @type {ModuleId} */
+				(chunkGraph.getModuleId(item.module));
+			if (shortMode) {
+				map[item.userRequest] = moduleId;
+			} else {
+				/** @type {(ModuleId | FakeMapType | ChunkId[] | (ModuleId[] | undefined))[]} */
+				const array = [moduleId];
+				if (hasFakeMap) {
+					array.push(fakeMap[moduleId]);
 				}
-				const arrayStart = [item.dependency.module.id];
-				if (typeof fakeMap === "object") {
-					arrayStart.push(fakeMap[item.dependency.module.id]);
+				if (!hasNoChunk) {
+					array.push(
+						/** @type {Chunk[]} */ (item.chunks).map(
+							(chunk) => /** @type {ChunkId} */ (chunk.id)
+						)
+					);
 				}
-				map[item.userRequest] = arrayStart.concat(
-					chunks.map(chunk => chunk.id)
-				);
-
-				return map;
-			}, Object.create(null));
+				if (!hasNoModuleDeferred) {
+					array.push(item.asyncDeps);
+				}
+				map[item.userRequest] = array;
+			}
+		}
 
-		const chunksStartPosition = typeof fakeMap === "object" ? 2 : 1;
-		const requestPrefix = hasMultipleOrNoChunks
-			? `Promise.all(ids.slice(${chunksStartPosition}).map(__webpack_require__.e))`
-			: `__webpack_require__.e(ids[${chunksStartPosition}])`;
+		const chunksPosition = hasFakeMap ? 2 : 1;
+		// No chunks slot is pushed when `hasNoChunk`, so `asyncDeps` sits at `chunksPosition`.
+		const asyncDepsPosition = hasNoChunk ? chunksPosition : chunksPosition + 1;
+		const requestPrefix = hasNoChunk
+			? "Promise.resolve()"
+			: hasMultipleOrNoChunks
+				? `Promise.all(ids[${chunksPosition}].map(${RuntimeGlobals.ensureChunk}))`
+				: `${RuntimeGlobals.ensureChunk}(ids[${chunksPosition}][0])`;
+		// Only the id map reads it; nothing loads a chunk when there is none to load.
+		if (!hasNoChunk) {
+			runtimeRequirements.add(RuntimeGlobals.ensureChunk);
+		}
 		const returnModuleObject = this.getReturnModuleObjectSource(
 			fakeMap,
-			"ids[1]"
+			true,
+			hasNoModuleDeferred ? undefined : `ids[${asyncDepsPosition}]`,
+			shortMode ? "invalid" : "ids[1]"
 		);
 
-		return `var map = ${JSON.stringify(map, null, "\t")};
+		const cst = runtimeTemplate.renderConst();
+		const webpackAsyncContext =
+			requestPrefix === "Promise.resolve()"
+				? `
 function webpackAsyncContext(req) {
-	var ids = map[req];
-	if(!ids) {
-		return Promise.resolve().then(function() {
-			var e = new Error("Cannot find module '" + req + "'");
-			e.code = 'MODULE_NOT_FOUND';
-			throw e;
-		});
+	return Promise.resolve().then(${runtimeTemplate.basicFunction("", [
+		`if(!${RuntimeGlobals.hasOwnProperty}(map, req)) {`,
+		Template.indent([
+			`${cst} e = new Error("Cannot find module '" + req + "'");`,
+			"e.code = 'MODULE_NOT_FOUND';",
+			"throw e;"
+		]),
+		"}",
+		shortMode ? `${cst} id = map[req];` : `${cst} ids = map[req], id = ids[0];`,
+		`return ${returnModuleObject};`
+	])});
+}`
+				: `function webpackAsyncContext(req) {
+	try {
+		if(!${RuntimeGlobals.hasOwnProperty}(map, req)) {
+			return Promise.resolve().then(${runtimeTemplate.basicFunction("", [
+				`${cst} e = new Error("Cannot find module '" + req + "'");`,
+				"e.code = 'MODULE_NOT_FOUND';",
+				"throw e;"
+			])});
+		}
+	} catch(err) {
+		return Promise.reject(err);
 	}
-	return ${requestPrefix}.then(function() {
-		var id = ids[0];
-		${returnModuleObject}
-	});
-}
-webpackAsyncContext.keys = function webpackAsyncContextKeys() {
-	return Object.keys(map);
-};
+
+	${cst} ids = map[req], id = ids[0];
+	return ${requestPrefix}.then(${runtimeTemplate.returningFunction(returnModuleObject)});
+}`;
+
+		return `${cst} map = ${JSON.stringify(map, null, "\t")};
+${webpackAsyncContext}
+webpackAsyncContext.keys = ${runtimeTemplate.returningFunction(
+			"Object.keys(map)"
+		)};
 webpackAsyncContext.id = ${JSON.stringify(id)};
 module.exports = webpackAsyncContext;`;
 	}
 
-	getSourceForEmptyContext(id) {
+	/**
+	 * @param {ModuleId} id module id
+	 * @param {RuntimeTemplate} runtimeTemplate runtime template
+	 * @returns {string} source for empty async context
+	 */
+	getSourceForEmptyContext(id, runtimeTemplate) {
 		return `function webpackEmptyContext(req) {
-	var e = new Error("Cannot find module '" + req + "'");
+	${runtimeTemplate.renderConst()} e = new Error("Cannot find module '" + req + "'");
 	e.code = 'MODULE_NOT_FOUND';
 	throw e;
 }
-webpackEmptyContext.keys = function() { return []; };
+webpackEmptyContext.keys = ${runtimeTemplate.returningFunction("[]")};
 webpackEmptyContext.resolve = webpackEmptyContext;
-module.exports = webpackEmptyContext;
-webpackEmptyContext.id = ${JSON.stringify(id)};`;
+webpackEmptyContext.id = ${JSON.stringify(id)};
+module.exports = webpackEmptyContext;`;
 	}
 
-	getSourceForEmptyAsyncContext(id) {
+	/**
+	 * @param {ModuleId} id module id
+	 * @param {RuntimeTemplate} runtimeTemplate runtime template
+	 * @returns {string} source for empty async context
+	 */
+	getSourceForEmptyAsyncContext(id, runtimeTemplate) {
 		return `function webpackEmptyAsyncContext(req) {
 	// Here Promise.resolve().then() is used instead of new Promise() to prevent
 	// uncaught exception popping up in devtools
-	return Promise.resolve().then(function() {
-		var e = new Error("Cannot find module '" + req + "'");
-		e.code = 'MODULE_NOT_FOUND';
-		throw e;
-	});
+	return Promise.resolve().then(${runtimeTemplate.basicFunction("", [
+		`${runtimeTemplate.renderConst()} e = new Error("Cannot find module '" + req + "'");`,
+		"e.code = 'MODULE_NOT_FOUND';",
+		"throw e;"
+	])});
 }
-webpackEmptyAsyncContext.keys = function() { return []; };
+webpackEmptyAsyncContext.keys = ${runtimeTemplate.returningFunction("[]")};
 webpackEmptyAsyncContext.resolve = webpackEmptyAsyncContext;
-module.exports = webpackEmptyAsyncContext;
-webpackEmptyAsyncContext.id = ${JSON.stringify(id)};`;
+webpackEmptyAsyncContext.id = ${JSON.stringify(id)};
+module.exports = webpackEmptyAsyncContext;`;
+	}
+
+	/**
+	 * @returns {string | undefined} import property access
+	 */
+	getImportPropertyAccess() {
+		const importName = this.options.importName;
+		if (!importName || importName === "*") return;
+		return propertyAccess([importName]);
+	}
+
+	/**
+	 * @param {number} index index
+	 * @param {string} userRequest user request
+	 * @param {string} value value expression
+	 * @param {string[]} entries entries
+	 * @returns {void}
+	 */
+	appendGlobMapEntry(index, userRequest, value, entries) {
+		entries.push(`\n  ${JSON.stringify(userRequest)}: ${value}`);
+	}
+
+	/**
+	 * @param {string} entries entries
+	 * @returns {string} object export source
+	 */
+	getGlobObjectExportSource(entries) {
+		return `\nmodule.exports = {${entries}\n};\n`;
+	}
+
+	/**
+	 * @returns {string} empty object export source
+	 */
+	getEmptyObjectExportSource() {
+		return "\nmodule.exports = {};\n";
 	}
 
-	getSourceString(asyncMode, runtimeTemplate) {
+	/**
+	 * @param {AsyncDependenciesBlock[]} blocks blocks
+	 * @param {ImportPhaseType} phase import phase
+	 * @param {object} context context
+	 * @param {RuntimeTemplate} context.runtimeTemplate runtime template
+	 * @param {ChunkGraph} context.chunkGraph chunk graph
+	 * @param {RuntimeRequirements=} context.runtimeRequirements runtime requirements
+	 * @returns {string} glob lazy source
+	 */
+	getGlobLazySource(
+		blocks,
+		phase,
+		{ runtimeTemplate, chunkGraph, runtimeRequirements }
+	) {
+		if (!blocks || blocks.length === 0) {
+			return this.getEmptyObjectExportSource();
+		}
+		const moduleGraph = chunkGraph.moduleGraph;
+		const globImportAccess = this.getImportPropertyAccess();
+		/** @type {RuntimeRequirements} */
+		const localRuntimeRequirements = runtimeRequirements || new Set();
+		/** @type {{ userRequest: string, block: AsyncDependenciesBlock, dependency: ContextElementDependency }[]} */
+		const blockInfo = blocks
+			.map((block) => {
+				const dependency =
+					/** @type {ContextElementDependency} */
+					(block.dependencies[0]);
+				return {
+					userRequest: dependency.userRequest,
+					block,
+					dependency
+				};
+			})
+			.sort((a, b) =>
+				a.userRequest < b.userRequest
+					? -1
+					: a.userRequest > b.userRequest
+						? 1
+						: 0
+			);
+		/** @type {string[]} */
+		const entries = [];
+		for (let i = 0; i < blockInfo.length; i++) {
+			const info = blockInfo[i];
+			let importPromise = runtimeTemplate.moduleNamespacePromise({
+				chunkGraph,
+				block: info.block,
+				module: /** @type {Module} */ (moduleGraph.getModule(info.dependency)),
+				request: info.userRequest,
+				message: "import.meta.glob",
+				strict: this.options.namespaceObject === "strict",
+				dependency: info.dependency,
+				runtimeRequirements: localRuntimeRequirements,
+				originModule: this
+			});
+			if (globImportAccess) {
+				importPromise = `${importPromise}.then(function(m) { return m${globImportAccess}; })`;
+			}
+			this.appendGlobMapEntry(
+				i,
+				info.userRequest,
+				`function() { return ${importPromise}; }`,
+				entries
+			);
+		}
+		return this.getGlobObjectExportSource(entries.join(","));
+	}
+
+	/**
+	 * @param {Dependency[]} dependencies dependencies
+	 * @param {ChunkGraph} chunkGraph chunk graph
+	 * @param {RuntimeTemplate} runtimeTemplate runtime template
+	 * @returns {string} glob sync source
+	 */
+	getGlobSyncSource(dependencies, chunkGraph, runtimeTemplate) {
+		if (!dependencies || dependencies.length === 0) {
+			return this.getEmptyObjectExportSource();
+		}
+		const moduleGraph = chunkGraph.moduleGraph;
+		const fakeMap = this.getFakeMap(dependencies, chunkGraph);
+		const returnModuleObject = this.getReturnModuleObjectSource(fakeMap);
+		const globImportAccess = this.getImportPropertyAccess();
+		const sortedDependencies =
+			/** @type {import("./dependencies/ContextElementDependency")[]} */
+			(dependencies).sort((a, b) =>
+				a.userRequest < b.userRequest
+					? -1
+					: a.userRequest > b.userRequest
+						? 1
+						: 0
+			);
+		/** @type {string[]} */
+		const entries = [];
+		let index = 0;
+		for (const dep of sortedDependencies) {
+			const module = moduleGraph.getModule(dep);
+			let moduleExpr;
+			if (module) {
+				const moduleId = chunkGraph.getModuleId(module);
+				moduleExpr = `(function() { var id = ${JSON.stringify(moduleId)}; return ${returnModuleObject}; })()`;
+				if (globImportAccess) {
+					moduleExpr += globImportAccess;
+				}
+			} else {
+				moduleExpr = `undefined /* ${JSON.stringify(dep.userRequest)} */`;
+			}
+			this.appendGlobMapEntry(index++, dep.userRequest, moduleExpr, entries);
+		}
+		const fakeMapInit = this.getFakeMapInitStatement(fakeMap, runtimeTemplate);
+		return `${fakeMapInit}\n${this.getGlobObjectExportSource(entries.join(","))}`;
+	}
+
+	/**
+	 * @param {string} asyncMode module mode
+	 * @param {ImportPhaseType} phase import phase
+	 * @param {CodeGenerationContext} context context info
+	 * @returns {string} the source code
+	 */
+	getGlobSourceString(asyncMode, phase, context) {
 		if (asyncMode === "lazy") {
 			if (this.blocks && this.blocks.length > 0) {
-				return this.getLazySource(this.blocks, this.id);
+				return this.getGlobLazySource(this.blocks, phase, context);
 			}
-			return this.getSourceForEmptyAsyncContext(this.id);
+			return this.getEmptyObjectExportSource();
+		}
+		if (this.dependencies && this.dependencies.length > 0) {
+			return this.getGlobSyncSource(
+				this.dependencies,
+				context.chunkGraph,
+				context.runtimeTemplate
+			);
+		}
+		return this.getEmptyObjectExportSource();
+	}
+
+	/**
+	 * @param {string} asyncMode module mode
+	 * @param {ImportPhaseType} phase import phase
+	 * @param {CodeGenerationContext & { runtimeRequirements: RuntimeRequirements }} context context info
+	 * @returns {string} the source code
+	 */
+	getSourceString(asyncMode, phase, context) {
+		if (this.options.patterns) {
+			return this.getGlobSourceString(asyncMode, phase, context);
+		}
+		return this.getContextSourceString(asyncMode, phase, context);
+	}
+
+	/**
+	 * @param {string} asyncMode module mode
+	 * @param {ImportPhaseType} phase import phase
+	 * @param {CodeGenerationContext & { runtimeRequirements: RuntimeRequirements }} context context info
+	 * @returns {string} the source code
+	 */
+	getContextSourceString(asyncMode, phase, context) {
+		const { runtimeTemplate, chunkGraph, runtimeRequirements } = context;
+		const id = /** @type {ModuleId} */ (chunkGraph.getModuleId(this));
+		if (asyncMode === "lazy") {
+			if (this.blocks && this.blocks.length > 0) {
+				return this.getLazySource(this.blocks, id, phase, {
+					runtimeTemplate,
+					chunkGraph,
+					runtimeRequirements
+				});
+			}
+			return this.getSourceForEmptyAsyncContext(id, runtimeTemplate);
 		}
 		if (asyncMode === "eager") {
 			if (this.dependencies && this.dependencies.length > 0) {
-				return this.getEagerSource(this.dependencies, this.id);
+				return this.getEagerSource(this.dependencies, id, phase, {
+					chunkGraph,
+					runtimeTemplate
+				});
 			}
-			return this.getSourceForEmptyAsyncContext(this.id);
+			return this.getSourceForEmptyAsyncContext(id, runtimeTemplate);
 		}
 		if (asyncMode === "lazy-once") {
 			const block = this.blocks[0];
 			if (block) {
-				return this.getLazyOnceSource(
-					block,
-					block.dependencies,
-					this.id,
-					runtimeTemplate
-				);
+				return this.getLazyOnceSource(block, block.dependencies, id, phase, {
+					runtimeTemplate,
+					chunkGraph,
+					runtimeRequirements
+				});
 			}
-			return this.getSourceForEmptyAsyncContext(this.id);
+			return this.getSourceForEmptyAsyncContext(id, runtimeTemplate);
 		}
 		if (asyncMode === "async-weak") {
 			if (this.dependencies && this.dependencies.length > 0) {
-				return this.getAsyncWeakSource(this.dependencies, this.id);
+				return this.getAsyncWeakSource(this.dependencies, id, phase, {
+					chunkGraph,
+					runtimeTemplate
+				});
 			}
-			return this.getSourceForEmptyAsyncContext(this.id);
+			return this.getSourceForEmptyAsyncContext(id, runtimeTemplate);
 		}
-		if (asyncMode === "weak") {
-			if (this.dependencies && this.dependencies.length > 0) {
-				return this.getWeakSyncSource(this.dependencies, this.id);
-			}
+		if (
+			asyncMode === "weak" &&
+			this.dependencies &&
+			this.dependencies.length > 0
+		) {
+			return this.getWeakSyncSource(
+				this.dependencies,
+				id,
+				chunkGraph,
+				runtimeTemplate
+			);
 		}
 		if (this.dependencies && this.dependencies.length > 0) {
-			return this.getSyncSource(this.dependencies, this.id);
+			return this.getSyncSource(
+				this.dependencies,
+				id,
+				chunkGraph,
+				runtimeTemplate
+			);
 		}
-		return this.getSourceForEmptyContext(this.id);
+		return this.getSourceForEmptyContext(id, runtimeTemplate);
 	}
 
-	getSource(sourceString) {
-		if (this.useSourceMap) {
-			return new OriginalSource(sourceString, this.identifier());
+	/**
+	 * @param {string} sourceString source content
+	 * @param {Compilation=} compilation the compilation
+	 * @returns {Source} generated source
+	 */
+	getSource(sourceString, compilation) {
+		if (this.useSourceMap || this.useSimpleSourceMap) {
+			return new OriginalSource(
+				sourceString,
+				`webpack://${makePathsRelative(
+					(compilation && compilation.compiler.context) || "",
+					this.identifier(),
+					compilation && compilation.compiler.root
+				)}`
+			);
 		}
 		return new RawSource(sourceString);
 	}
 
-	source(dependencyTemplates, runtimeTemplate) {
-		return this.getSource(
-			this.getSourceString(this.options.mode, runtimeTemplate)
+	/**
+	 * Generates code and runtime requirements for this module.
+	 * @param {CodeGenerationContext} context context for code generation
+	 * @returns {CodeGenerationResult} result
+	 */
+	codeGeneration(context) {
+		const { chunkGraph, compilation } = context;
+
+		/** @type {RuntimeRequirements} */
+		const set = new Set();
+		/** @type {Sources} */
+		const sources = new Map();
+		sources.set(
+			JAVASCRIPT_TYPE,
+			this.getSource(
+				this.getSourceString(
+					this.options.mode,
+					this.options.phase || ImportPhase.Evaluation,
+					{ ...context, runtimeRequirements: set }
+				),
+				compilation
+			)
 		);
+		const allDeps =
+			this.dependencies.length > 0
+				? /** @type {ContextElementDependency[]} */ [...this.dependencies]
+				: [];
+		for (const block of this.blocks) {
+			for (const dep of block.dependencies) {
+				allDeps.push(/** @type {ContextElementDependency} */ (dep));
+			}
+		}
+		set.add(RuntimeGlobals.module);
+		set.add(RuntimeGlobals.hasOwnProperty);
+		if (allDeps.length > 0) {
+			const asyncMode = this.options.mode;
+			set.add(RuntimeGlobals.require);
+			// `ensureChunk` is not listed per mode: the source generators above add it
+			// where they emit a call, so a baked context or one with no chunk drops it.
+			if (asyncMode === "weak" || asyncMode === "async-weak") {
+				set.add(RuntimeGlobals.moduleFactories);
+			}
+			if (this.getFakeMap(allDeps, chunkGraph) !== 9) {
+				set.add(RuntimeGlobals.createFakeNamespaceObject);
+			}
+			if (
+				ImportPhaseUtils.isDefer(this.options.phase || ImportPhase.Evaluation)
+			) {
+				set.add(RuntimeGlobals.makeDeferredNamespaceObject);
+			}
+		}
+		return {
+			sources,
+			runtimeRequirements: set
+		};
 	}
 
-	size() {
+	/**
+	 * Returns the estimated size for the requested source type.
+	 * @param {string=} type the source type for which the size should be estimated
+	 * @returns {number} the estimated size of the module (must be non-zero)
+	 */
+	size(type) {
 		// base penalty
-		const initialSize = 160;
+		let size = 160;
 
-		// if we dont have dependencies we stop here.
-		return this.dependencies.reduce((size, dependency) => {
+		// if we don't have dependencies we stop here.
+		for (const dependency of this.dependencies) {
 			const element = /** @type {ContextElementDependency} */ (dependency);
-			return size + 5 + element.userRequest.length;
-		}, initialSize);
+			size += 5 + element.userRequest.length;
+		}
+		return size;
+	}
+
+	/**
+	 * Serializes this instance into the provided serializer context.
+	 * @param {ObjectSerializerContext} context context
+	 */
+	serialize(context) {
+		context.write(this._identifier).write(this._forceBuild);
+		super.serialize(context);
+	}
+
+	/**
+	 * Restores this instance from the provided deserializer context.
+	 * @param {ObjectDeserializerContext} context context
+	 */
+	deserialize(context) {
+		this._identifier = context.read();
+		const c1 = context.rest;
+		this._forceBuild = c1.read();
+		super.deserialize(c1.rest);
 	}
 }
 
-// TODO remove in webpack 5
-Object.defineProperty(ContextModule.prototype, "recursive", {
-	configurable: false,
-	get: util.deprecate(function() {
-		return this.options.recursive;
-	}, "ContextModule.recursive has been moved to ContextModule.options.recursive"),
-	set: util.deprecate(function(value) {
-		this.options.recursive = value;
-	}, "ContextModule.recursive has been moved to ContextModule.options.recursive")
-});
-
-// TODO remove in webpack 5
-Object.defineProperty(ContextModule.prototype, "regExp", {
-	configurable: false,
-	get: util.deprecate(function() {
-		return this.options.regExp;
-	}, "ContextModule.regExp has been moved to ContextModule.options.regExp"),
-	set: util.deprecate(function(value) {
-		this.options.regExp = value;
-	}, "ContextModule.regExp has been moved to ContextModule.options.regExp")
-});
-
-// TODO remove in webpack 5
-Object.defineProperty(ContextModule.prototype, "addon", {
-	configurable: false,
-	get: util.deprecate(function() {
-		return this.options.addon;
-	}, "ContextModule.addon has been moved to ContextModule.options.addon"),
-	set: util.deprecate(function(value) {
-		this.options.addon = value;
-	}, "ContextModule.addon has been moved to ContextModule.options.addon")
-});
-
-// TODO remove in webpack 5
-Object.defineProperty(ContextModule.prototype, "async", {
-	configurable: false,
-	get: util.deprecate(function() {
-		return this.options.mode;
-	}, "ContextModule.async has been moved to ContextModule.options.mode"),
-	set: util.deprecate(function(value) {
-		this.options.mode = value;
-	}, "ContextModule.async has been moved to ContextModule.options.mode")
-});
-
-// TODO remove in webpack 5
-Object.defineProperty(ContextModule.prototype, "chunkName", {
-	configurable: false,
-	get: util.deprecate(function() {
-		return this.options.chunkName;
-	}, "ContextModule.chunkName has been moved to ContextModule.options.chunkName"),
-	set: util.deprecate(function(value) {
-		this.options.chunkName = value;
-	}, "ContextModule.chunkName has been moved to ContextModule.options.chunkName")
-});
+makeSerializable(ContextModule, "webpack/lib/ContextModule");
 
 module.exports = ContextModule;
diff --git a/lib/ContextModuleFactory.js b/lib/ContextModuleFactory.js
index 2a52a58f058..337c1aa72fb 100644
--- a/lib/ContextModuleFactory.js
+++ b/lib/ContextModuleFactory.js
@@ -2,72 +2,175 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
 const asyncLib = require("neo-async");
-const path = require("path");
-
-const {
-	Tapable,
-	AsyncSeriesWaterfallHook,
-	SyncWaterfallHook
-} = require("tapable");
+const { AsyncSeriesWaterfallHook, SyncWaterfallHook } = require("tapable");
 const ContextModule = require("./ContextModule");
+const ModuleFactory = require("./ModuleFactory");
 const ContextElementDependency = require("./dependencies/ContextElementDependency");
+const LazySet = require("./util/LazySet");
+const { cachedSetProperty } = require("./util/cleverMerge");
+const { createFakeHook } = require("./util/deprecation");
+const { join } = require("./util/fs");
+const {
+	globPatternBaseReachesDir,
+	globUserRequest,
+	isNonExhaustiveImportMetaGlobSkippedDir,
+	resolveContextModuleGlobPattern
+} = require("./util/globUtils");
+
+/** @typedef {import("./util/globUtils").ResolvedContextModuleGlobPattern} ResolvedContextModuleGlobPattern */
+/** @typedef {(context: string, subResource: string, callback: () => void, resolvedGlobPatterns?: ResolvedContextModuleGlobPattern[]) => void} AddSubDirectoryFn */
+/** @typedef {import("enhanced-resolve").ResolveRequest} ResolveRequest */
+/** @typedef {import("./Compilation").FileSystemDependencies} FileSystemDependencies */
+/** @typedef {import("./ContextModule").ContextModuleOptions} ContextModuleOptions */
+/** @typedef {import("./ContextModule").ResolveDependenciesCallback} ResolveDependenciesCallback */
+/** @typedef {import("./ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */
+/** @typedef {import("./ModuleFactory").ModuleFactoryCallback} ModuleFactoryCallback */
+/** @typedef {import("./ResolverFactory")} ResolverFactory */
+/** @typedef {import("./dependencies/ContextDependency")} ContextDependency */
+/** @typedef {import("./dependencies/ContextDependency").ContextOptions} ContextOptions */
+
+/**
+ * Defines the shared type used by this module.
+ * @template T
+ * @typedef {import("./util/deprecation").FakeHook} FakeHook
+ */
+/** @typedef {import("./util/fs").IStats} IStats */
+/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
+/** @typedef {{ context: string, request: string }} ContextAlternativeRequest */
+
+/**
+ * Defines the context resolve data type used by this module.
+ * @typedef {object} ContextResolveData
+ * @property {string} context
+ * @property {string} request
+ * @property {ModuleFactoryCreateData["resolveOptions"]} resolveOptions
+ * @property {FileSystemDependencies} fileDependencies
+ * @property {FileSystemDependencies} missingDependencies
+ * @property {FileSystemDependencies} contextDependencies
+ * @property {ContextDependency[]} dependencies
+ */
+
+/** @typedef {ContextResolveData & ContextOptions} BeforeContextResolveData */
+/** @typedef {BeforeContextResolveData & { resource: string | string[], resourceQuery: string | undefined, resourceFragment: string | undefined, resolveDependencies: ContextModuleFactory["resolveDependencies"] }} AfterContextResolveData */
 
 const EMPTY_RESOLVE_OPTIONS = {};
 
-module.exports = class ContextModuleFactory extends Tapable {
+class ContextModuleFactory extends ModuleFactory {
+	/**
+	 * Creates an instance of ContextModuleFactory.
+	 * @param {ResolverFactory} resolverFactory resolverFactory
+	 */
 	constructor(resolverFactory) {
 		super();
-		this.hooks = {
+		/** @type {AsyncSeriesWaterfallHook<[ContextAlternativeRequest[], ContextModuleOptions]>} */
+		const alternativeRequests = new AsyncSeriesWaterfallHook([
+			"modules",
+			"options"
+		]);
+		this.hooks = Object.freeze({
+			/** @type {AsyncSeriesWaterfallHook<[BeforeContextResolveData], BeforeContextResolveData | false | void>} */
 			beforeResolve: new AsyncSeriesWaterfallHook(["data"]),
+			/** @type {AsyncSeriesWaterfallHook<[AfterContextResolveData], AfterContextResolveData | false | void>} */
 			afterResolve: new AsyncSeriesWaterfallHook(["data"]),
+			/** @type {SyncWaterfallHook<[string[]]>} */
 			contextModuleFiles: new SyncWaterfallHook(["files"]),
-			alternatives: new AsyncSeriesWaterfallHook(["modules"])
-		};
-		this._pluginCompat.tap("ContextModuleFactory", options => {
-			switch (options.name) {
-				case "before-resolve":
-				case "after-resolve":
-				case "alternatives":
-					options.async = true;
-					break;
-			}
+			/** @type {FakeHook, "tap" | "tapAsync" | "tapPromise" | "name">>} */
+			alternatives: createFakeHook(
+				{
+					name: "alternatives",
+					/** @type {AsyncSeriesWaterfallHook<[ContextAlternativeRequest[]]>["intercept"]} */
+					intercept: (interceptor) => {
+						throw new Error(
+							"Intercepting fake hook ContextModuleFactory.hooks.alternatives is not possible, use ContextModuleFactory.hooks.alternativeRequests instead"
+						);
+					},
+					/** @type {AsyncSeriesWaterfallHook<[ContextAlternativeRequest[]]>["tap"]} */
+					tap: (options, fn) => {
+						alternativeRequests.tap(options, fn);
+					},
+					/** @type {AsyncSeriesWaterfallHook<[ContextAlternativeRequest[]]>["tapAsync"]} */
+					tapAsync: (options, fn) => {
+						alternativeRequests.tapAsync(options, (items, _options, callback) =>
+							fn(items, callback)
+						);
+					},
+					/** @type {AsyncSeriesWaterfallHook<[ContextAlternativeRequest[]]>["tapPromise"]} */
+					tapPromise: (options, fn) => {
+						alternativeRequests.tapPromise(options, fn);
+					}
+				},
+				"ContextModuleFactory.hooks.alternatives has deprecated in favor of ContextModuleFactory.hooks.alternativeRequests with an additional options argument.",
+				"DEP_WEBPACK_CONTEXT_MODULE_FACTORY_ALTERNATIVES"
+			),
+			alternativeRequests
 		});
+		/** @type {ResolverFactory} */
 		this.resolverFactory = resolverFactory;
 	}
 
+	/**
+	 * Processes the provided data.
+	 * @param {ModuleFactoryCreateData} data data object
+	 * @param {ModuleFactoryCallback} callback callback
+	 * @returns {void}
+	 */
 	create(data, callback) {
 		const context = data.context;
-		const dependencies = data.dependencies;
+		const dependencies = /** @type {ContextDependency[]} */ (data.dependencies);
 		const resolveOptions = data.resolveOptions;
 		const dependency = dependencies[0];
+		/** @type {FileSystemDependencies} */
+		const fileDependencies = new LazySet();
+		/** @type {FileSystemDependencies} */
+		const missingDependencies = new LazySet();
+		/** @type {FileSystemDependencies} */
+		const contextDependencies = new LazySet();
 		this.hooks.beforeResolve.callAsync(
-			Object.assign(
-				{
-					context: context,
-					dependencies: dependencies,
-					resolveOptions
-				},
-				dependency.options
-			),
+			{
+				context,
+				dependencies,
+				layer: data.contextInfo.issuerLayer,
+				resolveOptions,
+				fileDependencies,
+				missingDependencies,
+				contextDependencies,
+				...dependency.options
+			},
 			(err, beforeResolveResult) => {
-				if (err) return callback(err);
+				if (err) {
+					return callback(err, {
+						fileDependencies,
+						missingDependencies,
+						contextDependencies
+					});
+				}
 
 				// Ignored
-				if (!beforeResolveResult) return callback();
+				if (!beforeResolveResult) {
+					return callback(null, {
+						fileDependencies,
+						missingDependencies,
+						contextDependencies
+					});
+				}
 
 				const context = beforeResolveResult.context;
 				const request = beforeResolveResult.request;
 				const resolveOptions = beforeResolveResult.resolveOptions;
 
-				let loaders,
-					resource,
-					loadersPrefix = "";
+				/** @type {undefined | string[]} */
+				let loaders;
+				/** @type {undefined | string} */
+				let resource;
+				let loadersPrefix = "";
 				const idx = request.lastIndexOf("!");
 				if (idx >= 0) {
-					let loadersRequest = request.substr(0, idx + 1);
+					let loadersRequest = request.slice(0, idx + 1);
+					/** @type {number} */
 					let i;
 					for (
 						i = 0;
@@ -77,15 +180,11 @@ module.exports = class ContextModuleFactory extends Tapable {
 						loadersPrefix += "!";
 					}
 					loadersRequest = loadersRequest
-						.substr(i)
+						.slice(i)
 						.replace(/!+$/, "")
-						.replace(/!!+/g, "!");
-					if (loadersRequest === "") {
-						loaders = [];
-					} else {
-						loaders = loadersRequest.split("!");
-					}
-					resource = request.substr(idx + 1);
+						.replace(/!{2,}/g, "!");
+					loaders = loadersRequest === "" ? [] : loadersRequest.split("!");
+					resource = request.slice(idx + 1);
 				} else {
 					loaders = [];
 					resource = request;
@@ -93,28 +192,46 @@ module.exports = class ContextModuleFactory extends Tapable {
 
 				const contextResolver = this.resolverFactory.get(
 					"context",
-					resolveOptions || EMPTY_RESOLVE_OPTIONS
-				);
-				const loaderResolver = this.resolverFactory.get(
-					"loader",
-					EMPTY_RESOLVE_OPTIONS
+					dependencies.length > 0
+						? cachedSetProperty(
+								resolveOptions || EMPTY_RESOLVE_OPTIONS,
+								"dependencyType",
+								dependencies[0].category
+							)
+						: resolveOptions
 				);
+				const loaderResolver = this.resolverFactory.get("loader");
 
 				asyncLib.parallel(
 					[
-						callback => {
+						(callback) => {
+							const results = /** @type {ResolveRequest[]} */ ([]);
+							/**
+							 * Processes the provided obj.
+							 * @param {ResolveRequest} obj obj
+							 * @returns {void}
+							 */
+							const yield_ = (obj) => {
+								results.push(obj);
+							};
+
 							contextResolver.resolve(
 								{},
 								context,
 								resource,
-								{},
-								(err, result) => {
+								{
+									fileDependencies,
+									missingDependencies,
+									contextDependencies,
+									yield: yield_
+								},
+								(err) => {
 									if (err) return callback(err);
-									callback(null, result);
+									callback(null, results);
 								}
 							);
 						},
-						callback => {
+						(callback) => {
 							asyncLib.map(
 								loaders,
 								(loader, callback) => {
@@ -122,7 +239,11 @@ module.exports = class ContextModuleFactory extends Tapable {
 										{},
 										context,
 										loader,
-										{},
+										{
+											fileDependencies,
+											missingDependencies,
+											contextDependencies
+										},
 										(err, result) => {
 											if (err) return callback(err);
 											callback(null, result);
@@ -134,30 +255,59 @@ module.exports = class ContextModuleFactory extends Tapable {
 						}
 					],
 					(err, result) => {
-						if (err) return callback(err);
-
+						if (err) {
+							return callback(err, {
+								fileDependencies,
+								missingDependencies,
+								contextDependencies
+							});
+						}
+						let [contextResult, loaderResult] =
+							/** @type {[ResolveRequest[], string[]]} */ (result);
+						if (contextResult.length > 1) {
+							const first = contextResult[0];
+							contextResult = contextResult.filter((r) => r.path);
+							if (contextResult.length === 0) contextResult.push(first);
+						}
 						this.hooks.afterResolve.callAsync(
-							Object.assign(
-								{
-									addon:
-										loadersPrefix +
-										result[1].join("!") +
-										(result[1].length > 0 ? "!" : ""),
-									resource: result[0],
-									resolveDependencies: this.resolveDependencies.bind(this)
-								},
-								beforeResolveResult
-							),
+							{
+								addon:
+									loadersPrefix +
+									loaderResult.join("!") +
+									(loaderResult.length > 0 ? "!" : ""),
+								resource:
+									contextResult.length > 1
+										? /** @type {string[]} */ (contextResult.map((r) => r.path))
+										: /** @type {string} */ (contextResult[0].path),
+								resolveDependencies: this.resolveDependencies.bind(this),
+								resourceQuery: contextResult[0].query,
+								resourceFragment: contextResult[0].fragment,
+								...beforeResolveResult
+							},
 							(err, result) => {
-								if (err) return callback(err);
+								if (err) {
+									return callback(err, {
+										fileDependencies,
+										missingDependencies,
+										contextDependencies
+									});
+								}
 
 								// Ignored
-								if (!result) return callback();
+								if (!result) {
+									return callback(null, {
+										fileDependencies,
+										missingDependencies,
+										contextDependencies
+									});
+								}
 
-								return callback(
-									null,
-									new ContextModule(result.resolveDependencies, result)
-								);
+								return callback(null, {
+									module: new ContextModule(result.resolveDependencies, result),
+									fileDependencies,
+									missingDependencies,
+									contextDependencies
+								});
 							}
 						);
 					}
@@ -166,69 +316,200 @@ module.exports = class ContextModuleFactory extends Tapable {
 		);
 	}
 
+	/**
+	 * Resolves dependencies.
+	 * @param {InputFileSystem} fs file system
+	 * @param {ContextModuleOptions} options options
+	 * @param {ResolveDependenciesCallback} callback callback function
+	 * @returns {void}
+	 */
 	resolveDependencies(fs, options, callback) {
 		const cmf = this;
-		let resource = options.resource;
-		let resourceQuery = options.resourceQuery;
-		let recursive = options.recursive;
-		let regExp = options.regExp;
-		let include = options.include;
-		let exclude = options.exclude;
-		if (!regExp || !resource) return callback(null, []);
-
-		const addDirectory = (directory, callback) => {
+		const {
+			resource,
+			resourceQuery,
+			resourceFragment,
+			recursive,
+			regExp,
+			patterns,
+			requestContext,
+			exhaustive,
+			caseSensitive,
+			include,
+			exclude,
+			referencedExports,
+			category,
+			typePrefix,
+			attributes
+		} = options;
+		const isImportMetaGlob = Boolean(patterns && requestContext);
+		if ((!regExp && !isImportMetaGlob) || !resource) return callback(null, []);
+
+		/**
+		 * Adds directory checked.
+		 * @param {string} ctx context
+		 * @param {string} directory directory
+		 * @param {Set} visited visited
+		 * @param {ResolveDependenciesCallback} callback callback
+		 * @param {ResolvedContextModuleGlobPattern[] | undefined} resolvedGlobPatterns resolved glob patterns
+		 */
+		const addDirectoryChecked = (
+			ctx,
+			directory,
+			visited,
+			callback,
+			resolvedGlobPatterns
+		) => {
+			/** @type {NonNullable} */
+			(fs.realpath)(directory, (err, _realPath) => {
+				if (err) return callback(err);
+				const realPath = /** @type {string} */ (_realPath);
+				if (visited.has(realPath)) return callback(null, []);
+				/** @type {Set | undefined} */
+				let recursionStack;
+				addDirectory(
+					ctx,
+					directory,
+					(_, dir, callback) => {
+						if (recursionStack === undefined) {
+							recursionStack = new Set(visited);
+							recursionStack.add(realPath);
+						}
+						addDirectoryChecked(
+							ctx,
+							dir,
+							recursionStack,
+							callback,
+							resolvedGlobPatterns
+						);
+					},
+					callback,
+					resolvedGlobPatterns
+				);
+			});
+		};
+
+		/**
+		 * Adds the provided ctx to the context module factory.
+		 * @param {string} ctx context
+		 * @param {string} directory directory
+		 * @param {AddSubDirectoryFn} addSubDirectory addSubDirectoryFn
+		 * @param {ResolveDependenciesCallback} callback callback
+		 * @param {ResolvedContextModuleGlobPattern[] | undefined} resolvedGlobPatterns resolved glob patterns
+		 * @returns {void}
+		 */
+		const addDirectory = (
+			ctx,
+			directory,
+			addSubDirectory,
+			callback,
+			resolvedGlobPatterns
+		) => {
 			fs.readdir(directory, (err, files) => {
 				if (err) return callback(err);
-				files = cmf.hooks.contextModuleFiles.call(files);
-				if (!files || files.length === 0) return callback(null, []);
+				const processedFiles = cmf.hooks.contextModuleFiles.call(
+					/** @type {string[]} */ (files).map((file) => file.normalize("NFC"))
+				);
+				if (!processedFiles || processedFiles.length === 0) {
+					return callback(null, []);
+				}
+				/** @type {ContextAlternativeRequest[]} */
+				const fileObjs = [];
+				/** @type {ContextElementDependency[]} */
+				const globDeps = [];
+				/** @type {Set} */
+				const globUserRequests = new Set();
 				asyncLib.map(
-					files.filter(p => p.indexOf(".") !== 0),
+					isImportMetaGlob
+						? processedFiles
+						: processedFiles.filter((p) => p.indexOf(".") !== 0),
 					(segment, callback) => {
-						const subResource = path.join(directory, segment);
+						const subResource = join(fs, directory, segment);
 
-						if (!exclude || !subResource.match(exclude)) {
-							fs.stat(subResource, (err, stat) => {
+						if (!exclude || !exclude.test(subResource)) {
+							fs.stat(subResource, (err, _stat) => {
 								if (err) {
 									if (err.code === "ENOENT") {
 										// ENOENT is ok here because the file may have been deleted between
 										// the readdir and stat calls.
 										return callback();
-									} else {
-										return callback(err);
 									}
+									return callback(err);
 								}
 
+								const stat = /** @type {IStats} */ (_stat);
+
 								if (stat.isDirectory()) {
 									if (!recursive) return callback();
-									addDirectory.call(this, subResource, callback);
+									if (
+										isImportMetaGlob &&
+										!exhaustive &&
+										isNonExhaustiveImportMetaGlobSkippedDir(segment) &&
+										!(
+											resolvedGlobPatterns &&
+											globPatternBaseReachesDir(
+												resolvedGlobPatterns,
+												subResource
+											)
+										)
+									) {
+										return callback();
+									}
+									addSubDirectory(
+										ctx,
+										subResource,
+										callback,
+										resolvedGlobPatterns
+									);
 								} else if (
 									stat.isFile() &&
-									(!include || subResource.match(include))
+									(!include || include.test(subResource))
 								) {
-									const obj = {
-										context: resource,
-										request:
-											"." +
-											subResource.substr(resource.length).replace(/\\/g, "/")
-									};
-
-									this.hooks.alternatives.callAsync(
-										[obj],
-										(err, alternatives) => {
-											if (err) return callback(err);
-											alternatives = alternatives
-												.filter(obj => regExp.test(obj.request))
-												.map(obj => {
-													const dep = new ContextElementDependency(
-														obj.request + resourceQuery,
-														obj.request
-													);
-													dep.optional = true;
-													return dep;
-												});
-											callback(null, alternatives);
+									if (
+										isImportMetaGlob &&
+										patterns &&
+										requestContext &&
+										resolvedGlobPatterns
+									) {
+										const relativePath = `.${subResource
+											.slice(ctx.length)
+											.replace(/\\/g, "/")}`;
+										const exposedUserRequest = globUserRequest(
+											resolvedGlobPatterns,
+											subResource,
+											exhaustive === true,
+											caseSensitive !== false
+										);
+										if (
+											exposedUserRequest &&
+											!globUserRequests.has(exposedUserRequest)
+										) {
+											globUserRequests.add(exposedUserRequest);
+											const dep = new ContextElementDependency(
+												`${relativePath}${resourceQuery}${resourceFragment}`,
+												exposedUserRequest,
+												typePrefix,
+												/** @type {string} */
+												(category),
+												referencedExports,
+												ctx,
+												attributes
+											);
+											dep.optional = true;
+											globDeps.push(dep);
 										}
-									);
+										return callback();
+									}
+									// Collect for a single batched alternativeRequests call
+									// per directory below. Calling the hook once per file
+									// would pay per-call overhead (closure, resolverFactory
+									// lookup, array allocations) for every file in the
+									// context — which is the bulk of work on rebuilds.
+									fileObjs.push({
+										context: ctx,
+										request: `.${subResource.slice(ctx.length).replace(/\\/g, "/")}`
+									});
+									callback();
 								} else {
 									callback();
 								}
@@ -240,17 +521,136 @@ module.exports = class ContextModuleFactory extends Tapable {
 					(err, result) => {
 						if (err) return callback(err);
 
-						if (!result) return callback(null, []);
+						/** @type {ContextElementDependency[]} */
+						const flattenedResult = [];
+
+						if (result) {
+							for (const item of result) {
+								if (item) flattenedResult.push(...item);
+							}
+						}
+
+						if (isImportMetaGlob) {
+							/** @type {Set} */
+							const mergedUserRequests = new Set();
+							/** @type {ContextElementDependency[]} */
+							const merged = [];
+							for (const dep of [...flattenedResult, ...globDeps]) {
+								if (mergedUserRequests.has(dep.userRequest)) continue;
+								mergedUserRequests.add(dep.userRequest);
+								merged.push(dep);
+							}
+							return callback(null, merged);
+						}
+
+						if (fileObjs.length === 0) {
+							return callback(null, flattenedResult);
+						}
 
-						callback(
-							null,
-							result.filter(Boolean).reduce((a, i) => a.concat(i), [])
+						this.hooks.alternativeRequests.callAsync(
+							fileObjs,
+							options,
+							(err, alternatives) => {
+								if (err) return callback(err);
+								for (const alt of /** @type {ContextAlternativeRequest[]} */ (
+									alternatives
+								)) {
+									if (
+										!(regExp instanceof RegExp) ||
+										!regExp.test(/** @type {string} */ (alt.request))
+									) {
+										continue;
+									}
+									const dep = new ContextElementDependency(
+										`${alt.request}${resourceQuery}${resourceFragment}`,
+										alt.request,
+										typePrefix,
+										/** @type {string} */
+										(category),
+										referencedExports,
+										alt.context,
+										attributes
+									);
+									dep.optional = true;
+									flattenedResult.push(dep);
+								}
+								callback(null, flattenedResult);
+							}
 						);
 					}
 				);
 			});
 		};
 
-		addDirectory(resource, callback);
+		/**
+		 * Adds sub directory.
+		 * @param {string} ctx context
+		 * @param {string} dir dir
+		 * @param {ResolveDependenciesCallback} callback callback
+		 * @param {ResolvedContextModuleGlobPattern[] | undefined} resolvedGlobPatterns resolved glob patterns
+		 * @returns {void}
+		 */
+		const addSubDirectory = (ctx, dir, callback, resolvedGlobPatterns) =>
+			addDirectory(ctx, dir, addSubDirectory, callback, resolvedGlobPatterns);
+
+		/**
+		 * Processes the provided resource.
+		 * @param {string} resource resource
+		 * @param {ResolveDependenciesCallback} callback callback
+		 */
+		const visitResource = (resource, callback) => {
+			/** @type {ResolvedContextModuleGlobPattern[] | undefined} */
+			const resolvedGlobPatterns =
+				isImportMetaGlob && patterns && requestContext
+					? patterns.map((pattern) =>
+							resolveContextModuleGlobPattern(pattern, requestContext, resource)
+						)
+					: undefined;
+			if (typeof fs.realpath === "function") {
+				addDirectoryChecked(
+					resource,
+					resource,
+					/** @type {Set} */
+					new Set(),
+					callback,
+					resolvedGlobPatterns
+				);
+			} else {
+				addDirectory(
+					resource,
+					resource,
+					addSubDirectory,
+					callback,
+					resolvedGlobPatterns
+				);
+			}
+		};
+
+		if (typeof resource === "string") {
+			visitResource(resource, callback);
+		} else {
+			asyncLib.map(resource, visitResource, (err, _result) => {
+				if (err) return callback(err);
+				const result = /** @type {ContextElementDependency[][]} */ (_result);
+
+				// result dependencies should have unique userRequest
+				// ordered by resolve result
+				/** @type {Set} */
+				const temp = new Set();
+				/** @type {ContextElementDependency[]} */
+				const res = [];
+				for (let i = 0; i < result.length; i++) {
+					const inner = result[i];
+					for (const el of inner) {
+						if (temp.has(el.userRequest)) continue;
+						res.push(el);
+						temp.add(el.userRequest);
+					}
+				}
+				callback(null, res);
+			});
+		}
 	}
-};
+}
+
+module.exports = ContextModuleFactory;
diff --git a/lib/ContextReplacementPlugin.js b/lib/ContextReplacementPlugin.js
index 39c29de83bf..d073487760a 100644
--- a/lib/ContextReplacementPlugin.js
+++ b/lib/ContextReplacementPlugin.js
@@ -2,52 +2,101 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
-const path = require("path");
 const ContextElementDependency = require("./dependencies/ContextElementDependency");
+const { join } = require("./util/fs");
+
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./ContextModule").ContextModuleOptions} ContextModuleOptions */
+/** @typedef {import("./ContextModuleFactory").BeforeContextResolveData} BeforeContextResolveData */
+/** @typedef {import("./ContextModuleFactory").AfterContextResolveData} AfterContextResolveData */
+/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
+
+/** @typedef {Record} NewContentCreateContextMap */
+
+const PLUGIN_NAME = "ContextReplacementPlugin";
 
 class ContextReplacementPlugin {
+	/**
+	 * Creates an instance of ContextReplacementPlugin.
+	 * @param {RegExp} resourceRegExp A regular expression that determines which files will be selected
+	 * @param {(string | ((context: BeforeContextResolveData | AfterContextResolveData) => void) | RegExp | boolean)=} newContentResource A new resource to replace the match
+	 * @param {(boolean | NewContentCreateContextMap | RegExp)=} newContentRecursive If true, all subdirectories are searched for matches
+	 * @param {RegExp=} newContentRegExp A regular expression that determines which files will be selected
+	 */
 	constructor(
 		resourceRegExp,
 		newContentResource,
 		newContentRecursive,
 		newContentRegExp
 	) {
+		/** @type {RegExp} */
 		this.resourceRegExp = resourceRegExp;
 
+		// new webpack.ContextReplacementPlugin(/selector/, (context) => { /* Logic */ });
 		if (typeof newContentResource === "function") {
 			this.newContentCallback = newContentResource;
-		} else if (
+		}
+		// new ContextReplacementPlugin(/selector/, './folder', { './request': './request' });
+		else if (
 			typeof newContentResource === "string" &&
 			typeof newContentRecursive === "object"
 		) {
+			/** @type {string | undefined} */
 			this.newContentResource = newContentResource;
+			/**
+			 * Stores new content create context map.
+			 * @param {InputFileSystem} fs input file system
+			 * @param {(err: null | Error, newContentRecursive: NewContentCreateContextMap) => void} callback callback
+			 */
 			this.newContentCreateContextMap = (fs, callback) => {
-				callback(null, newContentRecursive);
+				callback(
+					null,
+					/** @type {NewContentCreateContextMap} */ (newContentRecursive)
+				);
 			};
-		} else if (
+		}
+		// new ContextReplacementPlugin(/selector/, './folder', (context) => { /* Logic */ });
+		else if (
 			typeof newContentResource === "string" &&
 			typeof newContentRecursive === "function"
 		) {
 			this.newContentResource = newContentResource;
 			this.newContentCreateContextMap = newContentRecursive;
 		} else {
+			// new webpack.ContextReplacementPlugin(/selector/, false, /reg-exp/);
 			if (typeof newContentResource !== "string") {
-				newContentRegExp = newContentRecursive;
-				newContentRecursive = newContentResource;
+				newContentRegExp = /** @type {RegExp} */ (newContentRecursive);
+				newContentRecursive = /** @type {boolean} */ (newContentResource);
 				newContentResource = undefined;
 			}
+			// new webpack.ContextReplacementPlugin(/selector/, /de|fr|hu/);
 			if (typeof newContentRecursive !== "boolean") {
-				newContentRegExp = newContentRecursive;
+				newContentRegExp = /** @type {RegExp} */ (newContentRecursive);
 				newContentRecursive = undefined;
 			}
-			this.newContentResource = newContentResource;
-			this.newContentRecursive = newContentRecursive;
-			this.newContentRegExp = newContentRegExp;
+			// new webpack.ContextReplacementPlugin(/selector/, './folder', false, /selector/);
+			this.newContentResource =
+				/** @type {string | undefined} */
+				(newContentResource);
+			/** @type {boolean | undefined} */
+			this.newContentRecursive =
+				/** @type {boolean | undefined} */
+				(newContentRecursive);
+			/** @type {RegExp | undefined} */
+			this.newContentRegExp =
+				/** @type {RegExp | undefined} */
+				(newContentRegExp);
 		}
 	}
 
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
 	apply(compiler) {
 		const resourceRegExp = this.resourceRegExp;
 		const newContentCallback = this.newContentCallback;
@@ -56,17 +105,17 @@ class ContextReplacementPlugin {
 		const newContentRegExp = this.newContentRegExp;
 		const newContentCreateContextMap = this.newContentCreateContextMap;
 
-		compiler.hooks.contextModuleFactory.tap("ContextReplacementPlugin", cmf => {
-			cmf.hooks.beforeResolve.tap("ContextReplacementPlugin", result => {
+		compiler.hooks.contextModuleFactory.tap(PLUGIN_NAME, (cmf) => {
+			cmf.hooks.beforeResolve.tap(PLUGIN_NAME, (result) => {
 				if (!result) return;
 				if (resourceRegExp.test(result.request)) {
-					if (typeof newContentResource !== "undefined") {
+					if (newContentResource !== undefined) {
 						result.request = newContentResource;
 					}
-					if (typeof newContentRecursive !== "undefined") {
+					if (newContentRecursive !== undefined) {
 						result.recursive = newContentRecursive;
 					}
-					if (typeof newContentRegExp !== "undefined") {
+					if (newContentRegExp !== undefined) {
 						result.regExp = newContentRegExp;
 					}
 					if (typeof newContentCallback === "function") {
@@ -79,28 +128,72 @@ class ContextReplacementPlugin {
 				}
 				return result;
 			});
-			cmf.hooks.afterResolve.tap("ContextReplacementPlugin", result => {
+			cmf.hooks.afterResolve.tap(PLUGIN_NAME, (result) => {
 				if (!result) return;
-				if (resourceRegExp.test(result.resource)) {
-					if (typeof newContentResource !== "undefined") {
-						result.resource = path.resolve(result.resource, newContentResource);
+				const isMatchResourceRegExp = () => {
+					if (Array.isArray(result.resource)) {
+						return result.resource.some((item) => resourceRegExp.test(item));
 					}
-					if (typeof newContentRecursive !== "undefined") {
+
+					return resourceRegExp.test(result.resource);
+				};
+				if (isMatchResourceRegExp()) {
+					if (newContentResource !== undefined) {
+						if (
+							newContentResource.startsWith("/") ||
+							(newContentResource.length > 1 && newContentResource[1] === ":")
+						) {
+							result.resource = newContentResource;
+						} else {
+							const rootPath =
+								typeof result.resource === "string"
+									? result.resource
+									: /** @type {string} */
+										(result.resource.find((item) => resourceRegExp.test(item)));
+							result.resource = join(
+								/** @type {InputFileSystem} */
+								(compiler.inputFileSystem),
+								rootPath,
+								newContentResource
+							);
+						}
+					}
+					if (newContentRecursive !== undefined) {
 						result.recursive = newContentRecursive;
 					}
-					if (typeof newContentRegExp !== "undefined") {
+					if (newContentRegExp !== undefined) {
 						result.regExp = newContentRegExp;
 					}
 					if (typeof newContentCreateContextMap === "function") {
-						result.resolveDependencies = createResolveDependenciesFromContextMap(
-							newContentCreateContextMap
-						);
+						result.resolveDependencies =
+							createResolveDependenciesFromContextMap(
+								newContentCreateContextMap
+							);
 					}
 					if (typeof newContentCallback === "function") {
 						const origResource = result.resource;
 						newContentCallback(result);
 						if (result.resource !== origResource) {
-							result.resource = path.resolve(origResource, result.resource);
+							const newResource = Array.isArray(result.resource)
+								? result.resource
+								: [result.resource];
+
+							for (let i = 0; i < newResource.length; i++) {
+								if (
+									!newResource[i].startsWith("/") &&
+									(newResource[i].length <= 1 || newResource[i][1] !== ":")
+								) {
+									// When the function changed it to an relative path
+									newResource[i] = join(
+										/** @type {InputFileSystem} */
+										(compiler.inputFileSystem),
+										origResource[i],
+										newResource[i]
+									);
+								}
+							}
+
+							result.resource = newResource;
 						}
 					} else {
 						for (const d of result.dependencies) {
@@ -114,20 +207,28 @@ class ContextReplacementPlugin {
 	}
 }
 
-const createResolveDependenciesFromContextMap = createContextMap => {
-	const resolveDependenciesFromContextMap = (fs, options, callback) => {
+/**
+ * Creates a resolve dependencies from context map.
+ * @param {(fs: InputFileSystem, callback: (err: null | Error, map: NewContentCreateContextMap) => void) => void} createContextMap create context map function
+ * @returns {(fs: InputFileSystem, options: ContextModuleOptions, callback: (err: null | Error, dependencies?: ContextElementDependency[]) => void) => void} resolve resolve dependencies from context map function
+ */
+const createResolveDependenciesFromContextMap =
+	(createContextMap) => (fs, options, callback) => {
 		createContextMap(fs, (err, map) => {
 			if (err) return callback(err);
-			const dependencies = Object.keys(map).map(key => {
-				return new ContextElementDependency(
-					map[key] + options.resourceQuery,
-					key
-				);
-			});
+			const dependencies = Object.keys(map).map(
+				(key) =>
+					new ContextElementDependency(
+						map[key] + options.resourceQuery + options.resourceFragment,
+						key,
+						options.typePrefix,
+						/** @type {string} */
+						(options.category),
+						options.referencedExports
+					)
+			);
 			callback(null, dependencies);
 		});
 	};
-	return resolveDependenciesFromContextMap;
-};
 
 module.exports = ContextReplacementPlugin;
diff --git a/lib/DefinePlugin.js b/lib/DefinePlugin.js
index 4d485a9eb95..2ab4a6ddba0 100644
--- a/lib/DefinePlugin.js
+++ b/lib/DefinePlugin.js
@@ -2,102 +2,905 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
+const { SyncWaterfallHook } = require("tapable");
+const {
+	JAVASCRIPT_MODULE_TYPE_AUTO,
+	JAVASCRIPT_MODULE_TYPE_DYNAMIC,
+	JAVASCRIPT_MODULE_TYPE_ESM
+} = require("./ModuleTypeConstants");
+const RuntimeGlobals = require("./RuntimeGlobals");
 const ConstDependency = require("./dependencies/ConstDependency");
-const BasicEvaluatedExpression = require("./BasicEvaluatedExpression");
-const ParserHelpers = require("./ParserHelpers");
-const NullFactory = require("./NullFactory");
-
-const stringifyObj = obj => {
-	return (
-		"Object({" +
-		Object.keys(obj)
-			.map(key => {
+const WebpackError = require("./errors/WebpackError");
+const BasicEvaluatedExpression = require("./javascript/BasicEvaluatedExpression");
+const { VariableInfo } = require("./javascript/JavascriptParser");
+const {
+	evaluateToString,
+	toConstantDependency
+} = require("./javascript/JavascriptParserHelpers");
+const createHash = require("./util/createHash");
+const createHooksRegistry = require("./util/createHooksRegistry");
+
+/** @typedef {import("estree").Expression} Expression */
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./Module").BuildInfo} BuildInfo */
+/** @typedef {import("./Module").ValueCacheVersion} ValueCacheVersion */
+/** @typedef {import("./Module").ValueCacheVersions} ValueCacheVersions */
+/** @typedef {import("./NormalModule")} NormalModule */
+/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */
+/** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */
+/** @typedef {import("./javascript/JavascriptParser").DestructuringAssignmentProperties} DestructuringAssignmentProperties */
+/** @typedef {import("./javascript/JavascriptParser").Members} Members */
+/** @typedef {import("./javascript/JavascriptParser").Range} Range */
+/** @typedef {import("./logging/Logger").Logger} Logger */
+/** @typedef {import("./Compilation")} Compilation */
+/** @typedef {import("./NormalModule").NormalModuleBuildInfo} NormalModuleBuildInfo */
+
+/** @typedef {null | undefined | RegExp | EXPECTED_FUNCTION | string | number | boolean | bigint | undefined} CodeValuePrimitive */
+/** @typedef {RecursiveArrayOrRecord} CodeValue */
+
+/**
+ * Defines the runtime value options type used by this module.
+ * @typedef {object} RuntimeValueOptions
+ * @property {string[]=} fileDependencies
+ * @property {string[]=} contextDependencies
+ * @property {string[]=} missingDependencies
+ * @property {string[]=} buildDependencies
+ * @property {string | (() => string)=} version
+ */
+
+/** @typedef {(value: { module: NormalModule, key: string, readonly version: ValueCacheVersion }) => CodeValuePrimitive} GeneratorFn */
+
+class RuntimeValue {
+	/**
+	 * Creates an instance of RuntimeValue.
+	 * @param {GeneratorFn} fn generator function
+	 * @param {true | string[] | RuntimeValueOptions=} options options
+	 */
+	constructor(fn, options) {
+		/** @type {GeneratorFn} */
+		this.fn = fn;
+		if (Array.isArray(options)) {
+			options = {
+				fileDependencies: options
+			};
+		}
+		/** @type {true | RuntimeValueOptions} */
+		this.options = options || {};
+	}
+
+	get fileDependencies() {
+		return this.options === true ? true : this.options.fileDependencies;
+	}
+
+	/**
+	 * Returns code.
+	 * @param {JavascriptParser} parser the parser
+	 * @param {ValueCacheVersions} valueCacheVersions valueCacheVersions
+	 * @param {string} key the defined key
+	 * @returns {CodeValuePrimitive} code
+	 */
+	exec(parser, valueCacheVersions, key) {
+		const buildInfo = /** @type {BuildInfo} */ (parser.state.module.buildInfo);
+		if (this.options === true) {
+			buildInfo.cacheable = false;
+			const reasons =
+				buildInfo.notCacheableReasons || (buildInfo.notCacheableReasons = []);
+			const reason = `DefinePlugin runtime value ${key}`;
+			if (!reasons.includes(reason)) reasons.push(reason);
+		} else {
+			if (this.options.fileDependencies) {
+				for (const dep of this.options.fileDependencies) {
+					/** @type {NonNullable} */
+					(buildInfo.fileDependencies).add(dep);
+				}
+			}
+			if (this.options.contextDependencies) {
+				for (const dep of this.options.contextDependencies) {
+					/** @type {NonNullable} */
+					(buildInfo.contextDependencies).add(dep);
+				}
+			}
+			if (this.options.missingDependencies) {
+				for (const dep of this.options.missingDependencies) {
+					/** @type {NonNullable} */
+					(buildInfo.missingDependencies).add(dep);
+				}
+			}
+			if (this.options.buildDependencies) {
+				for (const dep of this.options.buildDependencies) {
+					/** @type {NonNullable} */
+					(buildInfo.buildDependencies).add(dep);
+				}
+			}
+		}
+
+		return this.fn({
+			module: parser.state.module,
+			key,
+			get version() {
+				return /** @type {ValueCacheVersion} */ (
+					valueCacheVersions.get(VALUE_DEP_PREFIX + key)
+				);
+			}
+		});
+	}
+
+	getCacheVersion() {
+		return this.options === true
+			? undefined
+			: (typeof this.options.version === "function"
+					? this.options.version()
+					: this.options.version) || "unset";
+	}
+}
+
+/**
+ * Returns used keys.
+ * @param {DestructuringAssignmentProperties | undefined} properties properties
+ * @returns {Set | undefined} used keys
+ */
+function getObjKeys(properties) {
+	if (!properties) return;
+	return new Set([...properties].map((p) => p.id));
+}
+
+/**
+ * Whether a value is a nested definition (plain object/array) to recurse into.
+ * @param {CodeValue} code code value
+ * @returns {code is Definitions} true for a plain object or array
+ */
+const isObjectDefinition = (code) =>
+	Boolean(code) &&
+	typeof code === "object" &&
+	!(code instanceof RuntimeValue) &&
+	!(code instanceof RegExp);
+
+/** @typedef {Set | null} ObjKeys */
+/** @typedef {boolean | undefined | null} AsiSafe */
+
+/**
+ * Returns code converted to string that evaluates.
+ * @param {EXPECTED_ANY[] | { [k: string]: EXPECTED_ANY }} obj obj
+ * @param {JavascriptParser} parser Parser
+ * @param {ValueCacheVersions} valueCacheVersions valueCacheVersions
+ * @param {string} key the defined key
+ * @param {RuntimeTemplate} runtimeTemplate the runtime template
+ * @param {Logger} logger the logger object
+ * @param {AsiSafe=} asiSafe asi safe (undefined: unknown, null: unneeded)
+ * @param {ObjKeys=} objKeys used keys
+ * @returns {string} code converted to string that evaluates
+ */
+const stringifyObj = (
+	obj,
+	parser,
+	valueCacheVersions,
+	key,
+	runtimeTemplate,
+	logger,
+	asiSafe,
+	objKeys
+) => {
+	/** @type {string} */
+	let code;
+	const arr = Array.isArray(obj);
+	if (arr) {
+		code = `[${obj
+			.map((code) =>
+				toCode(
+					code,
+					parser,
+					valueCacheVersions,
+					key,
+					runtimeTemplate,
+					logger,
+					null
+				)
+			)
+			.join(",")}]`;
+	} else {
+		let keys = Object.keys(obj);
+		if (objKeys) {
+			keys = objKeys.size === 0 ? [] : keys.filter((k) => objKeys.has(k));
+		}
+		code = `{${keys
+			.map((key) => {
 				const code = obj[key];
-				return JSON.stringify(key) + ":" + toCode(code);
+				return `${toPropertyKey(key)}:${toCode(
+					code,
+					parser,
+					valueCacheVersions,
+					key,
+					runtimeTemplate,
+					logger,
+					null
+				)}`;
 			})
-			.join(",") +
-		"})"
-	);
+			.join(",")}}`;
+	}
+
+	switch (asiSafe) {
+		case null:
+			return code;
+		case true:
+			return arr ? code : `(${code})`;
+		case false:
+			return arr ? `;${code}` : `;(${code})`;
+		default:
+			return `/*#__PURE__*/Object(${code})`;
+	}
 };
 
-const toCode = code => {
+/**
+ * Convert code to a string that evaluates
+ * @param {CodeValue} code Code to evaluate
+ * @param {JavascriptParser} parser Parser
+ * @param {ValueCacheVersions} valueCacheVersions valueCacheVersions
+ * @param {string} key the defined key
+ * @param {RuntimeTemplate} runtimeTemplate the runtime template
+ * @param {Logger} logger the logger object
+ * @param {boolean | undefined | null=} asiSafe asi safe (undefined: unknown, null: unneeded)
+ * @param {ObjKeys=} objKeys used keys
+ * @returns {string} code converted to string that evaluates
+ */
+const toCode = (
+	code,
+	parser,
+	valueCacheVersions,
+	key,
+	runtimeTemplate,
+	logger,
+	asiSafe,
+	objKeys
+) => {
+	const transformToCode = () => {
+		if (code === null) {
+			return "null";
+		}
+		if (code === undefined) {
+			return "undefined";
+		}
+		if (Object.is(code, -0)) {
+			return "-0";
+		}
+		if (code instanceof RuntimeValue) {
+			return toCode(
+				code.exec(parser, valueCacheVersions, key),
+				parser,
+				valueCacheVersions,
+				key,
+				runtimeTemplate,
+				logger,
+				asiSafe
+			);
+		}
+		if (code instanceof RegExp && code.toString) {
+			return code.toString();
+		}
+		if (typeof code === "function" && code.toString) {
+			return `(${code.toString()})`;
+		}
+		if (typeof code === "object") {
+			return stringifyObj(
+				code,
+				parser,
+				valueCacheVersions,
+				key,
+				runtimeTemplate,
+				logger,
+				asiSafe,
+				objKeys
+			);
+		}
+		if (typeof code === "bigint") {
+			return runtimeTemplate.supportsBigIntLiteral()
+				? `${code}n`
+				: `BigInt("${code}")`;
+		}
+		return `${code}`;
+	};
+
+	const strCode = transformToCode();
+
+	logger.debug(`Replaced "${key}" with "${strCode}"`);
+
+	return strCode;
+};
+
+/**
+ * Returns result.
+ * @param {CodeValue} code code
+ * @returns {string | undefined} result
+ */
+const toCacheVersion = (code) => {
 	if (code === null) {
 		return "null";
 	}
 	if (code === undefined) {
 		return "undefined";
 	}
+	if (Object.is(code, -0)) {
+		return "-0";
+	}
+	if (code instanceof RuntimeValue) {
+		return code.getCacheVersion();
+	}
 	if (code instanceof RegExp && code.toString) {
 		return code.toString();
 	}
 	if (typeof code === "function" && code.toString) {
-		return "(" + code.toString() + ")";
+		return `(${code.toString()})`;
 	}
 	if (typeof code === "object") {
-		return stringifyObj(code);
+		const items = Object.keys(code).map((key) => ({
+			key,
+			value: toCacheVersion(
+				/** @type {Record} */
+				(code)[key]
+			)
+		}));
+		if (items.some(({ value }) => value === undefined)) return;
+		return `{${items.map(({ key, value }) => `${key}: ${value}`).join(", ")}}`;
+	}
+	if (typeof code === "bigint") {
+		return `${code}n`;
 	}
-	return code + "";
+	return `${code}`;
 };
 
+const PLUGIN_NAME = "DefinePlugin";
+const VALUE_DEP_PREFIX = `webpack/${PLUGIN_NAME} `;
+const VALUE_DEP_MAIN = `webpack/${PLUGIN_NAME}_hash`;
+const TYPEOF_OPERATOR_REGEXP = /^typeof\s+/;
+const EMPTY_OBJECT = {};
+
+/** @typedef {Map} MergedDefinitionMap */
+/** @typedef {CodeValue | MergedDefinitionMap} MergedDefinitionNode */
+
+/**
+ * Merged view of the definitions of every DefinePlugin instance.
+ * @typedef {object} MergedDefinitions
+ * @property {Compilation} compilation the compilation
+ * @property {Definitions} definitions flat definitions
+ * @property {MergedDefinitionMap} root nested view (dotted keys expanded)
+ * @property {Map>} finalByNestedKey final key segments by nested object key
+ * @property {Map>} nestedByFinalKey nested object keys by final key segment
+ * @property {Map} keysByPrefix definition keys below each dotted-key prefix
+ * @property {boolean} hasRuntimeValue renders are module-dependent (RuntimeValue present)
+ * @property {WeakMap} codeCache rendered code per node (static trees only)
+ * @property {Logger} logger the logger
+ */
+
+/** @type {WeakMap} */
+const mergedDefinitionsMap = new WeakMap();
+
+/**
+ * Whether a definition value is a plain object to expand into the nested view.
+ * @param {CodeValue} code definition value
+ * @returns {code is Definitions} true for a plain object
+ */
+const isPlainObjectDefinition = (code) => {
+	if (!code || typeof code !== "object" || Array.isArray(code)) return false;
+	const proto = Object.getPrototypeOf(code);
+	return proto === Object.prototype || proto === null;
+};
+
+/**
+ * Object literal key code; `__proto__` must be computed to stay an own property.
+ * @param {string} key property key
+ * @returns {string} property key code
+ */
+const toPropertyKey = (key) =>
+	key === "__proto__" ? '["__proto__"]' : JSON.stringify(key);
+
+/**
+ * Whether a definition value contains a RuntimeValue (module-dependent code).
+ * @param {CodeValue} value definition value
+ * @returns {boolean} true when a RuntimeValue is contained
+ */
+const containsRuntimeValue = (value) => {
+	if (value instanceof RuntimeValue) return true;
+	if (isObjectDefinition(value)) {
+		for (const key of Object.keys(value)) {
+			if (containsRuntimeValue(value[key])) return true;
+		}
+	}
+	return false;
+};
+
+/**
+ * Sets a definition value on a node of the nested view, merging plain objects.
+ * @param {MergedDefinitionMap} node node
+ * @param {string} key key
+ * @param {CodeValue} value definition value
+ */
+const setMergedValue = (node, key, value) => {
+	const existing = node.get(key);
+	if (isPlainObjectDefinition(value)) {
+		const map =
+			existing instanceof Map
+				? existing
+				: /** @type {MergedDefinitionMap} */ (new Map());
+		if (map !== existing) node.set(key, map);
+		for (const k of Object.keys(value)) setMergedValue(map, k, value[k]);
+	} else {
+		node.set(key, value);
+	}
+};
+
+/**
+ * Inserts a definition key path into the nested view.
+ * @param {MergedDefinitionMap} root root node
+ * @param {string[]} path dot-separated key path
+ * @param {CodeValue} value definition value
+ */
+const insertMergedDefinition = (root, path, value) => {
+	let node = root;
+	for (let i = 0; i < path.length - 1; i++) {
+		let next = node.get(path[i]);
+		if (next === undefined && !node.has(path[i])) {
+			next = /** @type {MergedDefinitionMap} */ (new Map());
+			node.set(path[i], next);
+		} else if (!(next instanceof Map)) {
+			// deeper keys cannot merge into a non-object value
+			return;
+		}
+		node = next;
+	}
+	setMergedValue(node, path[path.length - 1], value);
+};
+
+/**
+ * Returns the merged view of all definitions (built once per compilation).
+ * @param {Compilation} compilation the compilation
+ * @returns {MergedDefinitions} merged definitions
+ */
+const getMergedDefinitions = (compilation) => {
+	const cached = mergedDefinitionsMap.get(compilation);
+	if (cached) return cached;
+
+	const definitions = DefinePlugin.getCompilationHooks(
+		compilation
+	).definitions.call({});
+	/** @type {MergedDefinitionMap} */
+	const root = new Map();
+	/** @type {Map>} */
+	const finalByNestedKey = new Map();
+	/** @type {Map>} */
+	const nestedByFinalKey = new Map();
+	/**
+	 * @param {Map>} map map
+	 * @param {string} key key
+	 * @param {string} value value
+	 */
+	const addToMap = (map, key, value) => {
+		const set = map.get(key);
+		if (set) {
+			set.add(value);
+		} else {
+			map.set(key, new Set([value]));
+		}
+	};
+	let hasRuntimeValue = false;
+	/** @type {Map} */
+	const keysByPrefix = new Map();
+	for (const key of Object.keys(definitions)) {
+		if (TYPEOF_OPERATOR_REGEXP.test(key)) continue;
+		const code = definitions[key];
+		insertMergedDefinition(root, key.split("."), code);
+		if (!hasRuntimeValue && containsRuntimeValue(code)) hasRuntimeValue = true;
+		// group this key under every ancestor prefix (value deps of object reads)
+		for (
+			let idx = key.indexOf(".");
+			idx !== -1;
+			idx = key.indexOf(".", idx + 1)
+		) {
+			const prefix = key.slice(0, idx);
+			const keys = keysByPrefix.get(prefix);
+			if (keys) {
+				keys.push(key);
+			} else {
+				keysByPrefix.set(prefix, [key]);
+			}
+		}
+		if (!code || typeof code === "object") continue;
+		const idx = key.lastIndexOf(".");
+		if (idx <= 0 || idx >= key.length - 1) continue;
+		const nested = key.slice(0, idx);
+		const final = key.slice(idx + 1);
+		addToMap(finalByNestedKey, nested, final);
+		addToMap(nestedByFinalKey, final, nested);
+	}
+	const result = {
+		compilation,
+		definitions,
+		root,
+		finalByNestedKey,
+		nestedByFinalKey,
+		keysByPrefix,
+		hasRuntimeValue,
+		codeCache: new WeakMap(),
+		logger: compilation.getLogger("webpack.DefinePlugin")
+	};
+	mergedDefinitionsMap.set(compilation, result);
+	return result;
+};
+
+/**
+ * Returns the merged definition node for a definition key.
+ * @param {Compilation} compilation the compilation
+ * @param {string} key definition key
+ * @returns {MergedDefinitionNode | undefined} merged node
+ */
+const getMergedDefinitionNode = (compilation, key) => {
+	/** @type {MergedDefinitionNode | undefined} */
+	let node = getMergedDefinitions(compilation).root;
+	for (const part of key.split(".")) {
+		if (!(node instanceof Map)) return;
+		node = node.get(part);
+	}
+	return node;
+};
+
+/**
+ * Returns a merged map node converted to an object literal code string
+ * (no ASI wrapping). Static renders (no key filter, no RuntimeValue) are
+ * cached per compilation.
+ * @param {MergedDefinitions} merged merged definitions
+ * @param {MergedDefinitionMap} node merged map node
+ * @param {JavascriptParser} parser Parser
+ * @param {string} key the defined key
+ * @param {ObjKeys=} objKeys used keys
+ * @returns {string} code
+ */
+const stringifyMergedNode = (merged, node, parser, key, objKeys) => {
+	const cacheable = objKeys === undefined && !merged.hasRuntimeValue;
+	if (cacheable) {
+		const cached = merged.codeCache.get(node);
+		if (cached !== undefined) return cached;
+	}
+	let keys = [...node.keys()];
+	if (objKeys) {
+		keys = objKeys.size === 0 ? [] : keys.filter((k) => objKeys.has(k));
+	}
+	const code = `{${keys
+		.map((k) => {
+			const value = /** @type {MergedDefinitionNode} */ (node.get(k));
+			return `${toPropertyKey(k)}:${
+				value instanceof Map
+					? stringifyMergedNode(merged, value, parser, k)
+					: toCode(
+							value,
+							parser,
+							merged.compilation.valueCacheVersions,
+							k,
+							merged.compilation.runtimeTemplate,
+							merged.logger,
+							null
+						)
+			}`;
+		})
+		.join(",")}}`;
+	if (cacheable) merged.codeCache.set(node, code);
+	return code;
+};
+
+/**
+ * Renders a merged definition node to raw code (object literal for map nodes).
+ * @param {Compilation} compilation the compilation
+ * @param {JavascriptParser} parser Parser
+ * @param {MergedDefinitionNode} node merged node
+ * @param {string} key the defined key
+ * @param {ObjKeys=} objKeys used keys
+ * @returns {string} code
+ */
+const stringifyMergedDefinition = (compilation, parser, node, key, objKeys) => {
+	const merged = getMergedDefinitions(compilation);
+	return node instanceof Map
+		? stringifyMergedNode(merged, node, parser, key, objKeys)
+		: toCode(
+				node,
+				parser,
+				compilation.valueCacheVersions,
+				key,
+				compilation.runtimeTemplate,
+				merged.logger,
+				null
+			);
+};
+
+const WEBPACK_REQUIRE_FUNCTION_REGEXP = new RegExp(
+	`${RuntimeGlobals.require}\\s*(!?\\.)`
+);
+
+/**
+ * Runtime requirements of replacement code referencing the require function.
+ * @param {string} code replacement code
+ * @returns {string[] | undefined} runtime requirements
+ */
+const getRuntimeRequirements = (code) => {
+	// fast path: most replacement code never references the require function
+	if (!code.includes(RuntimeGlobals.require)) return;
+	return WEBPACK_REQUIRE_FUNCTION_REGEXP.test(code)
+		? [RuntimeGlobals.require]
+		: [RuntimeGlobals.requireScope];
+};
+
+const createCompilationHooks = () => ({
+	/**
+	 * @type {SyncWaterfallHook<[Record]>}
+	 * @since 5.104.0
+	 */
+	definitions: new SyncWaterfallHook(["definitions"])
+});
+
+/**
+ * Prefixes an evaluation error with the offending define key so a bare
+ * "Unexpected token" becomes actionable. The original error is mutated and
+ * returned (not re-wrapped) so its `loc`/`stack` survive for ModuleParseError's
+ * code frame instead of being dumped into the message.
+ * @param {string} key the defined key
+ * @param {CodeValue} code the defined value
+ * @param {unknown} error the original evaluation error
+ * @returns {Error} the annotated error
+ */
+const annotateEvaluationError = (key, code, error) => {
+	const value = typeof code === "string" ? code : JSON.stringify(code);
+	const prefix = `DefinePlugin: failed to evaluate value for "${key}" (\`${value}\`)`;
+	if (error instanceof Error) {
+		error.message = `${prefix}: ${error.message}`;
+		return error;
+	}
+	return new WebpackError(`${prefix}: ${error}`);
+};
+
+/**
+ * @typedef {ReturnType} DefinePluginHooks
+ */
+
+/** @typedef {Record} Definitions */
+
 class DefinePlugin {
+	/**
+	 * Create a new define plugin
+	 * @param {Definitions} definitions A map of global object definitions
+	 */
 	constructor(definitions) {
+		/** @type {Definitions} */
 		this.definitions = definitions;
 	}
 
+	/**
+	 * Returns runtime value.
+	 * @param {GeneratorFn} fn generator function
+	 * @param {true | string[] | RuntimeValueOptions=} options options
+	 * @returns {RuntimeValue} runtime value
+	 */
+	static runtimeValue(fn, options) {
+		return new RuntimeValue(fn, options);
+	}
+
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
 	apply(compiler) {
-		const definitions = this.definitions;
 		compiler.hooks.compilation.tap(
-			"DefinePlugin",
+			PLUGIN_NAME,
 			(compilation, { normalModuleFactory }) => {
-				compilation.dependencyFactories.set(ConstDependency, new NullFactory());
+				const definitions = this.definitions;
+				const hooks = DefinePlugin.getCompilationHooks(compilation);
+
+				hooks.definitions.tap(PLUGIN_NAME, (previousDefinitions) => ({
+					...previousDefinitions,
+					...definitions
+				}));
+
+				const logger = compilation.getLogger("webpack.DefinePlugin");
 				compilation.dependencyTemplates.set(
 					ConstDependency,
 					new ConstDependency.Template()
 				);
+				const { runtimeTemplate } = compilation;
+
+				const mainHash = createHash(compilation.outputOptions.hashFunction);
+				mainHash.update(
+					/** @type {string} */
+					(compilation.valueCacheVersions.get(VALUE_DEP_MAIN)) || ""
+				);
 
-				const handler = parser => {
+				/**
+				 * Handles the hook callback for this code path.
+				 * @param {JavascriptParser} parser Parser
+				 * @returns {void}
+				 */
+				const handler = (parser) => {
+					// built lazily here: every definitions hook is tapped by the time
+					// parsers are created, and the result is cached per compilation
+					const mergedDefinitions = getMergedDefinitions(compilation);
+					/** @type {Set} */
+					const hooked = new Set();
+					const mainValue =
+						/** @type {ValueCacheVersion} */
+						(compilation.valueCacheVersions.get(VALUE_DEP_MAIN));
+					parser.hooks.program.tap(PLUGIN_NAME, () => {
+						const buildInfo = /** @type {NormalModuleBuildInfo} */ (
+							parser.state.module.buildInfo
+						);
+						if (!buildInfo.valueDependencies) {
+							buildInfo.valueDependencies = new Map();
+						}
+						buildInfo.valueDependencies.set(VALUE_DEP_MAIN, mainValue);
+					});
+
+					/**
+					 * Adds value dependency.
+					 * @param {string} key key
+					 */
+					const addValueDependency = (key) => {
+						const buildInfo =
+							/** @type {NormalModuleBuildInfo} */
+							(parser.state.module.buildInfo);
+						/** @type {NonNullable} */
+						(buildInfo.valueDependencies).set(
+							VALUE_DEP_PREFIX + key,
+							/** @type {ValueCacheVersion} */
+							(compilation.valueCacheVersions.get(VALUE_DEP_PREFIX + key))
+						);
+					};
+
+					/**
+					 * With value dependency.
+					 * @template T
+					 * @param {string} key key
+					 * @param {(expression: Expression) => T} fn fn
+					 * @returns {(expression: Expression) => T} result
+					 */
+					const withValueDependency =
+						(key, fn) =>
+						(...args) => {
+							addValueDependency(key);
+							return fn(...args);
+						};
+
+					/**
+					 * Processes the provided definition.
+					 * @param {Definitions} definitions Definitions map
+					 * @param {string} prefix Prefix string
+					 * @returns {void}
+					 */
 					const walkDefinitions = (definitions, prefix) => {
-						Object.keys(definitions).forEach(key => {
+						for (const key of Object.keys(definitions)) {
 							const code = definitions[key];
-							if (
-								code &&
-								typeof code === "object" &&
-								!(code instanceof RegExp)
-							) {
-								walkDefinitions(code, prefix + key + ".");
+							if (isObjectDefinition(code)) {
+								walkDefinitions(
+									/** @type {Definitions} */ (code),
+									`${prefix + key}.`
+								);
 								applyObjectDefine(prefix + key, code);
-								return;
+								continue;
 							}
 							applyDefineKey(prefix, key);
 							applyDefine(prefix + key, code);
-						});
+						}
 					};
 
+					/**
+					 * Processes the provided prefix.
+					 * @param {string} prefix Prefix
+					 * @param {string} key Key
+					 * @returns {void}
+					 */
 					const applyDefineKey = (prefix, key) => {
 						const splittedKey = key.split(".");
-						splittedKey.slice(1).forEach((_, i) => {
+						const firstKey = splittedKey[0];
+						for (const [i, _] of splittedKey.slice(1).entries()) {
 							const fullKey = prefix + splittedKey.slice(0, i + 1).join(".");
-							parser.hooks.canRename
-								.for(fullKey)
-								.tap("DefinePlugin", ParserHelpers.approve);
-						});
+							// `import.meta` is a MetaProperty, not an identifier that can be
+							// aliased: an alias declaration would keep the raw MetaProperty
+							// in the output, so never enable renaming for its prefixes
+							if (fullKey === "import" || fullKey === "import.meta") continue;
+							parser.hooks.canRename.for(fullKey).tap(PLUGIN_NAME, () => {
+								addValueDependency(key);
+								if (
+									parser.scope.definitions.get(firstKey) instanceof VariableInfo
+								) {
+									return false;
+								}
+								return true;
+							});
+						}
+						if (prefix === "") {
+							const final = splittedKey[splittedKey.length - 1];
+							// aggregated over all instances so keys split across
+							// several DefinePlugins can still be destructured together
+							const nestedSet = mergedDefinitions.nestedByFinalKey.get(final);
+							if (!nestedSet || nestedSet.size <= 0) return;
+							for (const nested of /** @type {Set} */ (nestedSet)) {
+								if (nested && !hooked.has(nested)) {
+									// only detect the same nested key once
+									hooked.add(nested);
+									parser.hooks.collectDestructuringAssignmentProperties.tap(
+										PLUGIN_NAME,
+										(expr) => {
+											const nameInfo = parser.getNameForExpression(expr);
+											if (nameInfo && nameInfo.name === nested) return true;
+										}
+									);
+									parser.hooks.expression.for(nested).tap(
+										{
+											name: PLUGIN_NAME,
+											// why 100? Ensures it runs after object define
+											stage: 100
+										},
+										(expr) => {
+											const destructed =
+												parser.destructuringAssignmentPropertiesFor(expr);
+											if (destructed === undefined) {
+												return;
+											}
+											/** @type {Definitions} */
+											const obj = Object.create(null);
+											const finalSet =
+												mergedDefinitions.finalByNestedKey.get(nested);
+											for (const { id } of destructed) {
+												const fullKey = `${nested}.${id}`;
+												if (
+													!finalSet ||
+													!finalSet.has(id) ||
+													!mergedDefinitions.definitions[fullKey]
+												) {
+													return;
+												}
+												addValueDependency(fullKey);
+												obj[id] = mergedDefinitions.definitions[fullKey];
+											}
+											let strCode = stringifyObj(
+												obj,
+												parser,
+												compilation.valueCacheVersions,
+												key,
+												runtimeTemplate,
+												logger,
+												!parser.isAsiPosition(
+													/** @type {Range} */ (expr.range)[0]
+												),
+												getObjKeys(destructed)
+											);
+											if (parser.scope.inShorthand) {
+												strCode = `${parser.scope.inShorthand}:${strCode}`;
+											}
+											return toConstantDependency(parser, strCode)(expr);
+										}
+									);
+								}
+							}
+						}
 					};
 
+					/**
+					 * Processes the provided key.
+					 * @param {string} key Key
+					 * @param {CodeValue} code Code
+					 * @returns {void}
+					 */
 					const applyDefine = (key, code) => {
-						const isTypeof = /^typeof\s+/.test(key);
-						if (isTypeof) key = key.replace(/^typeof\s+/, "");
+						const originalKey = key;
+						const isTypeof = TYPEOF_OPERATOR_REGEXP.test(key);
+						if (isTypeof) key = key.replace(TYPEOF_OPERATOR_REGEXP, "");
 						let recurse = false;
 						let recurseTypeof = false;
-						code = toCode(code);
 						if (!isTypeof) {
-							parser.hooks.canRename
-								.for(key)
-								.tap("DefinePlugin", ParserHelpers.approve);
+							parser.hooks.canRename.for(key).tap(PLUGIN_NAME, () => {
+								addValueDependency(originalKey);
+								return true;
+							});
 							parser.hooks.evaluateIdentifier
 								.for(key)
-								.tap("DefinePlugin", expr => {
+								.tap(PLUGIN_NAME, (expr) => {
 									/**
 									 * this is needed in case there is a recursion in the DefinePlugin
 									 * to prevent an endless recursion
@@ -107,26 +910,48 @@ class DefinePlugin {
 									 * });
 									 */
 									if (recurse) return;
+									addValueDependency(originalKey);
 									recurse = true;
-									const res = parser.evaluate(code);
+									const res = parser.evaluate(
+										toCode(
+											code,
+											parser,
+											compilation.valueCacheVersions,
+											key,
+											runtimeTemplate,
+											logger,
+											null
+										)
+									);
 									recurse = false;
-									res.setRange(expr.range);
+									res.setRange(/** @type {Range} */ (expr.range));
 									return res;
 								});
-							parser.hooks.expression
-								.for(key)
-								.tap(
-									"DefinePlugin",
-									/__webpack_require__/.test(code)
-										? ParserHelpers.toConstantDependencyWithWebpackRequire(
-												parser,
-												code
-										  )
-										: ParserHelpers.toConstantDependency(parser, code)
+							parser.hooks.expression.for(key).tap(PLUGIN_NAME, (expr) => {
+								addValueDependency(originalKey);
+								let strCode = toCode(
+									code,
+									parser,
+									compilation.valueCacheVersions,
+									originalKey,
+									runtimeTemplate,
+									logger,
+									!parser.isAsiPosition(/** @type {Range} */ (expr.range)[0]),
+									null
 								);
+
+								if (parser.scope.inShorthand) {
+									strCode = `${parser.scope.inShorthand}:${strCode}`;
+								}
+
+								return toConstantDependency(
+									parser,
+									strCode,
+									getRuntimeRequirements(strCode)
+								)(expr);
+							});
 						}
-						const typeofCode = isTypeof ? code : "typeof (" + code + ")";
-						parser.hooks.evaluateTypeof.for(key).tap("DefinePlugin", expr => {
+						parser.hooks.evaluateTypeof.for(key).tap(PLUGIN_NAME, (expr) => {
 							/**
 							 * this is needed in case there is a recursion in the DefinePlugin
 							 * to prevent an endless recursion
@@ -137,52 +962,249 @@ class DefinePlugin {
 							 */
 							if (recurseTypeof) return;
 							recurseTypeof = true;
+							addValueDependency(originalKey);
+							const codeCode = toCode(
+								code,
+								parser,
+								compilation.valueCacheVersions,
+								originalKey,
+								runtimeTemplate,
+								logger,
+								null
+							);
+							const typeofCode = isTypeof ? codeCode : `typeof (${codeCode})`;
 							const res = parser.evaluate(typeofCode);
 							recurseTypeof = false;
-							res.setRange(expr.range);
+							res.setRange(/** @type {Range} */ (expr.range));
 							return res;
 						});
-						parser.hooks.typeof.for(key).tap("DefinePlugin", expr => {
-							const res = parser.evaluate(typeofCode);
+						parser.hooks.typeof.for(key).tap(PLUGIN_NAME, (expr) => {
+							addValueDependency(originalKey);
+							/** @type {BasicEvaluatedExpression} */
+							let res;
+							try {
+								const codeCode = toCode(
+									code,
+									parser,
+									compilation.valueCacheVersions,
+									originalKey,
+									runtimeTemplate,
+									logger,
+									null
+								);
+								const typeofCode = isTypeof ? codeCode : `typeof (${codeCode})`;
+								res = parser.evaluate(typeofCode);
+							} catch (err) {
+								throw annotateEvaluationError(originalKey, code, err);
+							}
 							if (!res.isString()) return;
-							return ParserHelpers.toConstantDependency(
+							return toConstantDependency(
 								parser,
 								JSON.stringify(res.string)
 							).bind(parser)(expr);
 						});
 					};
 
+					/**
+					 * Processes the provided key.
+					 * @param {string} key Key
+					 * @param {object} obj Object
+					 * @returns {void}
+					 */
 					const applyObjectDefine = (key, obj) => {
-						const code = stringifyObj(obj);
-						parser.hooks.canRename
-							.for(key)
-							.tap("DefinePlugin", ParserHelpers.approve);
+						const mergedNode = getMergedDefinitionNode(compilation, key);
+						/** @type {MergedDefinitionNode} */
+						const definition =
+							mergedNode === undefined
+								? /** @type {CodeValue} */ (obj)
+								: mergedNode;
+						// value dependencies of a whole-object read: the key itself and
+						// every dotted definition key below it (from any instance)
+						/** @type {[string, ValueCacheVersion][]} */
+						const objectValueDependencies = [];
+						for (const defKey of [
+							key,
+							...(mergedDefinitions.keysByPrefix.get(key) || [])
+						]) {
+							const name = VALUE_DEP_PREFIX + defKey;
+							objectValueDependencies.push([
+								name,
+								/** @type {ValueCacheVersion} */
+								(compilation.valueCacheVersions.get(name))
+							]);
+						}
+						parser.hooks.canRename.for(key).tap(PLUGIN_NAME, () => {
+							addValueDependency(key);
+							return true;
+						});
 						parser.hooks.evaluateIdentifier
 							.for(key)
-							.tap("DefinePlugin", expr =>
-								new BasicEvaluatedExpression().setTruthy().setRange(expr.range)
-							);
+							.tap(PLUGIN_NAME, (expr) => {
+								addValueDependency(key);
+								return new BasicEvaluatedExpression()
+									.setTruthy()
+									.setSideEffects(false)
+									.setRange(/** @type {Range} */ (expr.range));
+							});
 						parser.hooks.evaluateTypeof
-							.for(key)
-							.tap("DefinePlugin", ParserHelpers.evaluateToString("object"));
-						parser.hooks.expression
 							.for(key)
 							.tap(
-								"DefinePlugin",
-								/__webpack_require__/.test(code)
-									? ParserHelpers.toConstantDependencyWithWebpackRequire(
-											parser,
-											code
-									  )
-									: ParserHelpers.toConstantDependency(parser, code)
+								PLUGIN_NAME,
+								withValueDependency(key, evaluateToString("object"))
+							);
+						parser.hooks.collectDestructuringAssignmentProperties.tap(
+							PLUGIN_NAME,
+							(expr) => {
+								const nameInfo = parser.getNameForExpression(expr);
+								if (nameInfo && nameInfo.name === key) return true;
+							}
+						);
+						parser.hooks.expression.for(key).tap(PLUGIN_NAME, (expr) => {
+							const valueDependencies =
+								/** @type {NonNullable} */
+								(
+									/** @type {NormalModuleBuildInfo} */
+									(parser.state.module.buildInfo).valueDependencies
+								);
+							for (const [name, version] of objectValueDependencies) {
+								valueDependencies.set(name, version);
+							}
+							// render the merged view so dotted keys (of any instance)
+							// are part of whole-object and destructured reads
+							const objKeys = getObjKeys(
+								parser.destructuringAssignmentPropertiesFor(expr)
+							);
+							const asiSafe = !parser.isAsiPosition(
+								/** @type {Range} */ (expr.range)[0]
 							);
+							let strCode;
+							if (definition instanceof Map) {
+								const code = stringifyMergedNode(
+									mergedDefinitions,
+									definition,
+									parser,
+									key,
+									objKeys
+								);
+								strCode = asiSafe ? `(${code})` : `;(${code})`;
+							} else {
+								strCode = toCode(
+									definition,
+									parser,
+									compilation.valueCacheVersions,
+									key,
+									runtimeTemplate,
+									logger,
+									asiSafe,
+									objKeys
+								);
+							}
+
+							if (parser.scope.inShorthand) {
+								strCode = `${parser.scope.inShorthand}:${strCode}`;
+							}
+
+							return toConstantDependency(
+								parser,
+								strCode,
+								getRuntimeRequirements(strCode)
+							)(expr);
+						});
+						// A property access not defined on the object resolves to `undefined`
+						// and the whole object is never inlined (issue #15559). Keyed by the
+						// chain root so dotted object keys (e.g. `a.b`) are also covered.
+						const chainParts = key.split(".");
+						// `import.meta` is one chain root (a MetaProperty), not two members.
+						const isMeta =
+							chainParts[0] === "import" && chainParts[1] === "meta";
+						const chainRoot = isMeta ? "import.meta" : chainParts[0];
+						const chainPrefix = chainParts.slice(isMeta ? 2 : 1);
+						/**
+						 * Whether a member chain reads a property that is not defined.
+						 * Walks the merged view so dotted keys of every instance count.
+						 * Collects every define key consulted so the caller can record a
+						 * value dependency on each (a sibling key like `OBJECT.SUB2` affects
+						 * the result even though `OBJECT` registered the handler).
+						 * Inherited members (e.g. `toString`) stay defined.
+						 * @param {Members} members chain members (after the root)
+						 * @param {string[]} deps consulted define keys (mutated)
+						 * @returns {boolean} true when the access resolves to `undefined`
+						 */
+						const isUndefinedMemberAccess = (members, deps) => {
+							if (members.length <= chainPrefix.length) return false;
+							for (let i = 0; i < chainPrefix.length; i++) {
+								if (members[i] !== chainPrefix[i]) return false;
+							}
+							/** @type {MergedDefinitionNode | undefined} */
+							let value = definition;
+							let path = key;
+							for (let i = chainPrefix.length; i < members.length; i++) {
+								const member = members[i];
+								const nextPath = `${path}.${member}`;
+								if (value instanceof Map) {
+									if (value.has(member)) {
+										deps.push(nextPath);
+										value = value.get(member);
+									} else if (member in EMPTY_OBJECT) {
+										return false;
+									} else {
+										return true;
+									}
+								} else if (isObjectDefinition(value)) {
+									// non-expandable object leaf (e.g. an array)
+									if (member in value) {
+										value = /** @type {Definitions} */ (value)[member];
+									} else if (
+										Object.prototype.hasOwnProperty.call(
+											mergedDefinitions.definitions,
+											nextPath
+										)
+									) {
+										// defined via a dotted sibling key, e.g. `OBJECT.SUB2`
+										deps.push(nextPath);
+										value = mergedDefinitions.definitions[nextPath];
+									} else {
+										return true;
+									}
+								} else {
+									// a leaf with members left is a real property access on a value
+									return false;
+								}
+								path = nextPath;
+							}
+							return false;
+						};
+						parser.hooks.expressionMemberChain
+							.for(chainRoot)
+							.tap(PLUGIN_NAME, (expr, members) => {
+								const deps = [key];
+								if (!isUndefinedMemberAccess(members, deps)) return;
+								for (const dep of deps) addValueDependency(dep);
+								return toConstantDependency(parser, "undefined")(expr);
+							});
+						// In a call, replace only the callee so the call itself is kept:
+						// `x.MISSING()` stays a (throwing) call and `x.MISSING?.()`
+						// short-circuits, with the object never inlined.
+						parser.hooks.callMemberChain
+							.for(chainRoot)
+							.tap(PLUGIN_NAME, (expr, members) => {
+								const deps = [key];
+								if (!isUndefinedMemberAccess(members, deps)) return;
+								for (const dep of deps) addValueDependency(dep);
+								toConstantDependency(
+									parser,
+									"undefined"
+								)(/** @type {Expression} */ (expr.callee));
+								parser.walkExpressions(expr.arguments);
+								return true;
+							});
 						parser.hooks.typeof
 							.for(key)
 							.tap(
-								"DefinePlugin",
-								ParserHelpers.toConstantDependency(
-									parser,
-									JSON.stringify("object")
+								PLUGIN_NAME,
+								withValueDependency(
+									key,
+									toConstantDependency(parser, JSON.stringify("object"))
 								)
 							);
 					};
@@ -191,16 +1213,64 @@ class DefinePlugin {
 				};
 
 				normalModuleFactory.hooks.parser
-					.for("javascript/auto")
-					.tap("DefinePlugin", handler);
+					.for(JAVASCRIPT_MODULE_TYPE_AUTO)
+					.tap(PLUGIN_NAME, handler);
 				normalModuleFactory.hooks.parser
-					.for("javascript/dynamic")
-					.tap("DefinePlugin", handler);
+					.for(JAVASCRIPT_MODULE_TYPE_DYNAMIC)
+					.tap(PLUGIN_NAME, handler);
 				normalModuleFactory.hooks.parser
-					.for("javascript/esm")
-					.tap("DefinePlugin", handler);
+					.for(JAVASCRIPT_MODULE_TYPE_ESM)
+					.tap(PLUGIN_NAME, handler);
+
+				/**
+				 * Processes the provided definition.
+				 * @param {Definitions} definitions Definitions map
+				 * @param {string} prefix Prefix string
+				 * @returns {void}
+				 */
+				const walkDefinitionsForValues = (definitions, prefix) => {
+					for (const key of Object.keys(definitions)) {
+						const code = definitions[key];
+						const version = /** @type {string} */ (toCacheVersion(code));
+						const name = VALUE_DEP_PREFIX + prefix + key;
+						mainHash.update(`|${prefix}${key}`);
+						const oldVersion = compilation.valueCacheVersions.get(name);
+						if (oldVersion === undefined) {
+							compilation.valueCacheVersions.set(name, version);
+						} else if (oldVersion !== version) {
+							const warning = new WebpackError(
+								`${PLUGIN_NAME}\nConflicting values for '${prefix + key}'`
+							);
+							warning.details = `'${oldVersion}' !== '${version}'`;
+							warning.hideStack = true;
+							compilation.warnings.push(warning);
+						}
+						if (isObjectDefinition(code)) {
+							walkDefinitionsForValues(
+								/** @type {Definitions} */ (code),
+								`${prefix + key}.`
+							);
+						}
+					}
+				};
+
+				walkDefinitionsForValues(definitions, "");
+
+				compilation.valueCacheVersions.set(
+					VALUE_DEP_MAIN,
+					mainHash.digest("hex").slice(0, 8)
+				);
 			}
 		);
 	}
 }
+
+DefinePlugin.getCompilationHooks = createHooksRegistry(createCompilationHooks);
+
 module.exports = DefinePlugin;
+module.exports.VALUE_DEP_MAIN = VALUE_DEP_MAIN;
+module.exports.VALUE_DEP_PREFIX = VALUE_DEP_PREFIX;
+module.exports.getMergedDefinitionNode = getMergedDefinitionNode;
+module.exports.getRuntimeRequirements = getRuntimeRequirements;
+module.exports.stringifyMergedDefinition = stringifyMergedDefinition;
+module.exports.toPropertyKey = toPropertyKey;
diff --git a/lib/DelegatedModule.js b/lib/DelegatedModule.js
deleted file mode 100644
index 769d030cc5c..00000000000
--- a/lib/DelegatedModule.js
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const { OriginalSource, RawSource } = require("webpack-sources");
-
-const Module = require("./Module");
-const WebpackMissingModule = require("./dependencies/WebpackMissingModule");
-const DelegatedSourceDependency = require("./dependencies/DelegatedSourceDependency");
-const DelegatedExportsDependency = require("./dependencies/DelegatedExportsDependency");
-
-class DelegatedModule extends Module {
-	constructor(sourceRequest, data, type, userRequest, originalRequest) {
-		super("javascript/dynamic", null);
-
-		// Info from Factory
-		this.sourceRequest = sourceRequest;
-		this.request = data.id;
-		this.type = type;
-		this.userRequest = userRequest;
-		this.originalRequest = originalRequest;
-		this.delegateData = data;
-	}
-
-	libIdent(options) {
-		return typeof this.originalRequest === "string"
-			? this.originalRequest
-			: this.originalRequest.libIdent(options);
-	}
-
-	identifier() {
-		return `delegated ${JSON.stringify(this.request)} from ${
-			this.sourceRequest
-		}`;
-	}
-
-	readableIdentifier() {
-		return `delegated ${this.userRequest} from ${this.sourceRequest}`;
-	}
-
-	needRebuild() {
-		return false;
-	}
-
-	build(options, compilation, resolver, fs, callback) {
-		this.built = true;
-		this.buildMeta = Object.assign({}, this.delegateData.buildMeta);
-		this.buildInfo = {};
-		this.addDependency(new DelegatedSourceDependency(this.sourceRequest));
-		this.addDependency(
-			new DelegatedExportsDependency(this, this.delegateData.exports || true)
-		);
-		callback();
-	}
-
-	source(depTemplates, runtime) {
-		const dep = /** @type {DelegatedSourceDependency} */ (this.dependencies[0]);
-		const sourceModule = dep.module;
-		let str;
-
-		if (!sourceModule) {
-			str = WebpackMissingModule.moduleCode(this.sourceRequest);
-		} else {
-			str = `module.exports = (${runtime.moduleExports({
-				module: sourceModule,
-				request: dep.request
-			})})`;
-
-			switch (this.type) {
-				case "require":
-					str += `(${JSON.stringify(this.request)})`;
-					break;
-				case "object":
-					str += `[${JSON.stringify(this.request)}]`;
-					break;
-			}
-
-			str += ";";
-		}
-
-		if (this.useSourceMap) {
-			return new OriginalSource(str, this.identifier());
-		} else {
-			return new RawSource(str);
-		}
-	}
-
-	size() {
-		return 42;
-	}
-
-	updateHash(hash) {
-		hash.update(this.type);
-		hash.update(JSON.stringify(this.request));
-		super.updateHash(hash);
-	}
-}
-
-module.exports = DelegatedModule;
diff --git a/lib/DelegatedModuleFactoryPlugin.js b/lib/DelegatedModuleFactoryPlugin.js
deleted file mode 100644
index 26db0066f75..00000000000
--- a/lib/DelegatedModuleFactoryPlugin.js
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const DelegatedModule = require("./DelegatedModule");
-
-// options.source
-// options.type
-// options.context
-// options.scope
-// options.content
-class DelegatedModuleFactoryPlugin {
-	constructor(options) {
-		this.options = options;
-		options.type = options.type || "require";
-		options.extensions = options.extensions || ["", ".js"];
-	}
-
-	apply(normalModuleFactory) {
-		const scope = this.options.scope;
-		if (scope) {
-			normalModuleFactory.hooks.factory.tap(
-				"DelegatedModuleFactoryPlugin",
-				factory => (data, callback) => {
-					const dependency = data.dependencies[0];
-					const request = dependency.request;
-					if (request && request.indexOf(scope + "/") === 0) {
-						const innerRequest = "." + request.substr(scope.length);
-						let resolved;
-						if (innerRequest in this.options.content) {
-							resolved = this.options.content[innerRequest];
-							return callback(
-								null,
-								new DelegatedModule(
-									this.options.source,
-									resolved,
-									this.options.type,
-									innerRequest,
-									request
-								)
-							);
-						}
-						for (let i = 0; i < this.options.extensions.length; i++) {
-							const extension = this.options.extensions[i];
-							const requestPlusExt = innerRequest + extension;
-							if (requestPlusExt in this.options.content) {
-								resolved = this.options.content[requestPlusExt];
-								return callback(
-									null,
-									new DelegatedModule(
-										this.options.source,
-										resolved,
-										this.options.type,
-										requestPlusExt,
-										request + extension
-									)
-								);
-							}
-						}
-					}
-					return factory(data, callback);
-				}
-			);
-		} else {
-			normalModuleFactory.hooks.module.tap(
-				"DelegatedModuleFactoryPlugin",
-				module => {
-					if (module.libIdent) {
-						const request = module.libIdent(this.options);
-						if (request && request in this.options.content) {
-							const resolved = this.options.content[request];
-							return new DelegatedModule(
-								this.options.source,
-								resolved,
-								this.options.type,
-								request,
-								module
-							);
-						}
-					}
-					return module;
-				}
-			);
-		}
-	}
-}
-module.exports = DelegatedModuleFactoryPlugin;
diff --git a/lib/DelegatedPlugin.js b/lib/DelegatedPlugin.js
deleted file mode 100644
index 714eb8533cd..00000000000
--- a/lib/DelegatedPlugin.js
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-
-"use strict";
-
-const DelegatedModuleFactoryPlugin = require("./DelegatedModuleFactoryPlugin");
-const DelegatedSourceDependency = require("./dependencies/DelegatedSourceDependency");
-const DelegatedExportsDependency = require("./dependencies/DelegatedExportsDependency");
-const NullFactory = require("./NullFactory");
-
-class DelegatedPlugin {
-	constructor(options) {
-		this.options = options;
-	}
-
-	apply(compiler) {
-		compiler.hooks.compilation.tap(
-			"DelegatedPlugin",
-			(compilation, { normalModuleFactory }) => {
-				compilation.dependencyFactories.set(
-					DelegatedSourceDependency,
-					normalModuleFactory
-				);
-				compilation.dependencyFactories.set(
-					DelegatedExportsDependency,
-					new NullFactory()
-				);
-			}
-		);
-
-		compiler.hooks.compile.tap("DelegatedPlugin", ({ normalModuleFactory }) => {
-			new DelegatedModuleFactoryPlugin(this.options).apply(normalModuleFactory);
-		});
-	}
-}
-
-module.exports = DelegatedPlugin;
diff --git a/lib/DependenciesBlock.js b/lib/DependenciesBlock.js
index 77fe91629b0..2f339586671 100644
--- a/lib/DependenciesBlock.js
+++ b/lib/DependenciesBlock.js
@@ -1,45 +1,72 @@
 /*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Tobias Koppers @sokra
- */
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
 "use strict";
 
-const DependenciesBlockVariable = require("./DependenciesBlockVariable");
+const makeSerializable = require("./util/makeSerializable");
 
-/** @typedef {import("./ChunkGroup")} ChunkGroup */
+/** @typedef {import("./AsyncDependenciesBlock")} AsyncDependenciesBlock */
 /** @typedef {import("./Dependency")} Dependency */
+/** @typedef {import("./Dependency").UpdateHashContext} UpdateHashContext */
+/** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */
+/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */
+/** @typedef {import("./util/Hash")} Hash */
+
+/** @typedef {(d: Dependency) => boolean} DependencyFilterFunction */
 
+/**
+ * DependenciesBlock is the base class for all Module classes in webpack. It describes a
+ * "block" of dependencies which are pointers to other DependenciesBlock instances. For example
+ * when a Module has a CommonJs require statement, the DependencyBlock for the CommonJs module
+ * would be added as a dependency to the Module. DependenciesBlock is inherited by two types of classes:
+ * Module subclasses and AsyncDependenciesBlock subclasses. The only difference between the two is that
+ * AsyncDependenciesBlock subclasses are used for code-splitting (async boundary) and Module subclasses are not.
+ */
 class DependenciesBlock {
 	constructor() {
 		/** @type {Dependency[]} */
 		this.dependencies = [];
+		/** @type {AsyncDependenciesBlock[]} */
 		this.blocks = [];
-		this.variables = [];
-		// TODO remove this line, it's wrong
-		/** @type {ChunkGroup=} */
-		this.chunkGroup = undefined;
+		/** @type {DependenciesBlock | undefined} */
+		this.parent = undefined;
 	}
 
+	getRootBlock() {
+		/** @type {DependenciesBlock} */
+		let current = this;
+		while (current.parent) current = current.parent;
+		return current;
+	}
+
+	/**
+	 * Adds a DependencyBlock to DependencyBlock relationship.
+	 * This is used for when a Module has a AsyncDependencyBlock tie (for code-splitting)
+	 * @param {AsyncDependenciesBlock} block block being added
+	 * @returns {void}
+	 */
 	addBlock(block) {
 		this.blocks.push(block);
 		block.parent = this;
 	}
 
-	addVariable(name, expression, dependencies) {
-		for (let v of this.variables) {
-			if (v.name === name && v.expression === expression) {
-				return;
-			}
-		}
-		this.variables.push(
-			new DependenciesBlockVariable(name, expression, dependencies)
-		);
-	}
-
+	/**
+	 * Adds the provided dependency to the dependencies block.
+	 * @param {Dependency} dependency dependency being tied to block.
+	 * This is an "edge" pointing to another "node" on module graph.
+	 * @returns {void}
+	 */
 	addDependency(dependency) {
 		this.dependencies.push(dependency);
 	}
 
+	/**
+	 * Removes dependency.
+	 * @param {Dependency} dependency dependency being removed
+	 * @returns {void}
+	 */
 	removeDependency(dependency) {
 		const idx = this.dependencies.indexOf(dependency);
 		if (idx >= 0) {
@@ -47,45 +74,52 @@ class DependenciesBlock {
 		}
 	}
 
-	updateHash(hash) {
-		for (const dep of this.dependencies) dep.updateHash(hash);
-		for (const block of this.blocks) block.updateHash(hash);
-		for (const variable of this.variables) variable.updateHash(hash);
+	/**
+	 * Clear dependencies and blocks.
+	 * @returns {void}
+	 */
+	clearDependenciesAndBlocks() {
+		this.dependencies.length = 0;
+		this.blocks.length = 0;
 	}
 
-	disconnect() {
-		for (const dep of this.dependencies) dep.disconnect();
-		for (const block of this.blocks) block.disconnect();
-		for (const variable of this.variables) variable.disconnect();
+	/**
+	 * Updates the hash with the data contributed by this instance.
+	 * @param {Hash} hash the hash used to track dependencies
+	 * @param {UpdateHashContext} context context
+	 * @returns {void}
+	 */
+	updateHash(hash, context) {
+		for (const dep of this.dependencies) {
+			dep.updateHash(hash, context);
+		}
+		for (const block of this.blocks) {
+			block.updateHash(hash, context);
+		}
 	}
 
-	unseal() {
-		for (const block of this.blocks) block.unseal();
+	/**
+	 * Serializes this instance into the provided serializer context.
+	 * @param {ObjectSerializerContext} context context
+	 */
+	serialize({ write }) {
+		write(this.dependencies);
+		write(this.blocks);
 	}
 
-	hasDependencies(filter) {
-		if (filter) {
-			for (const dep of this.dependencies) {
-				if (filter(dep)) return true;
-			}
-		} else {
-			if (this.dependencies.length > 0) {
-				return true;
-			}
-		}
-
+	/**
+	 * Restores this instance from the provided deserializer context.
+	 * @param {ObjectDeserializerContext} context context
+	 */
+	deserialize({ read }) {
+		this.dependencies = read();
+		this.blocks = read();
 		for (const block of this.blocks) {
-			if (block.hasDependencies(filter)) return true;
+			block.parent = this;
 		}
-		for (const variable of this.variables) {
-			if (variable.hasDependencies(filter)) return true;
-		}
-		return false;
-	}
-
-	sortItems() {
-		for (const block of this.blocks) block.sortItems();
 	}
 }
 
+makeSerializable(DependenciesBlock, "webpack/lib/DependenciesBlock");
+
 module.exports = DependenciesBlock;
diff --git a/lib/DependenciesBlockVariable.js b/lib/DependenciesBlockVariable.js
deleted file mode 100644
index 7f7416271aa..00000000000
--- a/lib/DependenciesBlockVariable.js
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const { RawSource, ReplaceSource } = require("webpack-sources");
-
-class DependenciesBlockVariable {
-	constructor(name, expression, dependencies) {
-		this.name = name;
-		this.expression = expression;
-		this.dependencies = dependencies || [];
-	}
-
-	updateHash(hash) {
-		hash.update(this.name);
-		hash.update(this.expression);
-		for (const d of this.dependencies) {
-			d.updateHash(hash);
-		}
-	}
-
-	expressionSource(dependencyTemplates, runtimeTemplate) {
-		const source = new ReplaceSource(new RawSource(this.expression));
-		for (const dep of this.dependencies) {
-			const template = dependencyTemplates.get(dep.constructor);
-			if (!template) {
-				throw new Error(`No template for dependency: ${dep.constructor.name}`);
-			}
-			template.apply(dep, source, runtimeTemplate, dependencyTemplates);
-		}
-		return source;
-	}
-
-	disconnect() {
-		for (const d of this.dependencies) {
-			d.disconnect();
-		}
-	}
-
-	hasDependencies(filter) {
-		if (filter) {
-			if (this.dependencies.some(filter)) return true;
-		} else {
-			if (this.dependencies.length > 0) return true;
-		}
-		return false;
-	}
-}
-
-module.exports = DependenciesBlockVariable;
diff --git a/lib/Dependency.js b/lib/Dependency.js
index 6995c798b19..d134de64349 100644
--- a/lib/Dependency.js
+++ b/lib/Dependency.js
@@ -2,61 +2,551 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
-const compareLocations = require("./compareLocations");
-const DependencyReference = require("./dependencies/DependencyReference");
+const { JAVASCRIPT_TYPE } = require("./ModuleSourceTypeConstants");
+const memoize = require("./util/memoize");
+
+/** @typedef {import("./ChunkGraph")} ChunkGraph */
+/** @typedef {import("./DependenciesBlock")} DependenciesBlock */
+/** @typedef {import("./Module")} Module */
+/** @typedef {import("./ModuleGraph")} ModuleGraph */
+/** @typedef {import("./ModuleGraphConnection")} ModuleGraphConnection */
+/** @typedef {import("./Module").SourceType} SourceType */
+/** @typedef {import("./ModuleGraphConnection").ConnectionState} ConnectionState */
+/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */
+/** @typedef {import("./errors/WebpackError")} WebpackError */
+/** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */
+/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */
+/** @typedef {import("./util/Hash")} Hash */
+/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */
+/** @typedef {import("./dependencies/ModuleDependency")} ModuleDependency */
+/**
+ * Defines the update hash context type used by this module.
+ * @typedef {object} UpdateHashContext
+ * @property {ChunkGraph} chunkGraph
+ * @property {RuntimeSpec} runtime
+ * @property {RuntimeTemplate=} runtimeTemplate
+ */
+
+/**
+ * Defines the source position type used by this module.
+ * @typedef {object} SourcePosition
+ * @property {number} line
+ * @property {number=} column
+ */
 
-/** @typedef {Object} Position
- *  @property {number} column
- *  @property {number} line
+/**
+ * Defines the real dependency location type used by this module.
+ * @typedef {object} RealDependencyLocation
+ * @property {SourcePosition} start
+ * @property {SourcePosition=} end
+ * @property {number=} index
  */
 
-/** @typedef {Object} Loc
- *  @property {Position} start
- *  @property {Position} end
+/**
+ * Defines the synthetic dependency location type used by this module.
+ * @typedef {object} SyntheticDependencyLocation
+ * @property {string} name
+ * @property {number=} index
  */
 
+/** @typedef {SyntheticDependencyLocation | RealDependencyLocation} DependencyLocation */
+
+/** @typedef {string} ExportInfoName */
+
+/**
+ * Defines the export spec type used by this module.
+ * @typedef {object} ExportSpec
+ * @property {ExportInfoName} name the name of the export
+ * @property {boolean=} canMangle can the export be renamed (defaults to true)
+ * @property {boolean=} terminalBinding is the export a terminal binding that should be checked for export star conflicts
+ * @property {boolean=} isPure calling this export has no observable side effects
+ * @property {(string | ExportSpec)[]=} exports nested exports
+ * @property {ModuleGraphConnection=} from when reexported: from which module
+ * @property {string[] | null=} export when reexported: from which export
+ * @property {number=} priority when reexported: with which priority
+ * @property {boolean=} hidden export is not visible, because another export blends over it
+ * @property {import("./optimize/InlineExports").InlinedValue=} inlined when set: the export binds to a small primitive constant eligible for inlining
+ */
+
+/** @typedef {Set} ExportsSpecExcludeExports */
+
+/**
+ * Defines the exports spec type used by this module.
+ * @typedef {object} ExportsSpec
+ * @property {(string | ExportSpec)[] | true | null} exports exported names, true for unknown exports or null for no exports
+ * @property {ExportsSpecExcludeExports=} excludeExports when exports = true, list of unaffected exports
+ * @property {(Set | null)=} hideExports list of maybe prior exposed, but now hidden exports
+ * @property {ModuleGraphConnection=} from when reexported: from which module
+ * @property {number=} priority when reexported: with which priority
+ * @property {boolean=} canMangle can the export be renamed (defaults to true)
+ * @property {boolean=} terminalBinding are the exports terminal bindings that should be checked for export star conflicts
+ * @property {boolean=} isPure calling these exports has no observable side effects
+ * @property {Module[]=} dependencies module on which the result depends on
+ */
+
+/**
+ * Defines the referenced export type used by this module.
+ * @typedef {object} ReferencedExport
+ * @property {string[]} name name of the referenced export
+ * @property {boolean=} canMangle when false, referenced export can not be mangled, defaults to true
+ * @property {boolean=} canInline when false, the referenced export can not be substituted with an inlined literal at this site, defaults to true
+ */
+
+/** @typedef {string[][]} RawReferencedExports */
+/** @typedef {(string[] | ReferencedExport)[]} ReferencedExports */
+
+/** @typedef {(moduleGraphConnection: ModuleGraphConnection, runtime: RuntimeSpec) => ConnectionState} GetConditionFn */
+
+/**
+ * Lazy barrel classification of a dependency within a side-effect-free module.
+ * `LAZY_UNTIL_LOCAL`: locally provided export name (`getLazyName`), requesting it requires no dependency.
+ * `LAZY_UNTIL_ID`: named re-export (`getLazyName`) deferred until the export name is requested.
+ * `LAZY_UNTIL_FALLBACK`: star re-export, deferred until an unknown name or all names are requested.
+ * `LAZY_UNTIL_REQUEST`: deferred together with other dependencies of the same request.
+ * @typedef {"local" | "id" | "*" | "@"} LazyUntil
+ */
+
+const LAZY_UNTIL_LOCAL = /** @type {"local"} */ ("local");
+const LAZY_UNTIL_ID = /** @type {"id"} */ ("id");
+const LAZY_UNTIL_FALLBACK = /** @type {"*"} */ ("*");
+const LAZY_UNTIL_REQUEST = /** @type {"@"} */ ("@");
+
+const TRANSITIVE = /** @type {symbol} */ (Symbol("transitive"));
+
+const getIgnoredModule = memoize(() => {
+	const RawModule = require("./RawModule");
+
+	const module = new RawModule("/* (ignored) */", "ignored", "(ignored)");
+	module.factoryMeta = { sideEffectFree: true };
+	return module;
+});
+
 class Dependency {
 	constructor() {
-		this.module = null;
-		// TODO remove in webpack 5
-		this.weak = false;
+		/** @type {Module | undefined} */
+		this._parentModule = undefined;
+		/** @type {DependenciesBlock | undefined} */
+		this._parentDependenciesBlock = undefined;
+		/** @type {number} */
+		this._parentDependenciesBlockIndex = -1;
+		// stays on base: also set on ContextDependency, which is not a ModuleDependency
+		/** @type {boolean | undefined} */
 		this.optional = false;
-		this.loc = undefined;
+		/** @type {number} */
+		this._locSL = 0;
+		/** @type {number} */
+		this._locSC = 0;
+		/** @type {number} */
+		this._locEL = 0;
+		/** @type {number} */
+		this._locEC = 0;
+		/** @type {undefined | number} */
+		this._locI = undefined;
+		/** @type {undefined | string} */
+		this._locN = undefined;
+		/** @type {undefined | DependencyLocation} */
+		this._loc = undefined;
+	}
+
+	/**
+	 * Returns a display name for the type of dependency.
+	 * @returns {string} a display name for the type of dependency
+	 */
+	get type() {
+		return "unknown";
+	}
+
+	/**
+	 * Returns a dependency category, typical categories are "commonjs", "amd", "esm".
+	 * @returns {string} a dependency category, typical categories are "commonjs", "amd", "esm"
+	 */
+	get category() {
+		return "unknown";
+	}
+
+	/**
+	 * Returns the source type this dependency reads from the module it references:
+	 * `javascript` for anything going through the module wrapper, `asset-url` for a
+	 * bare url embedded into non-javascript output (css, html, a manifest).
+	 * @returns {SourceType} the source type read from the referenced module
+	 */
+	get referencedSourceType() {
+		return JAVASCRIPT_TYPE;
+	}
+
+	/**
+	 * Returns location.
+	 * @returns {DependencyLocation} location
+	 */
+	get loc() {
+		if (this._loc !== undefined) return this._loc;
+
+		/** @type {SyntheticDependencyLocation & RealDependencyLocation} */
+		const loc = {};
+
+		if (this._locSL > 0) {
+			loc.start = { line: this._locSL, column: this._locSC };
+		}
+		if (this._locEL > 0) {
+			loc.end = { line: this._locEL, column: this._locEC };
+		}
+		if (this._locN !== undefined) {
+			loc.name = this._locN;
+		}
+		if (this._locI !== undefined) {
+			loc.index = this._locI;
+		}
+
+		return (this._loc = loc);
+	}
+
+	set loc(loc) {
+		if ("start" in loc && typeof loc.start === "object") {
+			this._locSL = loc.start.line || 0;
+			this._locSC = loc.start.column || 0;
+		} else {
+			this._locSL = 0;
+			this._locSC = 0;
+		}
+		if ("end" in loc && typeof loc.end === "object") {
+			this._locEL = loc.end.line || 0;
+			this._locEC = loc.end.column || 0;
+		} else {
+			this._locEL = 0;
+			this._locEC = 0;
+		}
+		this._locI = "index" in loc ? loc.index : undefined;
+		this._locN = "name" in loc ? loc.name : undefined;
+		// Don't retain the passed object; `get loc` rebuilds it from the numbers
+		// above on demand, so dependencies whose loc is never read hold 4 numbers
+		// instead of a SourceLocation + two Position objects.
+		this._loc = undefined;
+	}
+
+	/**
+	 * Updates loc using the provided start line.
+	 * @param {number} startLine start line
+	 * @param {number} startColumn start column
+	 * @param {number} endLine end line
+	 * @param {number} endColumn end column
+	 */
+	setLoc(startLine, startColumn, endLine, endColumn) {
+		this._locSL = startLine;
+		this._locSC = startColumn;
+		this._locEL = endLine;
+		this._locEC = endColumn;
+		this._locI = undefined;
+		this._locN = undefined;
+		this._loc = undefined;
+	}
+
+	/**
+	 * Updates loc from a source location plus an explicit index, without
+	 * materializing the `loc` object (keeps `get loc` lazy). Replaces the
+	 * `dep.loc = Object.create(loc); dep.loc.index = i` pattern, which both
+	 * allocated a copy and stored the index outside the serialized fields.
+	 * @param {DependencyLocation} loc source location (start/end/name read from it)
+	 * @param {number} index dependency index within the statement
+	 */
+	setLocWithIndex(loc, index) {
+		this.loc = loc;
+		this._locI = index;
+	}
+
+	/**
+	 * Compares two dependencies by source location for sorting a module's
+	 * `dependencies`, without materializing the `loc` objects (`get loc` caches
+	 * its result, so comparing through it would retain a location object on every
+	 * sorted dependency). These dependencies always carry a real source position,
+	 * so only start (line, column) and the within-statement index are compared; a
+	 * dependency without an index sorts after one that has an index at the same
+	 * position.
+	 * @param {Dependency} a first dependency
+	 * @param {Dependency} b second dependency
+	 * @returns {-1 | 0 | 1} compare result
+	 */
+	static compareLocations(a, b) {
+		if (a._locSL !== b._locSL) return a._locSL < b._locSL ? -1 : 1;
+		if (a._locSC !== b._locSC) return a._locSC < b._locSC ? -1 : 1;
+		const ai = a._locI;
+		const bi = b._locI;
+		if (ai === bi) return 0;
+		if (ai === undefined) return 1;
+		if (bi === undefined) return -1;
+		return ai < bi ? -1 : 1;
 	}
 
+	/**
+	 * Returns a request context.
+	 * @returns {string | undefined} a request context
+	 */
+	getContext() {
+		return undefined;
+	}
+
+	/**
+	 * Returns an identifier to merge equal requests.
+	 * @returns {string | null} an identifier to merge equal requests
+	 */
 	getResourceIdentifier() {
 		return null;
 	}
 
-	// Returns the referenced module and export
-	getReference() {
-		if (!this.module) return null;
-		return new DependencyReference(this.module, true, this.weak);
+	/**
+	 * Could affect referencing module.
+	 * @returns {boolean | TRANSITIVE} true, when changes to the referenced module could affect the referencing module; TRANSITIVE, when changes to the referenced module could affect referencing modules of the referencing module
+	 */
+	couldAffectReferencingModule() {
+		return TRANSITIVE;
+	}
+
+	/**
+	 * Returns the export name this dependency requests from its target module (lazy barrel optimization).
+	 * @returns {string | true | null} export name, true for all exports, null for none
+	 */
+	getForwardId() {
+		// unknown dependency types conservatively request all exports
+		return true;
+	}
+
+	/**
+	 * Returns how this dependency may be deferred when its parent module is side-effect-free (lazy barrel optimization).
+	 * @returns {LazyUntil | null} lazy classification, null when it must be processed eagerly
+	 */
+	getLazyUntil() {
+		return null;
+	}
+
+	/**
+	 * Returns the export name for a `LAZY_UNTIL_LOCAL`/`LAZY_UNTIL_ID` classification (lazy barrel optimization).
+	 * @returns {string | null} export name, null when not applicable
+	 */
+	getLazyName() {
+		return null;
+	}
+
+	/**
+	 * Whether the lazy barrel currently defers creating this dependency's target module (lazy barrel optimization).
+	 * @returns {boolean} true while deferred, so it must not be processed or rendered
+	 */
+	isLazy() {
+		return false;
+	}
+
+	/**
+	 * Sets whether the lazy barrel defers creating this dependency's target module (lazy barrel optimization).
+	 * @param {boolean} value true to defer, false to create it now
+	 */
+	setLazy(value) {}
+
+	/**
+	 * Returns the referenced module and export
+	 * @deprecated
+	 * @param {ModuleGraph} moduleGraph module graph
+	 * @returns {never} throws error
+	 */
+	getReference(moduleGraph) {
+		throw new Error(
+			"Dependency.getReference was removed in favor of Dependency.getReferencedExports, ModuleGraph.getModule, ModuleGraph.getConnection(), and ModuleGraphConnection.getActiveState(runtime)"
+		);
+	}
+
+	/**
+	 * Returns list of exports referenced by this dependency
+	 * @param {ModuleGraph} moduleGraph module graph
+	 * @param {RuntimeSpec} runtime the runtime for which the module is analysed
+	 * @returns {ReferencedExports} referenced exports
+	 */
+	getReferencedExports(moduleGraph, runtime) {
+		return Dependency.EXPORTS_OBJECT_REFERENCED;
 	}
 
-	// Returns the exported names
-	getExports() {
+	/**
+	 * Returns function to determine if the connection is active.
+	 * @param {ModuleGraph} moduleGraph module graph
+	 * @returns {null | false | GetConditionFn} function to determine if the connection is active
+	 */
+	getCondition(moduleGraph) {
 		return null;
 	}
 
-	getWarnings() {
+	/**
+	 * Returns the exported names
+	 * @param {ModuleGraph} moduleGraph module graph
+	 * @returns {ExportsSpec | undefined} export names
+	 */
+	getExports(moduleGraph) {
+		return undefined;
+	}
+
+	/**
+	 * Returns warnings.
+	 * @param {ModuleGraph} moduleGraph module graph
+	 * @returns {WebpackError[] | null | undefined} warnings
+	 */
+	getWarnings(moduleGraph) {
 		return null;
 	}
 
-	getErrors() {
+	/**
+	 * Returns errors.
+	 * @param {ModuleGraph} moduleGraph module graph
+	 * @returns {WebpackError[] | null | undefined} errors
+	 */
+	getErrors(moduleGraph) {
 		return null;
 	}
 
-	updateHash(hash) {
-		hash.update((this.module && this.module.id) + "");
+	/**
+	 * Updates the hash with the data contributed by this instance.
+	 * @param {Hash} hash hash to be updated
+	 * @param {UpdateHashContext} context context
+	 * @returns {void}
+	 */
+	updateHash(hash, context) {}
+
+	/**
+	 * implement this method to allow the occurrence order plugin to count correctly
+	 * @returns {number} count how often the id is used in this dependency
+	 */
+	getNumberOfIdOccurrences() {
+		return 1;
+	}
+
+	/**
+	 * Gets module evaluation side effects state.
+	 * @param {ModuleGraph} moduleGraph the module graph
+	 * @returns {ConnectionState} how this dependency connects the module to referencing modules
+	 */
+	getModuleEvaluationSideEffectsState(moduleGraph) {
+		return true;
+	}
+
+	/**
+	 * Creates an ignored module.
+	 * @param {string} context context directory
+	 * @returns {Module} ignored module
+	 */
+	createIgnoredModule(context) {
+		return getIgnoredModule();
+	}
+
+	/**
+	 * Returns true if this dependency can be concatenated
+	 * @param {boolean} concatenateCommonJsModules whether optimization.concatenateModules.commonjs is enabled
+	 * @returns {boolean} true if this dependency can be concatenated
+	 */
+	canConcatenate(concatenateCommonJsModules) {
+		return false;
+	}
+
+	/**
+	 * Serializes this instance into the provided serializer context.
+	 * @param {ObjectSerializerContext} context context
+	 */
+	serialize({ write }) {
+		write(this.optional);
+		write(this._locSL);
+		write(this._locSC);
+		write(this._locEL);
+		write(this._locEC);
+		write(this._locI);
+		write(this._locN);
 	}
 
-	disconnect() {
-		this.module = null;
+	/**
+	 * Restores this instance from the provided deserializer context.
+	 * @param {ObjectDeserializerContext} context context
+	 */
+	deserialize({ read }) {
+		this.optional = read();
+		this._locSL = read();
+		this._locSC = read();
+		this._locEL = read();
+		this._locEC = read();
+		this._locI = read();
+		this._locN = read();
 	}
 }
-Dependency.compare = (a, b) => compareLocations(a.loc, b.loc);
+
+/** @type {RawReferencedExports} */
+Dependency.NO_EXPORTS_REFERENCED = [];
+/** @type {RawReferencedExports} */
+Dependency.EXPORTS_OBJECT_REFERENCED = [[]];
+// Like EXPORTS_OBJECT_REFERENCED, but the reference can be rendered as a
+// decoupled namespace object, so the module's exports stay mangleable.
+// Same shape as EXPORTS_OBJECT_REFERENCED; only distinguished by identity.
+/** @type {RawReferencedExports} */
+Dependency.EXPORTS_OBJECT_REFERENCED_MANGLEABLE = [[]];
+
+// TODO remove in webpack 6
+Object.defineProperty(Dependency.prototype, "module", {
+	/**
+	 * Returns throws.
+	 * @deprecated
+	 * @returns {EXPECTED_ANY} throws
+	 */
+	get() {
+		throw new Error(
+			"module property was removed from Dependency (use compilation.moduleGraph.getModule(dependency) instead)"
+		);
+	},
+
+	/**
+	 * Updates module.
+	 * @deprecated
+	 * @returns {never} throws
+	 */
+	set() {
+		throw new Error(
+			"module property was removed from Dependency (use compilation.moduleGraph.updateModule(dependency, module) instead)"
+		);
+	}
+});
+
+/**
+ * Returns true if the dependency is a low priority dependency.
+ * @param {Dependency} dependency dep
+ * @returns {boolean} true if the dependency is a low priority dependency
+ */
+Dependency.isLowPriorityDependency = (dependency) =>
+	/** @type {ModuleDependency} */ (dependency).sourceOrder === Infinity;
+
+// TODO in webpack 6, call canConcatenate() directly on the dependency instance instead of using this static method.
+/**
+ * Returns true if the dependency can be concatenated (scope hoisting).
+ * @param {Dependency} dependency dep
+ * @param {boolean} concatenateCommonJsModules whether optimization.concatenateModules.commonjs is enabled
+ * @returns {boolean} true if this dependency supports concatenation
+ */
+Dependency.canConcatenate = (dependency, concatenateCommonJsModules) => {
+	if (typeof dependency.canConcatenate === "function") {
+		return dependency.canConcatenate(concatenateCommonJsModules);
+	}
+	return false;
+};
+
+// TODO remove in webpack 6
+Object.defineProperty(Dependency.prototype, "disconnect", {
+	/**
+	 * Returns throws.
+	 * @deprecated
+	 * @returns {EXPECTED_ANY} throws
+	 */
+	get() {
+		throw new Error(
+			"disconnect was removed from Dependency (Dependency no longer carries graph specific information)"
+		);
+	}
+});
+
+Dependency.TRANSITIVE = TRANSITIVE;
+Dependency.LAZY_UNTIL_LOCAL = LAZY_UNTIL_LOCAL;
+Dependency.LAZY_UNTIL_ID = LAZY_UNTIL_ID;
+Dependency.LAZY_UNTIL_FALLBACK = LAZY_UNTIL_FALLBACK;
+Dependency.LAZY_UNTIL_REQUEST = LAZY_UNTIL_REQUEST;
 
 module.exports = Dependency;
diff --git a/lib/DependencyTemplate.js b/lib/DependencyTemplate.js
new file mode 100644
index 00000000000..79fd71a418f
--- /dev/null
+++ b/lib/DependencyTemplate.js
@@ -0,0 +1,77 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
+/** @typedef {import("./ChunkGraph")} ChunkGraph */
+/** @typedef {import("./CodeGenerationResults")} CodeGenerationResults */
+/** @typedef {import("./ConcatenationScope")} ConcatenationScope */
+/** @typedef {import("./Dependency")} Dependency */
+/** @typedef {import("./Dependency").RuntimeSpec} RuntimeSpec */
+/** @typedef {import("./DependencyTemplates")} DependencyTemplates */
+/** @typedef {import("./Generator").GenerateContext} GenerateContext */
+/** @typedef {import("./Module")} Module */
+/** @typedef {import("./Module").RuntimeRequirements} RuntimeRequirements */
+/** @typedef {import("./ModuleGraph")} ModuleGraph */
+/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */
+
+/**
+ * Defines the init fragment type used by this module.
+ * @template T
+ * @typedef {import("./InitFragment")} InitFragment
+ */
+
+/**
+ * Defines the dependency template context type used by this module.
+ * @typedef {object} DependencyTemplateContext
+ * @property {RuntimeTemplate} runtimeTemplate the runtime template
+ * @property {DependencyTemplates} dependencyTemplates the dependency templates
+ * @property {ModuleGraph} moduleGraph the module graph
+ * @property {ChunkGraph} chunkGraph the chunk graph
+ * @property {RuntimeRequirements} runtimeRequirements the requirements for runtime
+ * @property {Module} module current module
+ * @property {RuntimeSpec} runtime current runtimes, for which code is generated
+ * @property {InitFragment[]} initFragments mutable array of init fragments for the current module
+ * @property {ConcatenationScope=} concatenationScope when in a concatenated module, information about other concatenated modules
+ * @property {CodeGenerationResults} codeGenerationResults the code generation results
+ * @property {InitFragment[]} chunkInitFragments chunkInitFragments
+ */
+
+/**
+ * Defines the css dependency template context extras type used by this module.
+ * @typedef {object} CssDependencyTemplateContextExtras
+ * @property {CssData} cssData the css exports data
+ * @property {string} type the css exports data
+ */
+
+/**
+ * Defines the css data type used by this module.
+ * @typedef {object} CssData
+ * @property {boolean} esModule whether export __esModule
+ * @property {Map} exports the css exports
+ * @property {Map=} exportLocs source position (line is 1-based, column is 0-based) of each export's defining identifier in the original CSS, used to emit fine-grained JS-to-CSS source mappings
+ */
+
+/** @typedef {DependencyTemplateContext & CssDependencyTemplateContextExtras} CssDependencyTemplateContext */
+
+class DependencyTemplate {
+	/* istanbul ignore next */
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @abstract
+	 * @param {Dependency} dependency the dependency for which the template should be applied
+	 * @param {ReplaceSource} source the current replace source which can be modified
+	 * @param {DependencyTemplateContext} templateContext the context object
+	 * @returns {void}
+	 */
+	apply(dependency, source, templateContext) {
+		const AbstractMethodError = require("./errors/AbstractMethodError");
+
+		throw new AbstractMethodError();
+	}
+}
+
+module.exports = DependencyTemplate;
diff --git a/lib/DependencyTemplates.js b/lib/DependencyTemplates.js
new file mode 100644
index 00000000000..7f174b2eb78
--- /dev/null
+++ b/lib/DependencyTemplates.js
@@ -0,0 +1,71 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const { DEFAULTS } = require("./config/defaults");
+const createHash = require("./util/createHash");
+
+/** @typedef {import("./Compilation").DependencyConstructor} DependencyConstructor */
+/** @typedef {import("./DependencyTemplate")} DependencyTemplate */
+/** @typedef {import("./util/Hash").HashFunction} HashFunction */
+
+class DependencyTemplates {
+	/**
+	 * Creates an instance of DependencyTemplates.
+	 * @param {HashFunction} hashFunction the hash function to use
+	 */
+	constructor(hashFunction = DEFAULTS.HASH_FUNCTION) {
+		/** @type {Map} */
+		this._map = new Map();
+		/** @type {string} */
+		this._hash = "31d6cfe0d16ae931b73c59d7e0c089c0";
+		/** @type {HashFunction} */
+		this._hashFunction = hashFunction;
+	}
+
+	/**
+	 * Returns template for this dependency.
+	 * @param {DependencyConstructor} dependency Constructor of Dependency
+	 * @returns {DependencyTemplate | undefined} template for this dependency
+	 */
+	get(dependency) {
+		return this._map.get(dependency);
+	}
+
+	/**
+	 * Updates value using the provided dependency.
+	 * @param {DependencyConstructor} dependency Constructor of Dependency
+	 * @param {DependencyTemplate} dependencyTemplate template for this dependency
+	 * @returns {void}
+	 */
+	set(dependency, dependencyTemplate) {
+		this._map.set(dependency, dependencyTemplate);
+	}
+
+	/**
+	 * Updates the hash with the data contributed by this instance.
+	 * @param {string} part additional hash contributor
+	 * @returns {void}
+	 */
+	updateHash(part) {
+		const hash = createHash(this._hashFunction);
+		hash.update(`${this._hash}${part}`);
+		this._hash = hash.digest("hex");
+	}
+
+	getHash() {
+		return this._hash;
+	}
+
+	clone() {
+		const newInstance = new DependencyTemplates(this._hashFunction);
+		newInstance._map = new Map(this._map);
+		newInstance._hash = this._hash;
+		return newInstance;
+	}
+}
+
+module.exports = DependencyTemplates;
diff --git a/lib/DllEntryPlugin.js b/lib/DllEntryPlugin.js
deleted file mode 100644
index dbb1a40b3ab..00000000000
--- a/lib/DllEntryPlugin.js
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const DllEntryDependency = require("./dependencies/DllEntryDependency");
-const SingleEntryDependency = require("./dependencies/SingleEntryDependency");
-const DllModuleFactory = require("./DllModuleFactory");
-
-class DllEntryPlugin {
-	constructor(context, entries, name) {
-		this.context = context;
-		this.entries = entries;
-		this.name = name;
-	}
-
-	apply(compiler) {
-		compiler.hooks.compilation.tap(
-			"DllEntryPlugin",
-			(compilation, { normalModuleFactory }) => {
-				const dllModuleFactory = new DllModuleFactory();
-				compilation.dependencyFactories.set(
-					DllEntryDependency,
-					dllModuleFactory
-				);
-				compilation.dependencyFactories.set(
-					SingleEntryDependency,
-					normalModuleFactory
-				);
-			}
-		);
-		compiler.hooks.make.tapAsync("DllEntryPlugin", (compilation, callback) => {
-			compilation.addEntry(
-				this.context,
-				new DllEntryDependency(
-					this.entries.map((e, idx) => {
-						const dep = new SingleEntryDependency(e);
-						dep.loc = `${this.name}:${idx}`;
-						return dep;
-					}),
-					this.name
-				),
-				this.name,
-				callback
-			);
-		});
-	}
-}
-
-module.exports = DllEntryPlugin;
diff --git a/lib/DllModule.js b/lib/DllModule.js
deleted file mode 100644
index c0a45d731ea..00000000000
--- a/lib/DllModule.js
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-	*/
-"use strict";
-
-const { RawSource } = require("webpack-sources");
-const Module = require("./Module");
-
-class DllModule extends Module {
-	constructor(context, dependencies, name, type) {
-		super("javascript/dynamic", context);
-
-		// Info from Factory
-		this.dependencies = dependencies;
-		this.name = name;
-		this.type = type;
-	}
-
-	identifier() {
-		return `dll ${this.name}`;
-	}
-
-	readableIdentifier() {
-		return `dll ${this.name}`;
-	}
-
-	build(options, compilation, resolver, fs, callback) {
-		this.built = true;
-		this.buildMeta = {};
-		this.buildInfo = {};
-		return callback();
-	}
-
-	source() {
-		return new RawSource("module.exports = __webpack_require__;");
-	}
-
-	needRebuild() {
-		return false;
-	}
-
-	size() {
-		return 12;
-	}
-
-	updateHash(hash) {
-		hash.update("dll module");
-		hash.update(this.name || "");
-		super.updateHash(hash);
-	}
-}
-
-module.exports = DllModule;
diff --git a/lib/DllModuleFactory.js b/lib/DllModuleFactory.js
deleted file mode 100644
index f5d12ddc4be..00000000000
--- a/lib/DllModuleFactory.js
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const { Tapable } = require("tapable");
-const DllModule = require("./DllModule");
-
-class DllModuleFactory extends Tapable {
-	constructor() {
-		super();
-		this.hooks = {};
-	}
-	create(data, callback) {
-		const dependency = data.dependencies[0];
-		callback(
-			null,
-			new DllModule(
-				data.context,
-				dependency.dependencies,
-				dependency.name,
-				dependency.type
-			)
-		);
-	}
-}
-
-module.exports = DllModuleFactory;
diff --git a/lib/DllPlugin.js b/lib/DllPlugin.js
deleted file mode 100644
index 5542833719b..00000000000
--- a/lib/DllPlugin.js
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-	*/
-"use strict";
-
-const DllEntryPlugin = require("./DllEntryPlugin");
-const LibManifestPlugin = require("./LibManifestPlugin");
-const FlagInitialModulesAsUsedPlugin = require("./FlagInitialModulesAsUsedPlugin");
-
-const validateOptions = require("schema-utils");
-const schema = require("../schemas/plugins/DllPlugin.json");
-
-class DllPlugin {
-	constructor(options) {
-		validateOptions(schema, options, "Dll Plugin");
-		this.options = options;
-	}
-
-	apply(compiler) {
-		compiler.hooks.entryOption.tap("DllPlugin", (context, entry) => {
-			const itemToPlugin = (item, name) => {
-				if (Array.isArray(item)) {
-					return new DllEntryPlugin(context, item, name);
-				}
-				throw new Error("DllPlugin: supply an Array as entry");
-			};
-			if (typeof entry === "object" && !Array.isArray(entry)) {
-				Object.keys(entry).forEach(name => {
-					itemToPlugin(entry[name], name).apply(compiler);
-				});
-			} else {
-				itemToPlugin(entry, "main").apply(compiler);
-			}
-			return true;
-		});
-		new LibManifestPlugin(this.options).apply(compiler);
-		if (!this.options.entryOnly) {
-			new FlagInitialModulesAsUsedPlugin("DllPlugin").apply(compiler);
-		}
-	}
-}
-
-module.exports = DllPlugin;
diff --git a/lib/DllReferencePlugin.js b/lib/DllReferencePlugin.js
deleted file mode 100644
index b8cf3013be3..00000000000
--- a/lib/DllReferencePlugin.js
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const parseJson = require("json-parse-better-errors");
-const DelegatedSourceDependency = require("./dependencies/DelegatedSourceDependency");
-const DelegatedModuleFactoryPlugin = require("./DelegatedModuleFactoryPlugin");
-const ExternalModuleFactoryPlugin = require("./ExternalModuleFactoryPlugin");
-const DelegatedExportsDependency = require("./dependencies/DelegatedExportsDependency");
-const NullFactory = require("./NullFactory");
-
-const validateOptions = require("schema-utils");
-const schema = require("../schemas/plugins/DllReferencePlugin.json");
-
-class DllReferencePlugin {
-	constructor(options) {
-		validateOptions(schema, options, "Dll Reference Plugin");
-		this.options = options;
-	}
-
-	apply(compiler) {
-		compiler.hooks.compilation.tap(
-			"DllReferencePlugin",
-			(compilation, { normalModuleFactory }) => {
-				compilation.dependencyFactories.set(
-					DelegatedSourceDependency,
-					normalModuleFactory
-				);
-				compilation.dependencyFactories.set(
-					DelegatedExportsDependency,
-					new NullFactory()
-				);
-			}
-		);
-
-		compiler.hooks.beforeCompile.tapAsync(
-			"DllReferencePlugin",
-			(params, callback) => {
-				const manifest = this.options.manifest;
-				if (typeof manifest === "string") {
-					params.compilationDependencies.add(manifest);
-					compiler.inputFileSystem.readFile(manifest, (err, result) => {
-						if (err) return callback(err);
-						params["dll reference " + manifest] = parseJson(
-							result.toString("utf-8")
-						);
-						return callback();
-					});
-				} else {
-					return callback();
-				}
-			}
-		);
-
-		compiler.hooks.compile.tap("DllReferencePlugin", params => {
-			let manifest = this.options.manifest;
-			if (typeof manifest === "string") {
-				manifest = params["dll reference " + manifest];
-			}
-			const name = this.options.name || manifest.name;
-			const sourceType =
-				this.options.sourceType || (manifest && manifest.type) || "var";
-			const externals = {};
-			const source = "dll-reference " + name;
-			externals[source] = name;
-			const normalModuleFactory = params.normalModuleFactory;
-			new ExternalModuleFactoryPlugin(sourceType, externals).apply(
-				normalModuleFactory
-			);
-			new DelegatedModuleFactoryPlugin({
-				source: source,
-				type: this.options.type,
-				scope: this.options.scope,
-				context: this.options.context || compiler.options.context,
-				content: this.options.content || manifest.content,
-				extensions: this.options.extensions
-			}).apply(normalModuleFactory);
-		});
-	}
-}
-
-module.exports = DllReferencePlugin;
diff --git a/lib/DotenvPlugin.js b/lib/DotenvPlugin.js
new file mode 100644
index 00000000000..f5fa52a0de8
--- /dev/null
+++ b/lib/DotenvPlugin.js
@@ -0,0 +1,473 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Natsu @xiaoxiaojx
+*/
+
+"use strict";
+
+const FileSystemInfo = require("./FileSystemInfo");
+const { join } = require("./util/fs");
+
+/** @typedef {import("../declarations/WebpackOptions").DotenvPluginOptions} DotenvPluginOptions */
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./CacheFacade").ItemCacheFacade} ItemCacheFacade */
+/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
+/** @typedef {import("./FileSystemInfo").Snapshot} Snapshot */
+
+/** @typedef {Exclude} Prefix */
+/** @typedef {Record} Env */
+
+const DEFAULT_TEMPLATE = [
+	".env",
+	".env.local",
+	".env.[mode]",
+	".env.[mode].local"
+];
+
+// Regex for parsing .env files
+// ported from https://github.com/motdotla/dotenv/blob/master/lib/main.js#L49
+const LINE =
+	/^\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?$/gm;
+
+const PLUGIN_NAME = "DotenvPlugin";
+
+/**
+ * Parse .env file content
+ * ported from https://github.com/motdotla/dotenv/blob/master/lib/main.js#L49
+ * @param {string | Buffer} src the source content to parse
+ * @returns {Env} parsed environment variables object
+ */
+function parse(src) {
+	const obj = /** @type {Env} */ (Object.create(null));
+
+	// Convert buffer to string
+	let lines = src.toString();
+
+	// Convert line breaks to same format
+	lines = lines.replace(/\r\n?/g, "\n");
+
+	/** @type {null | RegExpExecArray} */
+	let match;
+
+	while ((match = LINE.exec(lines)) !== null) {
+		const key = match[1];
+
+		// Default undefined or null to empty string
+		let value = match[2] || "";
+
+		// Remove whitespace
+		value = value.trim();
+
+		// Check if double quoted
+		const maybeQuote = value[0];
+
+		// Remove surrounding quotes
+		value = value.replace(/^(['"`])([\s\S]*)\1$/gm, "$2");
+
+		// Expand newlines if double quoted
+		if (maybeQuote === '"') {
+			value = value.replace(/\\n/g, "\n");
+			value = value.replace(/\\r/g, "\r");
+		}
+
+		// Add to object
+		obj[key] = value;
+	}
+
+	return obj;
+}
+
+/**
+ * Resolve escape sequences
+ * ported from https://github.com/motdotla/dotenv-expand
+ * @param {string} value value to resolve
+ * @returns {string} resolved value
+ */
+function _resolveEscapeSequences(value) {
+	return value.replace(/\\\$/g, "$");
+}
+
+/**
+ * Expand environment variable value
+ * ported from https://github.com/motdotla/dotenv-expand
+ * @param {string} value value to expand
+ * @param {Record} processEnv process.env object
+ * @param {Env} runningParsed running parsed object
+ * @returns {string} expanded value
+ */
+function expandValue(value, processEnv, runningParsed) {
+	const env = { ...runningParsed, ...processEnv }; // process.env wins
+
+	const regex = /(?} */
+	const seen = new Set(); // self-referential checker
+
+	while ((match = regex.exec(result)) !== null) {
+		seen.add(result);
+
+		const [template, bracedExpression, unbracedExpression] = match;
+		const expression = bracedExpression || unbracedExpression;
+
+		// match the operators `:+`, `+`, `:-`, and `-`
+		const opRegex = /(:\+|\+|:-|-)/;
+		// find first match
+		const opMatch = expression.match(opRegex);
+		const splitter = opMatch ? opMatch[0] : null;
+
+		const r = expression.split(/** @type {string} */ (splitter));
+		// const r = splitter ? expression.split(splitter) : [expression];
+
+		/** @type {string} */
+		let defaultValue;
+		/** @type {undefined | null | string} */
+		let value;
+
+		const key = r.shift();
+
+		if ([":+", "+"].includes(splitter || "")) {
+			defaultValue = env[key || ""] ? r.join(splitter || "") : "";
+			value = null;
+		} else {
+			defaultValue = r.join(splitter || "");
+			value = env[key || ""];
+		}
+
+		if (value) {
+			// self-referential check
+			result = seen.has(value)
+				? result.replace(template, defaultValue)
+				: result.replace(template, value);
+		} else {
+			result = result.replace(template, defaultValue);
+		}
+
+		// if the result equaled what was in process.env and runningParsed then stop expanding
+		if (result === runningParsed[key || ""]) {
+			break;
+		}
+
+		regex.lastIndex = 0; // reset regex search position to re-evaluate after each replacement
+	}
+
+	return result;
+}
+
+/**
+ * Expand environment variables in parsed object
+ * ported from https://github.com/motdotla/dotenv-expand
+ * @param {{ parsed: Env, processEnv: Record }} options expand options
+ * @returns {{ parsed: Env }} expanded options
+ */
+function expand(options) {
+	// for use with progressive expansion
+	const runningParsed = /** @type {Env} */ (Object.create(null));
+	const processEnv = options.processEnv;
+
+	// dotenv.config() ran before this so the assumption is process.env has already been set
+	for (const key in options.parsed) {
+		let value = options.parsed[key];
+
+		// short-circuit scenario: process.env was already set prior to the file value
+		value =
+			Object.prototype.hasOwnProperty.call(processEnv, key) &&
+			processEnv[key] !== value
+				? /** @type {string} */ (processEnv[key])
+				: expandValue(value, processEnv, runningParsed);
+
+		const resolvedValue = _resolveEscapeSequences(value);
+
+		options.parsed[key] = resolvedValue;
+		// for use with progressive expansion
+		runningParsed[key] = resolvedValue;
+	}
+
+	// Part of `dotenv-expand` code, but we don't need it because of we don't modify `process.env`
+	// for (const processKey in options.parsed) {
+	// 	if (processEnv) {
+	// 		processEnv[processKey] = options.parsed[processKey];
+	// 	}
+	// }
+
+	return options;
+}
+
+/**
+ * Format environment variables as DefinePlugin definitions
+ * @param {Env} env environment variables
+ * @returns {Record} formatted definitions
+ */
+const envToDefinitions = (env) => {
+	const definitions = /** @type {Record} */ ({});
+
+	for (const [key, value] of Object.entries(env)) {
+		const defValue = JSON.stringify(value);
+		definitions[`process.env.${key}`] = defValue;
+		definitions[`import.meta.env.${key}`] = defValue;
+	}
+
+	return definitions;
+};
+
+class DotenvPlugin {
+	/**
+	 * Creates an instance of DotenvPlugin.
+	 * @param {DotenvPluginOptions=} options options object
+	 */
+	constructor(options = {}) {
+		/** @type {DotenvPluginOptions} */
+		this.options = options;
+	}
+
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
+	apply(compiler) {
+		compiler.hooks.validate.tap(PLUGIN_NAME, () => {
+			compiler.validate(
+				() => {
+					const { definitions } = require("../schemas/WebpackOptions.json");
+
+					return {
+						definitions,
+						oneOf: [{ $ref: "#/definitions/DotenvPluginOptions" }]
+					};
+				},
+				this.options,
+				{
+					name: "Dotenv Plugin",
+					baseDataPath: "options"
+				}
+			);
+		});
+		const definePlugin = new compiler.webpack.DefinePlugin({});
+		const prefixes = Array.isArray(this.options.prefix)
+			? this.options.prefix
+			: [this.options.prefix || "WEBPACK_"];
+		/** @type {string | false} */
+		const dir =
+			typeof this.options.dir === "string"
+				? this.options.dir
+				: typeof this.options.dir === "undefined"
+					? compiler.context
+					: this.options.dir;
+
+		/** @type {undefined | Snapshot} */
+		let snapshot;
+
+		const cache = compiler.getCache(PLUGIN_NAME);
+		const identifier = JSON.stringify(
+			this.options.template || DEFAULT_TEMPLATE
+		);
+		const itemCache = cache.getItemCache(identifier, null);
+
+		compiler.hooks.beforeCompile.tapPromise(PLUGIN_NAME, async () => {
+			const { parsed, snapshot: newSnapshot } = dir
+				? await this._loadEnv(compiler, itemCache, dir)
+				: { parsed: {} };
+			const env = this._getEnv(prefixes, parsed);
+
+			definePlugin.definitions = envToDefinitions(env || {});
+			snapshot = newSnapshot;
+		});
+
+		compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
+			if (snapshot) {
+				compilation.fileDependencies.addAll(snapshot.getFileIterable());
+				compilation.missingDependencies.addAll(snapshot.getMissingIterable());
+			}
+		});
+
+		definePlugin.apply(compiler);
+	}
+
+	/**
+	 * Get list of env files to load based on mode and template
+	 * Similar to Vite's getEnvFilesForMode
+	 * @private
+	 * @param {InputFileSystem} inputFileSystem the input file system
+	 * @param {string | false} dir the directory containing .env files
+	 * @param {string | undefined} mode the mode (e.g., 'production', 'development')
+	 * @returns {string[]} array of file paths to load
+	 */
+	_getEnvFilesForMode(inputFileSystem, dir, mode) {
+		if (!dir) {
+			return [];
+		}
+
+		const templates = this.options.template || DEFAULT_TEMPLATE;
+
+		return templates
+			.map((pattern) => pattern.replace(/\[mode\]/g, mode || "development"))
+			.map((file) => join(inputFileSystem, dir, file));
+	}
+
+	/**
+	 * Get parsed env variables from `.env` files
+	 * @private
+	 * @param {InputFileSystem} fs input file system
+	 * @param {string} dir dir to load `.env` files
+	 * @param {string} mode mode
+	 * @returns {Promise<{ parsed: Env, fileDependencies: string[], missingDependencies: string[] }>} parsed env variables and dependencies
+	 */
+	async _getParsed(fs, dir, mode) {
+		/** @type {string[]} */
+		const fileDependencies = [];
+		/** @type {string[]} */
+		const missingDependencies = [];
+
+		// Get env files to load
+		const envFiles = this._getEnvFilesForMode(fs, dir, mode);
+
+		// Read all files
+		const contents = await Promise.all(
+			envFiles.map((filePath) =>
+				this._loadFile(fs, filePath).then(
+					(content) => {
+						fileDependencies.push(filePath);
+						return content;
+					},
+					() => {
+						// File doesn't exist, add to missingDependencies (this is normal)
+						missingDependencies.push(filePath);
+						return "";
+					}
+				)
+			)
+		);
+
+		// Parse all files and merge (later files override earlier ones)
+		// Similar to Vite's implementation
+		const parsed = /** @type {Env} */ (Object.create(null));
+
+		for (const content of contents) {
+			if (!content) continue;
+			const entries = parse(content);
+			for (const key in entries) {
+				parsed[key] = entries[key];
+			}
+		}
+
+		return { parsed, fileDependencies, missingDependencies };
+	}
+
+	/**
+	 * Loads the provided compiler.
+	 * @private
+	 * @param {Compiler} compiler compiler
+	 * @param {ItemCacheFacade} itemCache item cache facade
+	 * @param {string} dir directory to read
+	 * @returns {Promise<{ parsed: Env, snapshot: Snapshot }>} parsed result and snapshot
+	 */
+	async _loadEnv(compiler, itemCache, dir) {
+		const fs = /** @type {InputFileSystem} */ (compiler.inputFileSystem);
+		const fileSystemInfo = new FileSystemInfo(fs, {
+			unmanagedPaths: compiler.unmanagedPaths,
+			managedPaths: compiler.managedPaths,
+			immutablePaths: compiler.immutablePaths,
+			hashFunction: compiler.options.output.hashFunction
+		});
+
+		const result = await itemCache.getPromise();
+
+		if (result) {
+			const isSnapshotValid = await new Promise((resolve, reject) => {
+				fileSystemInfo.checkSnapshotValid(result.snapshot, (error, isValid) => {
+					if (error) {
+						reject(error);
+
+						return;
+					}
+
+					resolve(isValid);
+				});
+			});
+
+			if (isSnapshotValid) {
+				return { parsed: result.parsed, snapshot: result.snapshot };
+			}
+		}
+
+		const { parsed, fileDependencies, missingDependencies } =
+			await this._getParsed(
+				fs,
+				dir,
+				/** @type {string} */
+				(compiler.options.mode)
+			);
+
+		const startTime = Date.now();
+		const newSnapshot = await new Promise((resolve, reject) => {
+			fileSystemInfo.createSnapshot(
+				startTime,
+				fileDependencies,
+				null,
+				missingDependencies,
+				// `.env` files are build dependencies
+				compiler.options.snapshot.buildDependencies,
+				(err, snapshot) => {
+					if (err) return reject(err);
+					resolve(snapshot);
+				}
+			);
+		});
+
+		await itemCache.storePromise({ parsed, snapshot: newSnapshot });
+
+		return { parsed, snapshot: newSnapshot };
+	}
+
+	/**
+	 * Generate env variables
+	 * @private
+	 * @param {Prefix} prefixes expose only environment variables that start with these prefixes
+	 * @param {Env} parsed parsed env variables
+	 * @returns {Env} env variables
+	 */
+	_getEnv(prefixes, parsed) {
+		// Always expand environment variables (like Vite does)
+		// Make a copy of process.env so that dotenv-expand doesn't modify global process.env
+		const processEnv = { ...process.env };
+		expand({ parsed, processEnv });
+		const env = /** @type {Env} */ (Object.create(null));
+
+		// Get all keys from parser and process.env
+		const keys = [...Object.keys(parsed), ...Object.keys(process.env)];
+
+		// Prioritize actual env variables from `process.env`, fallback to parsed
+		for (const key of keys) {
+			if (prefixes.some((prefix) => key.startsWith(prefix))) {
+				env[key] =
+					Object.prototype.hasOwnProperty.call(process.env, key) &&
+					process.env[key]
+						? process.env[key]
+						: parsed[key];
+			}
+		}
+
+		return env;
+	}
+
+	/**
+	 * Load a file with proper path resolution
+	 * @private
+	 * @param {InputFileSystem} fs the input file system
+	 * @param {string} file the file to load
+	 * @returns {Promise} the content of the file
+	 */
+	_loadFile(fs, file) {
+		return new Promise((resolve, reject) => {
+			fs.readFile(file, (err, content) => {
+				if (err) reject(err);
+				else resolve(/** @type {Buffer} */ (content).toString() || "");
+			});
+		});
+	}
+}
+
+module.exports = DotenvPlugin;
diff --git a/lib/DynamicEntryPlugin.js b/lib/DynamicEntryPlugin.js
index 0f1fabf0e8b..a601b852f9b 100644
--- a/lib/DynamicEntryPlugin.js
+++ b/lib/DynamicEntryPlugin.js
@@ -2,72 +2,94 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Naoyuki Kanezawa @nkzawa
 */
+
 "use strict";
 
-const MultiEntryDependency = require("./dependencies/MultiEntryDependency");
-const SingleEntryDependency = require("./dependencies/SingleEntryDependency");
-const MultiModuleFactory = require("./MultiModuleFactory");
-const MultiEntryPlugin = require("./MultiEntryPlugin");
-const SingleEntryPlugin = require("./SingleEntryPlugin");
+const EntryOptionPlugin = require("./EntryOptionPlugin");
+const EntryPlugin = require("./EntryPlugin");
+const EntryDependency = require("./dependencies/EntryDependency");
+
+/** @typedef {import("../declarations/WebpackOptions").EntryDescriptionNormalized} EntryDescriptionNormalized */
+/** @typedef {import("../declarations/WebpackOptions").EntryStatic} EntryStatic */
+/** @typedef {import("../declarations/WebpackOptions").EntryStaticNormalized} EntryStaticNormalized */
+/** @typedef {import("./Compiler")} Compiler */
+
+const PLUGIN_NAME = "DynamicEntryPlugin";
+
+/** @typedef {() => EntryStatic | Promise} RawEntryDynamic */
+/** @typedef {() => Promise} EntryDynamic */
 
 class DynamicEntryPlugin {
+	/**
+	 * Creates an instance of DynamicEntryPlugin.
+	 * @param {string} context the context path
+	 * @param {EntryDynamic} entry the entry value
+	 */
 	constructor(context, entry) {
+		/** @type {string} */
 		this.context = context;
+		/** @type {EntryDynamic} */
 		this.entry = entry;
 	}
 
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
 	apply(compiler) {
 		compiler.hooks.compilation.tap(
-			"DynamicEntryPlugin",
+			PLUGIN_NAME,
 			(compilation, { normalModuleFactory }) => {
-				const multiModuleFactory = new MultiModuleFactory();
-
 				compilation.dependencyFactories.set(
-					MultiEntryDependency,
-					multiModuleFactory
-				);
-				compilation.dependencyFactories.set(
-					SingleEntryDependency,
+					EntryDependency,
 					normalModuleFactory
 				);
 			}
 		);
 
-		compiler.hooks.make.tapAsync(
-			"DynamicEntryPlugin",
-			(compilation, callback) => {
-				const addEntry = (entry, name) => {
-					const dep = DynamicEntryPlugin.createDependency(entry, name);
-					return new Promise((resolve, reject) => {
-						compilation.addEntry(this.context, dep, name, err => {
-							if (err) return reject(err);
-							resolve();
-						});
-					});
-				};
-
-				Promise.resolve(this.entry()).then(entry => {
-					if (typeof entry === "string" || Array.isArray(entry)) {
-						addEntry(entry, "main").then(() => callback(), callback);
-					} else if (typeof entry === "object") {
-						Promise.all(
-							Object.keys(entry).map(name => {
-								return addEntry(entry[name], name);
-							})
-						).then(() => callback(), callback);
+		compiler.hooks.make.tapPromise(PLUGIN_NAME, (compilation) =>
+			Promise.resolve(this.entry())
+				.then((entry) => {
+					/** @type {Promise[]} */
+					const promises = [];
+					for (const name of Object.keys(entry)) {
+						const desc = entry[name];
+						const options = EntryOptionPlugin.entryDescriptionToOptions(
+							compiler,
+							name,
+							desc
+						);
+						for (const entry of /** @type {NonNullable} */ (
+							desc.import
+						)) {
+							promises.push(
+								new Promise(
+									/**
+									 * Handles the callback logic for this hook.
+									 * @param {(value?: undefined) => void} resolve resolve
+									 * @param {(reason?: Error) => void} reject reject
+									 */
+									(resolve, reject) => {
+										compilation.addEntry(
+											this.context,
+											EntryPlugin.createDependency(entry, options),
+											options,
+											(err) => {
+												if (err) return reject(err);
+												resolve();
+											}
+										);
+									}
+								)
+							);
+						}
 					}
-				});
-			}
+					return Promise.all(promises);
+				})
+				.then(() => {})
 		);
 	}
 }
 
 module.exports = DynamicEntryPlugin;
-
-DynamicEntryPlugin.createDependency = (entry, name) => {
-	if (Array.isArray(entry)) {
-		return MultiEntryPlugin.createDependency(entry, name);
-	} else {
-		return SingleEntryPlugin.createDependency(entry, name);
-	}
-};
diff --git a/lib/EntryModuleNotFoundError.js b/lib/EntryModuleNotFoundError.js
deleted file mode 100644
index b2458d6f24f..00000000000
--- a/lib/EntryModuleNotFoundError.js
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const WebpackError = require("./WebpackError");
-
-class EntryModuleNotFoundError extends WebpackError {
-	constructor(err) {
-		super("Entry module not found: " + err);
-
-		this.name = "EntryModuleNotFoundError";
-		this.details = err.details;
-		this.error = err;
-
-		Error.captureStackTrace(this, this.constructor);
-	}
-}
-
-module.exports = EntryModuleNotFoundError;
diff --git a/lib/EntryOptionPlugin.js b/lib/EntryOptionPlugin.js
index c8c3ce75588..328f630e71f 100644
--- a/lib/EntryOptionPlugin.js
+++ b/lib/EntryOptionPlugin.js
@@ -2,32 +2,137 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
-const SingleEntryPlugin = require("./SingleEntryPlugin");
-const MultiEntryPlugin = require("./MultiEntryPlugin");
-const DynamicEntryPlugin = require("./DynamicEntryPlugin");
+const { SyncBailHook } = require("tapable");
+
+/** @typedef {import("../declarations/WebpackOptions").EntryDescriptionNormalized} EntryDescription */
+/** @typedef {import("../declarations/WebpackOptions").EntryNormalized} Entry */
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./Entrypoint").EntryOptions} EntryOptions */
+
+/**
+ * @typedef {object} EntryOptionPluginHooks
+ * @property {SyncBailHook<[string, string, EntryDescription], string | undefined>} entry transform an entry into a different request (e.g. wrap a non-HTML entry in a synthetic HTML module); return `undefined` to keep the default behavior
+ */
+
+const PLUGIN_NAME = "EntryOptionPlugin";
+
+/** @type {WeakMap} */
+const hooksMap = new WeakMap();
 
-const itemToPlugin = (context, item, name) => {
-	if (Array.isArray(item)) {
-		return new MultiEntryPlugin(context, item, name);
+class EntryOptionPlugin {
+	/**
+	 * @param {Compiler} compiler the compiler
+	 * @returns {EntryOptionPluginHooks} the hooks
+	 */
+	static getHooks(compiler) {
+		let hooks = hooksMap.get(compiler);
+		if (hooks === undefined) {
+			hooks = {
+				entry: new SyncBailHook(["context", "name", "entryDescription"])
+			};
+			hooksMap.set(compiler, hooks);
+		}
+		return hooks;
 	}
-	return new SingleEntryPlugin(context, item, name);
-};
 
-module.exports = class EntryOptionPlugin {
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance one is tapping into
+	 * @returns {void}
+	 */
 	apply(compiler) {
-		compiler.hooks.entryOption.tap("EntryOptionPlugin", (context, entry) => {
-			if (typeof entry === "string" || Array.isArray(entry)) {
-				itemToPlugin(context, entry, "main").apply(compiler);
-			} else if (typeof entry === "object") {
-				for (const name of Object.keys(entry)) {
-					itemToPlugin(context, entry[name], name).apply(compiler);
-				}
-			} else if (typeof entry === "function") {
-				new DynamicEntryPlugin(context, entry).apply(compiler);
-			}
+		compiler.hooks.entryOption.tap(PLUGIN_NAME, (context, entry) => {
+			EntryOptionPlugin.applyEntryOption(compiler, context, entry);
 			return true;
 		});
 	}
-};
+
+	/**
+	 * Apply entry option.
+	 * @param {Compiler} compiler the compiler
+	 * @param {string} context context directory
+	 * @param {Entry} entry request
+	 * @returns {void}
+	 */
+	static applyEntryOption(compiler, context, entry) {
+		if (typeof entry === "function") {
+			const DynamicEntryPlugin = require("./DynamicEntryPlugin");
+
+			new DynamicEntryPlugin(context, entry).apply(compiler);
+		} else {
+			const EntryPlugin = require("./EntryPlugin");
+
+			for (const name of Object.keys(entry)) {
+				const desc = entry[name];
+				const options = EntryOptionPlugin.entryDescriptionToOptions(
+					compiler,
+					name,
+					desc
+				);
+				const descImport =
+					/** @type {Exclude} */
+					(desc.import);
+				// A plugin (e.g. HtmlModulesPlugin) may rewrite the entry into a
+				// single synthetic request; otherwise each import becomes an entry.
+				const request = EntryOptionPlugin.getHooks(compiler).entry.call(
+					context,
+					name,
+					desc
+				);
+				if (request !== undefined) {
+					new EntryPlugin(context, request, options).apply(compiler);
+				} else {
+					for (const entry of descImport) {
+						new EntryPlugin(context, entry, options).apply(compiler);
+					}
+				}
+			}
+		}
+	}
+
+	/**
+	 * Entry description to options.
+	 * @param {Compiler} compiler the compiler
+	 * @param {string} name entry name
+	 * @param {EntryDescription} desc entry description
+	 * @returns {EntryOptions} options for the entry
+	 */
+	static entryDescriptionToOptions(compiler, name, desc) {
+		/** @type {EntryOptions} */
+		const options = {
+			name,
+			filename: desc.filename,
+			runtime: desc.runtime,
+			layer: desc.layer,
+			dependOn: desc.dependOn,
+			baseUri: desc.baseUri,
+			publicPath: desc.publicPath,
+			chunkLoading: desc.chunkLoading,
+			asyncChunks: desc.asyncChunks,
+			wasmLoading: desc.wasmLoading,
+			worker: desc.worker,
+			library: desc.library
+		};
+		if (desc.chunkLoading) {
+			const EnableChunkLoadingPlugin = require("./javascript/EnableChunkLoadingPlugin");
+
+			EnableChunkLoadingPlugin.checkEnabled(compiler, desc.chunkLoading);
+		}
+		if (desc.wasmLoading) {
+			const EnableWasmLoadingPlugin = require("./wasm/EnableWasmLoadingPlugin");
+
+			EnableWasmLoadingPlugin.checkEnabled(compiler, desc.wasmLoading);
+		}
+		if (desc.library) {
+			const EnableLibraryPlugin = require("./library/EnableLibraryPlugin");
+
+			EnableLibraryPlugin.checkEnabled(compiler, desc.library.type);
+		}
+		return options;
+	}
+}
+
+module.exports = EntryOptionPlugin;
diff --git a/lib/EntryPlugin.js b/lib/EntryPlugin.js
new file mode 100644
index 00000000000..d7d1f31e8b0
--- /dev/null
+++ b/lib/EntryPlugin.js
@@ -0,0 +1,75 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const EntryDependency = require("./dependencies/EntryDependency");
+
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./Entrypoint").EntryOptions} EntryOptions */
+
+const PLUGIN_NAME = "EntryPlugin";
+
+class EntryPlugin {
+	/**
+	 * An entry plugin which will handle creation of the EntryDependency
+	 * @param {string} context context path
+	 * @param {string} entry entry path
+	 * @param {EntryOptions | string=} options entry options (passing a string is deprecated)
+	 */
+	constructor(context, entry, options) {
+		/** @type {string} */
+		this.context = context;
+		/** @type {string} */
+		this.entry = entry;
+		this.options = options || "";
+	}
+
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
+	apply(compiler) {
+		compiler.hooks.compilation.tap(
+			PLUGIN_NAME,
+			(compilation, { normalModuleFactory }) => {
+				compilation.dependencyFactories.set(
+					EntryDependency,
+					normalModuleFactory
+				);
+			}
+		);
+
+		const { entry, options, context } = this;
+		const dep = EntryPlugin.createDependency(entry, options);
+
+		compiler.hooks.make.tapAsync(PLUGIN_NAME, (compilation, callback) => {
+			compilation.addEntry(context, dep, options, (err) => {
+				callback(err);
+			});
+		});
+	}
+
+	/**
+	 * Creates a dependency.
+	 * @param {string} entry entry request
+	 * @param {EntryOptions | string} options entry options (passing string is deprecated)
+	 * @returns {EntryDependency} the dependency
+	 */
+	static createDependency(entry, options) {
+		const dep = new EntryDependency(entry);
+		// TODO webpack 6 remove string option
+		dep.loc = {
+			name:
+				typeof options === "object"
+					? /** @type {string} */ (options.name)
+					: options
+		};
+		return dep;
+	}
+}
+
+module.exports = EntryPlugin;
diff --git a/lib/Entrypoint.js b/lib/Entrypoint.js
index 892855e1607..5ff6e7d290f 100644
--- a/lib/Entrypoint.js
+++ b/lib/Entrypoint.js
@@ -2,11 +2,16 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
 const ChunkGroup = require("./ChunkGroup");
+const SortableSet = require("./util/SortableSet");
+
+/** @typedef {import("../declarations/WebpackOptions").EntryDescriptionNormalized} EntryDescription */
+/** @typedef {import("./Chunk")} Chunk */
 
-/** @typedef {import("./Chunk.js")} Chunk */
+/** @typedef {{ name?: string, worklet?: boolean } & Omit} EntryOptions */
 
 /**
  * Entrypoint serves as an encapsulation primitive for chunks that are
@@ -17,20 +22,34 @@ const ChunkGroup = require("./ChunkGroup");
 class Entrypoint extends ChunkGroup {
 	/**
 	 * Creates an instance of Entrypoint.
-	 * @param {string} name the name of the entrypoint
+	 * @param {EntryOptions | string} entryOptions the options for the entrypoint (or name)
+	 * @param {boolean=} initial false, when the entrypoint is not initial loaded
 	 */
-	constructor(name) {
-		super(name);
+	constructor(entryOptions, initial = true) {
+		if (typeof entryOptions === "string") {
+			entryOptions = { name: entryOptions };
+		}
+		super({
+			name: entryOptions.name
+		});
+		this.options = entryOptions;
 		/** @type {Chunk=} */
-		this.runtimeChunk = undefined;
+		this._runtimeChunk = undefined;
+		/** @type {Chunk=} */
+		this._entrypointChunk = undefined;
+		/** @type {boolean} */
+		this._initial = initial;
+		/** @type {SortableSet} */
+		this._dependOn = new SortableSet();
 	}
 
 	/**
-	 * isInitial will always return true for Entrypoint ChunkGroup.
-	 * @returns {true} returns true as all entrypoints are initial ChunkGroups
+	 * Indicates whether this chunk group is loaded as part of the initial page
+	 * load instead of being created lazily.
+	 * @returns {boolean} true, when this chunk group will be loaded on initial page load
 	 */
 	isInitial() {
-		return true;
+		return this._initial;
 	}
 
 	/**
@@ -39,15 +58,66 @@ class Entrypoint extends ChunkGroup {
 	 * @returns {void}
 	 */
 	setRuntimeChunk(chunk) {
-		this.runtimeChunk = chunk;
+		this._runtimeChunk = chunk;
 	}
 
 	/**
 	 * Fetches the chunk reference containing the webpack bootstrap code
-	 * @returns {Chunk} returns the runtime chunk or first chunk in `this.chunks`
+	 * @returns {Chunk | null} returns the runtime chunk or null if there is none
 	 */
 	getRuntimeChunk() {
-		return this.runtimeChunk || this.chunks[0];
+		if (this._runtimeChunk) return this._runtimeChunk;
+		for (const parent of this.parentsIterable) {
+			if (parent instanceof Entrypoint) return parent.getRuntimeChunk();
+		}
+		return null;
+	}
+
+	/**
+	 * Sets the chunk with the entrypoint modules for an entrypoint.
+	 * @param {Chunk} chunk the chunk being set as the entrypoint chunk.
+	 * @returns {void}
+	 */
+	setEntrypointChunk(chunk) {
+		this._entrypointChunk = chunk;
+	}
+
+	/**
+	 * Returns the chunk which contains the entrypoint modules
+	 * (or at least the execution of them)
+	 * @returns {Chunk} chunk
+	 */
+	getEntrypointChunk() {
+		return /** @type {Chunk} */ (this._entrypointChunk);
+	}
+
+	/**
+	 * Replaces one member chunk with another while preserving the group's
+	 * ordering and avoiding duplicates.
+	 * @param {Chunk} oldChunk chunk to be replaced
+	 * @param {Chunk} newChunk New chunk that will be replaced with
+	 * @returns {boolean | undefined} returns true if the replacement was successful
+	 */
+	replaceChunk(oldChunk, newChunk) {
+		if (this._runtimeChunk === oldChunk) this._runtimeChunk = newChunk;
+		if (this._entrypointChunk === oldChunk) this._entrypointChunk = newChunk;
+		return super.replaceChunk(oldChunk, newChunk);
+	}
+
+	/**
+	 * @param {Entrypoint} entrypoint the entrypoint
+	 * @returns {void}
+	 */
+	addDependOn(entrypoint) {
+		this._dependOn.add(entrypoint);
+	}
+
+	/**
+	 * @param {Entrypoint} entrypoint the entrypoint
+	 * @returns {boolean} true if the entrypoint is in the dependOn set
+	 */
+	dependOn(entrypoint) {
+		return this._dependOn.has(entrypoint);
 	}
 }
 
diff --git a/lib/EnvironmentPlugin.js b/lib/EnvironmentPlugin.js
index 388ce817372..d34503693b4 100644
--- a/lib/EnvironmentPlugin.js
+++ b/lib/EnvironmentPlugin.js
@@ -6,59 +6,69 @@
 "use strict";
 
 const DefinePlugin = require("./DefinePlugin");
+const WebpackError = require("./errors/WebpackError");
 
-const needsEnvVarFix =
-	["8", "9"].indexOf(process.versions.node.split(".")[0]) >= 0 &&
-	process.platform === "win32";
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./DefinePlugin").CodeValue} CodeValue */
+
+const PLUGIN_NAME = "EnvironmentPlugin";
 
 class EnvironmentPlugin {
+	/**
+	 * Creates an instance of EnvironmentPlugin.
+	 * @param {(string | string[] | Record)[]} keys keys
+	 */
 	constructor(...keys) {
 		if (keys.length === 1 && Array.isArray(keys[0])) {
+			/** @type {string[]} */
 			this.keys = keys[0];
 			this.defaultValues = {};
 		} else if (keys.length === 1 && keys[0] && typeof keys[0] === "object") {
 			this.keys = Object.keys(keys[0]);
-			this.defaultValues = keys[0];
+			this.defaultValues =
+				/** @type {Record} */
+				(keys[0]);
 		} else {
-			this.keys = keys;
+			this.keys = /** @type {string[]} */ (keys);
 			this.defaultValues = {};
 		}
 	}
 
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
 	apply(compiler) {
-		const definitions = this.keys.reduce((defs, key) => {
-			// TODO remove once the fix has made its way into Node 8.
-			// Work around https://github.com/nodejs/node/pull/18463,
-			// affecting Node 8 & 9 by performing an OS-level
-			// operation that always succeeds before reading
-			// environment variables:
-			if (needsEnvVarFix) require("os").cpus();
+		const definePlugin = new DefinePlugin({});
 
-			const value =
-				process.env[key] !== undefined
-					? process.env[key]
-					: this.defaultValues[key];
+		compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
+			/** @type {Record} */
+			const definitions = {};
+			for (const key of this.keys) {
+				const value =
+					process.env[key] !== undefined
+						? process.env[key]
+						: this.defaultValues[key];
 
-			if (value === undefined) {
-				compiler.hooks.thisCompilation.tap("EnvironmentPlugin", compilation => {
-					const error = new Error(
-						`EnvironmentPlugin - ${key} environment variable is undefined.\n\n` +
+				if (value === undefined) {
+					const error = new WebpackError(
+						`${PLUGIN_NAME} - ${key} environment variable is undefined.\n\n` +
 							"You can pass an object with default values to suppress this warning.\n" +
 							"See https://webpack.js.org/plugins/environment-plugin for example."
 					);
 
 					error.name = "EnvVariableNotDefinedError";
-					compilation.warnings.push(error);
-				});
+					compilation.errors.push(error);
+				}
+				const defValue =
+					value === undefined ? "undefined" : JSON.stringify(value);
+				definitions[`process.env.${key}`] = defValue;
+				definitions[`import.meta.env.${key}`] = defValue;
 			}
-
-			defs[`process.env.${key}`] =
-				typeof value === "undefined" ? "undefined" : JSON.stringify(value);
-
-			return defs;
-		}, {});
-
-		new DefinePlugin(definitions).apply(compiler);
+			definePlugin.definitions = definitions;
+		});
+		definePlugin.apply(compiler);
 	}
 }
 
diff --git a/lib/ErrorHelpers.js b/lib/ErrorHelpers.js
index 900ae4d59e8..918eb38d114 100644
--- a/lib/ErrorHelpers.js
+++ b/lib/ErrorHelpers.js
@@ -2,59 +2,106 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
 const loaderFlag = "LOADER_EXECUTION";
 
 const webpackOptionsFlag = "WEBPACK_OPTIONS";
 
-exports.cutOffByFlag = (stack, flag) => {
-	stack = stack.split("\n");
-	for (let i = 0; i < stack.length; i++) {
-		if (stack[i].includes(flag)) {
-			stack.length = i;
+/**
+ * Returns stack trace without the specified flag included.
+ * @param {string} stack stack trace
+ * @param {string} flag flag to cut off
+ * @returns {string} stack trace without the specified flag included
+ */
+const cutOffByFlag = (stack, flag) => {
+	const errorStack = stack.split("\n");
+	for (let i = 0; i < errorStack.length; i++) {
+		if (errorStack[i].includes(flag)) {
+			errorStack.length = i;
 		}
 	}
-	return stack.join("\n");
+	return errorStack.join("\n");
 };
 
-exports.cutOffLoaderExecution = stack =>
-	exports.cutOffByFlag(stack, loaderFlag);
+/**
+ * Cut off loader execution.
+ * @param {string} stack stack trace
+ * @returns {string} stack trace without the loader execution flag included
+ */
+const cutOffLoaderExecution = (stack) => cutOffByFlag(stack, loaderFlag);
+
+/**
+ * Cut off webpack options.
+ * @param {string} stack stack trace
+ * @returns {string} stack trace without the webpack options flag included
+ */
+const cutOffWebpackOptions = (stack) => cutOffByFlag(stack, webpackOptionsFlag);
 
-exports.cutOffWebpackOptions = stack =>
-	exports.cutOffByFlag(stack, webpackOptionsFlag);
+/**
+ * Cut off multiline message.
+ * @param {string} stack stack trace
+ * @param {string} message error message
+ * @returns {string} stack trace without the message included
+ */
+const cutOffMultilineMessage = (stack, message) => {
+	const stackSplitByLines = stack.split("\n");
+	const messageSplitByLines = message.split("\n");
 
-exports.cutOffMultilineMessage = (stack, message) => {
-	stack = stack.split("\n");
-	message = message.split("\n");
+	/** @type {string[]} */
+	const result = [];
 
-	return stack
-		.reduce(
-			(acc, line, idx) =>
-				line.includes(message[idx]) ? acc : acc.concat(line),
-			[]
-		)
-		.join("\n");
+	for (const [idx, line] of stackSplitByLines.entries()) {
+		if (!line.includes(messageSplitByLines[idx])) result.push(line);
+	}
+
+	return result.join("\n");
 };
 
-exports.cutOffMessage = (stack, message) => {
+/**
+ * Returns stack trace without the message included.
+ * @param {string} stack stack trace
+ * @param {string} message error message
+ * @returns {string} stack trace without the message included
+ */
+const cutOffMessage = (stack, message) => {
 	const nextLine = stack.indexOf("\n");
 	if (nextLine === -1) {
 		return stack === message ? "" : stack;
-	} else {
-		const firstLine = stack.substr(0, nextLine);
-		return firstLine === message ? stack.substr(nextLine + 1) : stack;
 	}
+	const firstLine = stack.slice(0, nextLine);
+	return firstLine === message ? stack.slice(nextLine + 1) : stack;
 };
 
-exports.cleanUp = (stack, message) => {
-	stack = exports.cutOffLoaderExecution(stack);
-	stack = exports.cutOffMessage(stack, message);
+/**
+ * Returns stack trace without the loader execution flag and message included.
+ * @param {string} stack stack trace
+ * @param {string} message error message
+ * @returns {string} stack trace without the loader execution flag and message included
+ */
+const cleanUp = (stack, message) => {
+	stack = cutOffLoaderExecution(stack);
+	stack = cutOffMessage(stack, message);
 	return stack;
 };
 
-exports.cleanUpWebpackOptions = (stack, message) => {
-	stack = exports.cutOffWebpackOptions(stack);
-	stack = exports.cutOffMultilineMessage(stack, message);
+/**
+ * Clean up webpack options.
+ * @param {string} stack stack trace
+ * @param {string} message error message
+ * @returns {string} stack trace without the webpack options flag and message included
+ */
+const cleanUpWebpackOptions = (stack, message) => {
+	stack = cutOffWebpackOptions(stack);
+	stack = cutOffMultilineMessage(stack, message);
 	return stack;
 };
+
+module.exports.cleanUp = cleanUp;
+module.exports.cleanUpWebpackOptions = cleanUpWebpackOptions;
+module.exports.cutOffByFlag = cutOffByFlag;
+module.exports.cutOffLoaderExecution = cutOffLoaderExecution;
+module.exports.cutOffMessage = cutOffMessage;
+module.exports.cutOffMultilineMessage = cutOffMultilineMessage;
+module.exports.cutOffWebpackOptions = cutOffWebpackOptions;
diff --git a/lib/EvalDevToolModulePlugin.js b/lib/EvalDevToolModulePlugin.js
index 2af11f9c3a4..7519450a3d4 100644
--- a/lib/EvalDevToolModulePlugin.js
+++ b/lib/EvalDevToolModulePlugin.js
@@ -2,24 +2,134 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
-const EvalDevToolModuleTemplatePlugin = require("./EvalDevToolModuleTemplatePlugin");
+const { ConcatSource, RawSource } = require("webpack-sources");
+const ExternalModule = require("./ExternalModule");
+const ModuleFilenameHelpers = require("./ModuleFilenameHelpers");
+const RuntimeGlobals = require("./RuntimeGlobals");
+const JavascriptModulesPlugin = require("./javascript/JavascriptModulesPlugin");
+
+/** @typedef {import("webpack-sources").Source} Source */
+/** @typedef {import("../declarations/WebpackOptions").DevtoolNamespace} DevtoolNamespace */
+/** @typedef {import("../declarations/WebpackOptions").DevtoolModuleFilenameTemplate} DevtoolModuleFilenameTemplate */
+/** @typedef {import("./Compiler")} Compiler */
+
+/** @type {WeakMap} */
+const cache = new WeakMap();
+
+const devtoolWarning = new RawSource(`/*
+ * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development").
+ * This devtool is neither made for production nor for readable output files.
+ * It uses "eval()" calls to create a separate source file in the browser devtools.
+ * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
+ * or disable the default devtool with "devtool: false".
+ * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
+ */
+`);
+
+/**
+ * Defines the eval dev tool module plugin options type used by this module.
+ * @typedef {object} EvalDevToolModulePluginOptions
+ * @property {DevtoolNamespace=} namespace namespace
+ * @property {string=} sourceUrlComment source url comment
+ * @property {DevtoolModuleFilenameTemplate=} moduleFilenameTemplate module filename template
+ */
+
+const PLUGIN_NAME = "EvalDevToolModulePlugin";
 
 class EvalDevToolModulePlugin {
-	constructor(options) {
-		this.sourceUrlComment = options.sourceUrlComment;
-		this.moduleFilenameTemplate = options.moduleFilenameTemplate;
-		this.namespace = options.namespace;
+	/**
+	 * Creates an instance of EvalDevToolModulePlugin.
+	 * @param {EvalDevToolModulePluginOptions=} options options
+	 */
+	constructor(options = {}) {
+		/** @type {DevtoolNamespace} */
+		this.namespace = options.namespace || "";
+		/** @type {string} */
+		this.sourceUrlComment = options.sourceUrlComment || "\n//# sourceURL=[url]";
+		/** @type {DevtoolModuleFilenameTemplate} */
+		this.moduleFilenameTemplate =
+			options.moduleFilenameTemplate ||
+			"webpack://[namespace]/[resourcePath]?[loaders]";
 	}
 
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
 	apply(compiler) {
-		compiler.hooks.compilation.tap("EvalDevToolModulePlugin", compilation => {
-			new EvalDevToolModuleTemplatePlugin({
-				sourceUrlComment: this.sourceUrlComment,
-				moduleFilenameTemplate: this.moduleFilenameTemplate,
-				namespace: this.namespace
-			}).apply(compilation.moduleTemplates.javascript);
+		compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
+			const hooks = JavascriptModulesPlugin.getCompilationHooks(compilation);
+			hooks.renderModuleContent.tap(
+				PLUGIN_NAME,
+				(source, module, { chunk, runtimeTemplate, chunkGraph }) => {
+					const cacheEntry = cache.get(source);
+					if (cacheEntry !== undefined) return cacheEntry;
+					if (module instanceof ExternalModule) {
+						cache.set(source, source);
+						return source;
+					}
+					const content = source.source();
+					const namespace = compilation.getPath(this.namespace, {
+						chunk
+					});
+					const str = ModuleFilenameHelpers.createFilename(
+						module,
+						{
+							moduleFilenameTemplate: this.moduleFilenameTemplate,
+							namespace
+						},
+						{
+							requestShortener: runtimeTemplate.requestShortener,
+							chunkGraph,
+							hashFunction: compilation.outputOptions.hashFunction
+						}
+					);
+					const footer = `\n${this.sourceUrlComment.replace(
+						/\[url\]/g,
+						encodeURI(str)
+							.replace(/%2F/g, "/")
+							.replace(/%20/g, "_")
+							.replace(/%5E/g, "^")
+							.replace(/%5C/g, "\\")
+							.replace(/^\//, "")
+					)}`;
+					const result = new RawSource(
+						`eval(${
+							compilation.outputOptions.trustedTypes
+								? `${RuntimeGlobals.createScript}(${JSON.stringify(
+										`{${content + footer}\n}`
+									)})`
+								: JSON.stringify(`{${content + footer}\n}`)
+						});`
+					);
+					cache.set(source, result);
+					return result;
+				}
+			);
+			hooks.inlineInRuntimeBailout.tap(
+				PLUGIN_NAME,
+				() => "the eval devtool is used."
+			);
+			hooks.render.tap(
+				PLUGIN_NAME,
+				(source) => new ConcatSource(devtoolWarning, source)
+			);
+			hooks.chunkHash.tap(PLUGIN_NAME, (chunk, hash) => {
+				hash.update(PLUGIN_NAME);
+				hash.update("2");
+			});
+			if (compilation.outputOptions.trustedTypes) {
+				compilation.hooks.additionalModuleRuntimeRequirements.tap(
+					PLUGIN_NAME,
+					(module, set, _context) => {
+						set.add(RuntimeGlobals.createScript);
+					}
+				);
+			}
 		});
 	}
 }
diff --git a/lib/EvalDevToolModuleTemplatePlugin.js b/lib/EvalDevToolModuleTemplatePlugin.js
deleted file mode 100644
index ab5dd1aac1c..00000000000
--- a/lib/EvalDevToolModuleTemplatePlugin.js
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const { RawSource } = require("webpack-sources");
-const ModuleFilenameHelpers = require("./ModuleFilenameHelpers");
-
-const cache = new WeakMap();
-
-class EvalDevToolModuleTemplatePlugin {
-	constructor(options) {
-		this.sourceUrlComment = options.sourceUrlComment || "\n//# sourceURL=[url]";
-		this.moduleFilenameTemplate =
-			options.moduleFilenameTemplate ||
-			"webpack://[namespace]/[resourcePath]?[loaders]";
-		this.namespace = options.namespace || "";
-	}
-
-	apply(moduleTemplate) {
-		moduleTemplate.hooks.module.tap(
-			"EvalDevToolModuleTemplatePlugin",
-			(source, module) => {
-				const cacheEntry = cache.get(source);
-				if (cacheEntry !== undefined) return cacheEntry;
-				const content = source.source();
-				const str = ModuleFilenameHelpers.createFilename(
-					module,
-					{
-						moduleFilenameTemplate: this.moduleFilenameTemplate,
-						namespace: this.namespace
-					},
-					moduleTemplate.runtimeTemplate.requestShortener
-				);
-				const footer =
-					"\n" +
-					this.sourceUrlComment.replace(
-						/\[url\]/g,
-						encodeURI(str)
-							.replace(/%2F/g, "/")
-							.replace(/%20/g, "_")
-							.replace(/%5E/g, "^")
-							.replace(/%5C/g, "\\")
-							.replace(/^\//, "")
-					);
-				const result = new RawSource(
-					`eval(${JSON.stringify(content + footer)});`
-				);
-				cache.set(source, result);
-				return result;
-			}
-		);
-		moduleTemplate.hooks.hash.tap("EvalDevToolModuleTemplatePlugin", hash => {
-			hash.update("EvalDevToolModuleTemplatePlugin");
-			hash.update("2");
-		});
-	}
-}
-
-module.exports = EvalDevToolModuleTemplatePlugin;
diff --git a/lib/EvalSourceMapDevToolModuleTemplatePlugin.js b/lib/EvalSourceMapDevToolModuleTemplatePlugin.js
deleted file mode 100644
index ac22c68976e..00000000000
--- a/lib/EvalSourceMapDevToolModuleTemplatePlugin.js
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const { RawSource } = require("webpack-sources");
-const ModuleFilenameHelpers = require("./ModuleFilenameHelpers");
-
-const cache = new WeakMap();
-
-class EvalSourceMapDevToolModuleTemplatePlugin {
-	constructor(compilation, options) {
-		this.compilation = compilation;
-		this.sourceMapComment =
-			options.append || "//# sourceURL=[module]\n//# sourceMappingURL=[url]";
-		this.moduleFilenameTemplate =
-			options.moduleFilenameTemplate ||
-			"webpack://[namespace]/[resource-path]?[hash]";
-		this.namespace = options.namespace || "";
-		this.options = options;
-	}
-
-	apply(moduleTemplate) {
-		const self = this;
-		const options = this.options;
-		const matchModule = ModuleFilenameHelpers.matchObject.bind(
-			ModuleFilenameHelpers,
-			options
-		);
-		moduleTemplate.hooks.module.tap(
-			"EvalSourceMapDevToolModuleTemplatePlugin",
-			(source, module) => {
-				const cachedSource = cache.get(source);
-				if (cachedSource !== undefined) {
-					return cachedSource;
-				}
-
-				if (!matchModule(module.resource)) {
-					return source;
-				}
-
-				/** @type {{ [key: string]: TODO; }} */
-				let sourceMap;
-				let content;
-				if (source.sourceAndMap) {
-					const sourceAndMap = source.sourceAndMap(options);
-					sourceMap = sourceAndMap.map;
-					content = sourceAndMap.source;
-				} else {
-					sourceMap = source.map(options);
-					content = source.source();
-				}
-				if (!sourceMap) {
-					return source;
-				}
-
-				// Clone (flat) the sourcemap to ensure that the mutations below do not persist.
-				sourceMap = Object.keys(sourceMap).reduce((obj, key) => {
-					obj[key] = sourceMap[key];
-					return obj;
-				}, {});
-				const modules = sourceMap.sources.map(source => {
-					const module = self.compilation.findModule(source);
-					return module || source;
-				});
-				let moduleFilenames = modules.map(module => {
-					return ModuleFilenameHelpers.createFilename(
-						module,
-						{
-							moduleFilenameTemplate: self.moduleFilenameTemplate,
-							namespace: self.namespace
-						},
-						moduleTemplate.runtimeTemplate.requestShortener
-					);
-				});
-				moduleFilenames = ModuleFilenameHelpers.replaceDuplicates(
-					moduleFilenames,
-					(filename, i, n) => {
-						for (let j = 0; j < n; j++) filename += "*";
-						return filename;
-					}
-				);
-				sourceMap.sources = moduleFilenames;
-				sourceMap.sourceRoot = options.sourceRoot || "";
-				sourceMap.file = `${module.id}.js`;
-
-				const footer =
-					self.sourceMapComment.replace(
-						/\[url\]/g,
-						`data:application/json;charset=utf-8;base64,${Buffer.from(
-							JSON.stringify(sourceMap),
-							"utf8"
-						).toString("base64")}`
-					) + `\n//# sourceURL=webpack-internal:///${module.id}\n`; // workaround for chrome bug
-
-				const evalSource = new RawSource(
-					`eval(${JSON.stringify(content + footer)});`
-				);
-
-				cache.set(source, evalSource);
-
-				return evalSource;
-			}
-		);
-		moduleTemplate.hooks.hash.tap(
-			"EvalSourceMapDevToolModuleTemplatePlugin",
-			hash => {
-				hash.update("eval-source-map");
-				hash.update("2");
-			}
-		);
-	}
-}
-module.exports = EvalSourceMapDevToolModuleTemplatePlugin;
diff --git a/lib/EvalSourceMapDevToolPlugin.js b/lib/EvalSourceMapDevToolPlugin.js
index daf9aaddfc6..44620153886 100644
--- a/lib/EvalSourceMapDevToolPlugin.js
+++ b/lib/EvalSourceMapDevToolPlugin.js
@@ -2,39 +2,250 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
-const EvalSourceMapDevToolModuleTemplatePlugin = require("./EvalSourceMapDevToolModuleTemplatePlugin");
+const { ConcatSource, RawSource } = require("webpack-sources");
+const ModuleFilenameHelpers = require("./ModuleFilenameHelpers");
+const NormalModule = require("./NormalModule");
+const RuntimeGlobals = require("./RuntimeGlobals");
 const SourceMapDevToolModuleOptionsPlugin = require("./SourceMapDevToolModuleOptionsPlugin");
+const JavascriptModulesPlugin = require("./javascript/JavascriptModulesPlugin");
+const ConcatenatedModule = require("./optimize/ConcatenatedModule");
+const generateDebugId = require("./util/generateDebugId");
+const { makePathsAbsolute } = require("./util/identifier");
+
+/** @typedef {import("webpack-sources").RawSourceMap} RawSourceMap */
+/** @typedef {import("webpack-sources").Source} Source */
+/** @typedef {import("../declarations/WebpackOptions").DevtoolNamespace} DevtoolNamespace */
+/** @typedef {import("../declarations/WebpackOptions").DevtoolModuleFilenameTemplate} DevtoolModuleFilenameTemplate */
+/** @typedef {import("../declarations/plugins/SourceMapDevToolPlugin").SourceMapDevToolPluginOptions} SourceMapDevToolPluginOptions */
+/** @typedef {import("../declarations/plugins/SourceMapDevToolPlugin").Rules} Rules */
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./ChunkGraph").ModuleId} ModuleId */
+
+/** @type {WeakMap} */
+const cache = new WeakMap();
+
+const devtoolWarning = new RawSource(`/*
+ * ATTENTION: An "eval-source-map" devtool has been used.
+ * This devtool is neither made for production nor for readable output files.
+ * It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
+ * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
+ * or disable the default devtool with "devtool: false".
+ * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
+ */
+`);
+
+const PLUGIN_NAME = "EvalSourceMapDevToolPlugin";
 
 class EvalSourceMapDevToolPlugin {
-	constructor(options) {
-		if (arguments.length > 1) {
-			throw new Error(
-				"EvalSourceMapDevToolPlugin only takes one argument (pass an options object)"
-			);
-		}
-		if (typeof options === "string") {
+	/**
+	 * Creates an instance of EvalSourceMapDevToolPlugin.
+	 * @param {SourceMapDevToolPluginOptions | string=} inputOptions Options object
+	 */
+	constructor(inputOptions = {}) {
+		/** @type {SourceMapDevToolPluginOptions} */
+		let options;
+		if (typeof inputOptions === "string") {
 			options = {
-				append: options
+				append: inputOptions
 			};
+		} else {
+			options = inputOptions;
 		}
-		if (!options) options = {};
+		/** @type {string} */
+		this.sourceMapComment =
+			options.append && typeof options.append !== "function"
+				? options.append
+				: "//# sourceURL=[module]\n//# sourceMappingURL=[url]";
+		/** @type {DevtoolModuleFilenameTemplate} */
+		this.moduleFilenameTemplate =
+			options.moduleFilenameTemplate ||
+			"webpack://[namespace]/[resource-path]?[hash]";
+		/** @type {DevtoolNamespace} */
+		this.namespace = options.namespace || "";
+		/** @type {SourceMapDevToolPluginOptions} */
 		this.options = options;
 	}
 
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
 	apply(compiler) {
 		const options = this.options;
-		compiler.hooks.compilation.tap(
-			"EvalSourceMapDevToolPlugin",
-			compilation => {
-				new SourceMapDevToolModuleOptionsPlugin(options).apply(compilation);
-				new EvalSourceMapDevToolModuleTemplatePlugin(
-					compilation,
-					options
-				).apply(compilation.moduleTemplates.javascript);
+		compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
+			const hooks = JavascriptModulesPlugin.getCompilationHooks(compilation);
+			new SourceMapDevToolModuleOptionsPlugin(options).apply(compilation);
+			const matchModule = ModuleFilenameHelpers.matchObject.bind(
+				ModuleFilenameHelpers,
+				options
+			);
+			hooks.renderModuleContent.tap(
+				PLUGIN_NAME,
+				(source, m, { chunk, runtimeTemplate, chunkGraph }) => {
+					const cachedSource = cache.get(source);
+					if (cachedSource !== undefined) {
+						return cachedSource;
+					}
+
+					/**
+					 * Returns result.
+					 * @param {Source} r result
+					 * @returns {Source} result
+					 */
+					const result = (r) => {
+						cache.set(source, r);
+						return r;
+					};
+
+					if (m instanceof NormalModule) {
+						if (!matchModule(m.resource)) {
+							return result(source);
+						}
+					} else if (m instanceof ConcatenatedModule) {
+						if (m.rootModule instanceof NormalModule) {
+							if (!matchModule(m.rootModule.resource)) {
+								return result(source);
+							}
+						} else {
+							return result(source);
+						}
+					} else {
+						return result(source);
+					}
+
+					const namespace = compilation.getPath(this.namespace, {
+						chunk
+					});
+					/** @type {RawSourceMap} */
+					let sourceMap;
+					/** @type {string | Buffer} */
+					let content;
+					if (source.sourceAndMap) {
+						const sourceAndMap = source.sourceAndMap(options);
+						sourceMap = /** @type {RawSourceMap} */ (sourceAndMap.map);
+						content = sourceAndMap.source;
+					} else {
+						sourceMap = /** @type {RawSourceMap} */ (source.map(options));
+						content = source.source();
+					}
+					if (!sourceMap) {
+						return result(source);
+					}
+
+					// Clone (flat) the sourcemap to ensure that the mutations below do not persist.
+					sourceMap = { ...sourceMap };
+					const context = compiler.context;
+					const root = compiler.root;
+					const cachedAbsolutify = makePathsAbsolute.bindContextCache(
+						context,
+						root
+					);
+					const modules = sourceMap.sources.map((source) => {
+						if (!source.startsWith("webpack://")) return source;
+						source = cachedAbsolutify(source.slice(10));
+						const module = compilation.findModule(source);
+						return module || source;
+					});
+					let moduleFilenames = modules.map((module) =>
+						ModuleFilenameHelpers.createFilename(
+							module,
+							{
+								moduleFilenameTemplate: this.moduleFilenameTemplate,
+								namespace
+							},
+							{
+								requestShortener: runtimeTemplate.requestShortener,
+								chunkGraph,
+								hashFunction: compilation.outputOptions.hashFunction
+							}
+						)
+					);
+					moduleFilenames = ModuleFilenameHelpers.replaceDuplicates(
+						moduleFilenames,
+						(filename, i, n) => {
+							for (let j = 0; j < n; j++) filename += "*";
+							return filename;
+						}
+					);
+					sourceMap.sources = moduleFilenames;
+					if (options.ignoreList) {
+						const ignoreList = sourceMap.sources.reduce(
+							/** @type {(acc: number[], sourceName: string, idx: number) => number[]} */ (
+								(acc, sourceName, idx) => {
+									const rule = /** @type {Rules} */ (options.ignoreList);
+									if (ModuleFilenameHelpers.matchPart(sourceName, rule)) {
+										acc.push(idx);
+									}
+									return acc;
+								}
+							),
+							[]
+						);
+						if (ignoreList.length > 0) {
+							sourceMap.ignoreList = ignoreList;
+						}
+					}
+
+					if (options.noSources) {
+						sourceMap.sourcesContent = undefined;
+					}
+					sourceMap.sourceRoot = options.sourceRoot || "";
+					const moduleId =
+						/** @type {ModuleId} */
+						(chunkGraph.getModuleId(m));
+					sourceMap.file =
+						typeof moduleId === "number" ? `${moduleId}.js` : moduleId;
+
+					if (options.debugIds) {
+						sourceMap.debugId = generateDebugId(content, sourceMap.file);
+					}
+
+					const footer = `${this.sourceMapComment.replace(
+						/\[url\]/g,
+						`data:application/json;charset=utf-8;base64,${Buffer.from(
+							JSON.stringify(sourceMap),
+							"utf8"
+						).toString("base64")}`
+					)}\n//# sourceURL=webpack-internal:///${moduleId}\n`; // workaround for chrome bug
+
+					return result(
+						new RawSource(
+							`eval(${
+								compilation.outputOptions.trustedTypes
+									? `${RuntimeGlobals.createScript}(${JSON.stringify(
+											`{${content + footer}\n}`
+										)})`
+									: JSON.stringify(`{${content + footer}\n}`)
+							});`
+						)
+					);
+				}
+			);
+			hooks.inlineInRuntimeBailout.tap(
+				PLUGIN_NAME,
+				() => "the eval-source-map devtool is used."
+			);
+			hooks.render.tap(
+				PLUGIN_NAME,
+				(source) => new ConcatSource(devtoolWarning, source)
+			);
+			hooks.chunkHash.tap(PLUGIN_NAME, (chunk, hash) => {
+				hash.update(PLUGIN_NAME);
+				hash.update("2");
+			});
+			if (compilation.outputOptions.trustedTypes) {
+				compilation.hooks.additionalModuleRuntimeRequirements.tap(
+					PLUGIN_NAME,
+					(module, set, context) => {
+						set.add(RuntimeGlobals.createScript);
+					}
+				);
 			}
-		);
+		});
 	}
 }
 
diff --git a/lib/ExportPropertyMainTemplatePlugin.js b/lib/ExportPropertyMainTemplatePlugin.js
deleted file mode 100644
index 90df031aabb..00000000000
--- a/lib/ExportPropertyMainTemplatePlugin.js
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const { ConcatSource } = require("webpack-sources");
-
-/** @typedef {import("./Compilation")} Compilation */
-
-/**
- * @param {string[]} accessor the accessor to convert to path
- * @returns {string} the path
- */
-const accessorToObjectAccess = accessor => {
-	return accessor.map(a => `[${JSON.stringify(a)}]`).join("");
-};
-
-class ExportPropertyMainTemplatePlugin {
-	/**
-	 * @param {string|string[]} property the name of the property to export
-	 */
-	constructor(property) {
-		this.property = property;
-	}
-
-	/**
-	 * @param {Compilation} compilation the compilation instance
-	 * @returns {void}
-	 */
-	apply(compilation) {
-		const { mainTemplate, chunkTemplate } = compilation;
-
-		const onRenderWithEntry = (source, chunk, hash) => {
-			const postfix = `${accessorToObjectAccess([].concat(this.property))}`;
-			return new ConcatSource(source, postfix);
-		};
-
-		for (const template of [mainTemplate, chunkTemplate]) {
-			template.hooks.renderWithEntry.tap(
-				"ExportPropertyMainTemplatePlugin",
-				onRenderWithEntry
-			);
-		}
-
-		mainTemplate.hooks.hash.tap("ExportPropertyMainTemplatePlugin", hash => {
-			hash.update("export property");
-			hash.update(`${this.property}`);
-		});
-	}
-}
-
-module.exports = ExportPropertyMainTemplatePlugin;
diff --git a/lib/ExportsInfo.js b/lib/ExportsInfo.js
new file mode 100644
index 00000000000..624857c4fbf
--- /dev/null
+++ b/lib/ExportsInfo.js
@@ -0,0 +1,2054 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const { ImportPhaseUtils } = require("./dependencies/ImportPhase");
+const { InlinedUsedName } = require("./optimize/InlineExports");
+const { equals } = require("./util/ArrayHelpers");
+const SortableSet = require("./util/SortableSet");
+const makeSerializable = require("./util/makeSerializable");
+const { forEachRuntime } = require("./util/runtime");
+
+/** @typedef {import("./Dependency")} Dependency */
+/** @typedef {import("./Dependency").RuntimeSpec} RuntimeSpec */
+/** @typedef {import("./Dependency").ExportInfoName} ExportInfoName */
+/** @typedef {import("./Dependency").ExportsSpecExcludeExports} ExportsSpecExcludeExports */
+/** @typedef {import("./dependencies/HarmonyImportDependency")} HarmonyImportDependency */
+/** @typedef {import("./Module")} Module */
+/** @typedef {import("./ModuleGraph")} ModuleGraph */
+/** @typedef {import("./ModuleGraphConnection")} ModuleGraphConnection */
+/** @typedef {import("./optimize/InlineExports").InlinedValue} InlinedValue */
+/** @typedef {[RestoreProvidedDataExports[], ExportInfo["provided"], ExportInfo["canMangleProvide"], ExportInfo["terminalBinding"]]} RestoreProvidedDataTuple */
+/** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */
+/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */
+/** @typedef {import("./util/Hash")} Hash */
+
+/** @typedef {typeof UsageState.OnlyPropertiesUsed | typeof UsageState.NoInfo | typeof UsageState.Unknown | typeof UsageState.Used} RuntimeUsageStateType */
+/** @typedef {typeof UsageState.Unused | RuntimeUsageStateType} UsageStateType */
+
+/** @typedef {Map} UsedInRuntime */
+/** @typedef {{ module: Module, export: ExportInfoName[], deferred: boolean }} TargetItemWithoutConnection */
+/** @typedef {{ module: Module, connection: ModuleGraphConnection, export: ExportInfoName[] | undefined }} TargetItemWithConnection */
+/** @typedef {(target: TargetItemWithConnection) => boolean} ResolveTargetFilter */
+/** @typedef {(module: Module) => boolean} ValidTargetModuleFilter */
+/** @typedef {{ connection: ModuleGraphConnection, export: ExportInfoName[], priority: number }} TargetItem */
+/** @typedef {Map} Target */
+
+/** @typedef {string | InlinedUsedName | null} ExportInfoUsedName */
+/** @typedef {boolean | null} ExportInfoProvided */
+
+/** @typedef {Map} Exports */
+/** @typedef {string | string[] | InlinedUsedName | false} UsedName */
+/** @typedef {Set} AlreadyVisitedExportInfo */
+
+/**
+ * Defines the restore provided data exports type used by this module.
+ * @typedef {object} RestoreProvidedDataExports
+ * @property {ExportInfoName} name
+ * @property {ExportInfo["provided"]} provided
+ * @property {ExportInfo["canMangleProvide"]} canMangleProvide
+ * @property {ExportInfo["canInlineProvide"]=} canInlineProvide
+ * @property {ExportInfo["terminalBinding"]} terminalBinding
+ * @property {ExportInfo["pureProvide"]=} pureProvide
+ * @property {RestoreProvidedData | undefined} exportsInfo
+ */
+
+const UsageState = Object.freeze({
+	Unused: /** @type {0} */ (0),
+	OnlyPropertiesUsed: /** @type {1} */ (1),
+	NoInfo: /** @type {2} */ (2),
+	Unknown: /** @type {3} */ (3),
+	Used: /** @type {4} */ (4)
+});
+
+const RETURNS_TRUE = () => true;
+
+// Shared empty visited-set for `findTarget`: `_findTarget` only reads
+// `alreadyVisited` (`.has`), never mutates it, so a fresh `new Set()` per call
+// is pure churn on the per-export reexport hot path. Must stay never-mutated.
+const EMPTY_VISITED = /** @type {Set} */ (new Set());
+
+const CIRCULAR = Symbol("circular target");
+
+// bit layout of ExportInfo._inlineFlags
+const INLINE_USE_TRUE = 1;
+const INLINE_USE_FALSE = 2;
+const INLINE_USE_MASK = 3;
+const PURE_PROVIDE_FLAG = 4;
+
+class RestoreProvidedData {
+	/**
+	 * Creates an instance of RestoreProvidedData.
+	 * @param {RestoreProvidedDataExports[]} exports exports
+	 * @param {ExportInfo["provided"]} otherProvided other provided
+	 * @param {ExportInfo["canMangleProvide"]} otherCanMangleProvide other can mangle provide
+	 * @param {ExportInfo["terminalBinding"]} otherTerminalBinding other terminal binding
+	 */
+	constructor(
+		exports,
+		otherProvided,
+		otherCanMangleProvide,
+		otherTerminalBinding
+	) {
+		/** @type {RestoreProvidedDataExports[]} */
+		this.exports = exports;
+		/** @type {ExportInfoProvided | undefined} */
+		this.otherProvided = otherProvided;
+		/** @type {boolean | undefined} */
+		this.otherCanMangleProvide = otherCanMangleProvide;
+		/** @type {boolean} */
+		this.otherTerminalBinding = otherTerminalBinding;
+	}
+
+	/**
+	 * Serializes this instance into the provided serializer context.
+	 * @param {ObjectSerializerContext} context context
+	 */
+	serialize(context) {
+		context
+			.write(this.exports)
+			.write(this.otherProvided)
+			.write(this.otherCanMangleProvide)
+			.write(this.otherTerminalBinding);
+	}
+
+	/**
+	 * Restores this instance from the provided deserializer context.
+	 * @param {ObjectDeserializerContext} context context
+	 * @returns {RestoreProvidedData} RestoreProvidedData
+	 */
+	static deserialize(context) {
+		const exports = context.read();
+		const c1 = context.rest;
+		const otherProvided = c1.read();
+		const c2 = c1.rest;
+		const otherCanMangleProvide = c2.read();
+		const c3 = c2.rest;
+		const otherTerminalBinding = c3.read();
+		return new RestoreProvidedData(
+			exports,
+			otherProvided,
+			otherCanMangleProvide,
+			otherTerminalBinding
+		);
+	}
+}
+
+makeSerializable(
+	RestoreProvidedData,
+	"webpack/lib/ModuleGraph",
+	"RestoreProvidedData"
+);
+
+class ExportsInfo {
+	constructor() {
+		/** @type {Exports} */
+		this._exports = new Map();
+
+		// `_otherExportsInfo` is a fallback entry for unlisted exports. Two roles:
+		// 1. factory template — `getExportInfo` creates `new ExportInfo(name, this)`,
+		//    so created export info extends its properties.
+		// 2. flags whether the whole exportsInfo can be statically analyzed.
+		// Its `used` reachable values:
+		// 	- NoInfo: no use analysis yet (`optimization#usedExports` off), or used without info
+		// 	- Unused: analyzed, no unlisted export needed
+		// 	- Unknown: used in unknown way
+		// 	- Used/OnlyPropertiesUsed: never reached
+		// Its `provided` reachable values:
+		// 	- undefined: provision not determined yet
+		// 	- false: determined, no unlisted export is provided
+		// 	- null: only runtime knows (dynamic/unknown exports)
+		// 	- true: never reached
+		/** @type {ExportInfo} */
+		this._otherExportsInfo = new ExportInfo(null);
+		/** @type {ExportInfo} */
+		this._sideEffectsOnlyInfo = new ExportInfo("*side effects only*");
+		/** @type {boolean} */
+		this._exportsAreOrdered = false;
+		/** @type {ExportsInfo=} */
+		this._redirectTo = undefined;
+		// fast-path flag for hasInlinedUsedName; set by InlineExportsPlugin
+		/** @type {boolean} */
+		this._hasInlinedExports = false;
+	}
+
+	/**
+	 * Records that an owned export got an inlined used name.
+	 * @returns {void}
+	 */
+	markInlinedExports() {
+		this._hasInlinedExports = true;
+	}
+
+	/**
+	 * Gets owned exports.
+	 * @returns {Iterable} all owned exports in any order
+	 */
+	get ownedExports() {
+		return this._exports.values();
+	}
+
+	/**
+	 * Gets ordered owned exports.
+	 * @returns {Iterable} all owned exports in order
+	 */
+	get orderedOwnedExports() {
+		if (!this._exportsAreOrdered) {
+			this._sortExports();
+		}
+		return this._exports.values();
+	}
+
+	/**
+	 * Returns all exports in any order.
+	 * @returns {Iterable} all exports in any order
+	 */
+	get exports() {
+		if (this._redirectTo !== undefined) {
+			const map = new Map(this._redirectTo._exports);
+			for (const [key, value] of this._exports) {
+				map.set(key, value);
+			}
+			return map.values();
+		}
+		return this._exports.values();
+	}
+
+	/**
+	 * Gets ordered exports.
+	 * @returns {Iterable} all exports in order
+	 */
+	get orderedExports() {
+		if (!this._exportsAreOrdered) {
+			this._sortExports();
+		}
+		if (this._redirectTo !== undefined) {
+			/** @type {Exports} */
+			const map = new Map(
+				Array.from(this._redirectTo.orderedExports, (item) => [item.name, item])
+			);
+			for (const [key, value] of this._exports) {
+				map.set(key, value);
+			}
+			// sorting should be pretty fast as map contains
+			// a lot of presorted items
+			this._sortExportsMap(map);
+			return map.values();
+		}
+		return this._exports.values();
+	}
+
+	/**
+	 * Gets other exports info.
+	 * @returns {ExportInfo} the export info of unlisted exports
+	 */
+	get otherExportsInfo() {
+		if (this._redirectTo !== undefined) {
+			return this._redirectTo.otherExportsInfo;
+		}
+		return this._otherExportsInfo;
+	}
+
+	/**
+	 * Processes the provided export.
+	 * @param {Exports} exports exports
+	 * @private
+	 */
+	_sortExportsMap(exports) {
+		if (exports.size > 1) {
+			/** @type {ExportInfoName[]} */
+			const namesInOrder = [];
+			for (const entry of exports.values()) {
+				namesInOrder.push(entry.name);
+			}
+			namesInOrder.sort();
+			let i = 0;
+			for (const entry of exports.values()) {
+				const name = namesInOrder[i];
+				if (entry.name !== name) break;
+				i++;
+			}
+			for (; i < namesInOrder.length; i++) {
+				const name = namesInOrder[i];
+				const correctEntry = /** @type {ExportInfo} */ (exports.get(name));
+				exports.delete(name);
+				exports.set(name, correctEntry);
+			}
+		}
+	}
+
+	_sortExports() {
+		this._sortExportsMap(this._exports);
+		this._exportsAreOrdered = true;
+	}
+
+	/**
+	 * Sets redirect named to.
+	 * @param {ExportsInfo | undefined} exportsInfo exports info
+	 * @returns {boolean} result
+	 */
+	setRedirectNamedTo(exportsInfo) {
+		if (this._redirectTo === exportsInfo) return false;
+		this._redirectTo = exportsInfo;
+		return true;
+	}
+
+	setHasProvideInfo() {
+		for (const exportInfo of this._exports.values()) {
+			exportInfo.setHasProvideInfo();
+		}
+		if (this._redirectTo !== undefined) {
+			this._redirectTo.setHasProvideInfo();
+		} else {
+			this._otherExportsInfo.setHasProvideInfo();
+		}
+	}
+
+	/**
+	 * Resets provide-side info so FlagDependencyExports can recompute it on an
+	 * incremental rebuild against the persisted module graph (which otherwise
+	 * keeps exports removed by the rebuild, since the plugin only merges).
+	 * @returns {void}
+	 */
+	_resetProvidedExports() {
+		for (const exportInfo of this._exports.values()) {
+			exportInfo._resetProvideInfo();
+		}
+		this._redirectTo = undefined;
+		this._otherExportsInfo._resetProvideInfo();
+	}
+
+	/**
+	 * Resets the use-side info to the undetermined state so FlagDependencyUsage
+	 * can re-trace usage on an incremental rebuild. The persisted graph keeps a
+	 * module marked "used", which stops the trace re-queuing it, so exports newly
+	 * used by a changed importer would never be flagged.
+	 * @returns {void}
+	 */
+	_resetUsedExports() {
+		for (const exportInfo of this._exports.values()) {
+			exportInfo._resetUseInfo();
+		}
+		this._sideEffectsOnlyInfo._resetUseInfo();
+		// every InlinedUsedName was just cleared, so drop the fast-path flag
+		this._hasInlinedExports = false;
+		// never follow _redirectTo: it points into another module's exports info
+		// (FlagDependencyExports targets), whose own reset/re-trace owns that state
+		this._otherExportsInfo._resetUseInfo();
+	}
+
+	setHasUseInfo() {
+		for (const exportInfo of this._exports.values()) {
+			exportInfo.setHasUseInfo();
+		}
+		this._sideEffectsOnlyInfo.setHasUseInfo();
+		if (this._redirectTo !== undefined) {
+			this._redirectTo.setHasUseInfo();
+		} else {
+			this._otherExportsInfo.setHasUseInfo();
+		}
+	}
+
+	/**
+	 * Gets own export info.
+	 * @param {ExportInfoName} name export name
+	 * @returns {ExportInfo} export info for this name
+	 */
+	getOwnExportInfo(name) {
+		const info = this._exports.get(name);
+		if (info !== undefined) return info;
+		const newInfo = new ExportInfo(name, this._otherExportsInfo);
+		this._exports.set(name, newInfo);
+		this._exportsAreOrdered = false;
+		return newInfo;
+	}
+
+	/**
+	 * Returns export info for this name.
+	 * @param {ExportInfoName} name export name
+	 * @returns {ExportInfo} export info for this name
+	 */
+	getExportInfo(name) {
+		const info = this._exports.get(name);
+		if (info !== undefined) return info;
+		if (this._redirectTo !== undefined) {
+			return this._redirectTo.getExportInfo(name);
+		}
+		const newInfo = new ExportInfo(name, this._otherExportsInfo);
+		this._exports.set(name, newInfo);
+		this._exportsAreOrdered = false;
+		return newInfo;
+	}
+
+	/**
+	 * Gets read only export info.
+	 * @param {ExportInfoName} name export name
+	 * @returns {ExportInfo} export info for this name
+	 */
+	getReadOnlyExportInfo(name) {
+		const info = this._exports.get(name);
+		if (info !== undefined) return info;
+		if (this._redirectTo !== undefined) {
+			return this._redirectTo.getReadOnlyExportInfo(name);
+		}
+		return this._otherExportsInfo;
+	}
+
+	/**
+	 * Gets read only export info recursive.
+	 * @param {ExportInfoName[]} name export name
+	 * @returns {ExportInfo | undefined} export info for this name
+	 */
+	getReadOnlyExportInfoRecursive(name) {
+		/** @type {ExportsInfo} */
+		let exportsInfo = this;
+		const last = name.length - 1;
+		for (let i = 0; ; i++) {
+			const exportInfo = exportsInfo.getReadOnlyExportInfo(name[i]);
+			if (i === last) return exportInfo;
+			if (!exportInfo.exportsInfo) return;
+			exportsInfo = exportInfo.exportsInfo;
+		}
+	}
+
+	/**
+	 * Gets nested exports info.
+	 * @param {ExportInfoName[]=} name the export name
+	 * @returns {ExportsInfo | undefined} the nested exports info
+	 */
+	getNestedExportsInfo(name) {
+		if (Array.isArray(name) && name.length > 0) {
+			/** @type {ExportsInfo} */
+			let exportsInfo = this;
+			for (let i = 0; i < name.length; i++) {
+				const info = exportsInfo.getReadOnlyExportInfo(name[i]);
+				if (!info.exportsInfo) return;
+				exportsInfo = info.exportsInfo;
+			}
+			return exportsInfo;
+		}
+		return this;
+	}
+
+	/**
+	 * Sets unknown exports provided.
+	 * @param {boolean=} canMangle true, if exports can still be mangled (defaults to false)
+	 * @param {ExportsSpecExcludeExports=} excludeExports list of unaffected exports
+	 * @param {Dependency=} targetKey use this as key for the target
+	 * @param {ModuleGraphConnection=} targetModule set this module as target
+	 * @param {number=} priority priority
+	 * @returns {boolean} true, if this call changed something
+	 */
+	setUnknownExportsProvided(
+		canMangle,
+		excludeExports,
+		targetKey,
+		targetModule,
+		priority
+	) {
+		let changed = false;
+		if (excludeExports) {
+			for (const name of excludeExports) {
+				// Make sure these entries exist, so they can get different info
+				this.getExportInfo(name);
+			}
+		}
+		for (const exportInfo of this._exports.values()) {
+			if (!canMangle && exportInfo.canMangleProvide !== false) {
+				exportInfo.canMangleProvide = false;
+				changed = true;
+			}
+			if (excludeExports && excludeExports.has(exportInfo.name)) continue;
+			if (exportInfo.provided !== true && exportInfo.provided !== null) {
+				exportInfo.provided = null;
+				changed = true;
+			}
+			if (targetKey) {
+				exportInfo.setTarget(
+					targetKey,
+					/** @type {ModuleGraphConnection} */
+					(targetModule),
+					[exportInfo.name],
+					-1
+				);
+			}
+		}
+		if (this._redirectTo !== undefined) {
+			if (
+				this._redirectTo.setUnknownExportsProvided(
+					canMangle,
+					excludeExports,
+					targetKey,
+					targetModule,
+					priority
+				)
+			) {
+				changed = true;
+			}
+		} else {
+			if (
+				this._otherExportsInfo.provided !== true &&
+				this._otherExportsInfo.provided !== null
+			) {
+				this._otherExportsInfo.provided = null;
+				changed = true;
+			}
+			if (!canMangle && this._otherExportsInfo.canMangleProvide !== false) {
+				this._otherExportsInfo.canMangleProvide = false;
+				changed = true;
+			}
+			if (targetKey) {
+				this._otherExportsInfo.setTarget(
+					targetKey,
+					/** @type {ModuleGraphConnection} */ (targetModule),
+					undefined,
+					priority
+				);
+			}
+		}
+		return changed;
+	}
+
+	/**
+	 * Sets used in unknown way.
+	 * @param {RuntimeSpec} runtime the runtime
+	 * @returns {boolean} true, when something changed
+	 */
+	setUsedInUnknownWay(runtime) {
+		let changed = false;
+		for (const exportInfo of this._exports.values()) {
+			if (exportInfo.setUsedInUnknownWay(runtime)) {
+				changed = true;
+			}
+		}
+		if (this._redirectTo !== undefined) {
+			if (this._redirectTo.setUsedInUnknownWay(runtime)) {
+				changed = true;
+			}
+		} else if (this._otherExportsInfo.setUsedInUnknownWay(runtime)) {
+			changed = true;
+		}
+		return changed;
+	}
+
+	/**
+	 * Sets used without info.
+	 * @param {RuntimeSpec} runtime the runtime
+	 * @returns {boolean} true, when something changed
+	 */
+	setUsedWithoutInfo(runtime) {
+		let changed = false;
+		for (const exportInfo of this._exports.values()) {
+			if (exportInfo.setUsedWithoutInfo(runtime)) {
+				changed = true;
+			}
+		}
+		if (this._redirectTo !== undefined) {
+			if (this._redirectTo.setUsedWithoutInfo(runtime)) {
+				changed = true;
+			}
+		} else if (this._otherExportsInfo.setUsedWithoutInfo(runtime)) {
+			changed = true;
+		}
+		return changed;
+	}
+
+	/**
+	 * Sets all known exports used.
+	 * @param {RuntimeSpec} runtime the runtime
+	 * @returns {boolean} true, when something changed
+	 */
+	setAllKnownExportsUsed(runtime) {
+		let changed = false;
+		for (const exportInfo of this._exports.values()) {
+			if (!exportInfo.provided) continue;
+			if (exportInfo.setUsed(UsageState.Used, runtime)) {
+				changed = true;
+			}
+		}
+		return changed;
+	}
+
+	/**
+	 * Sets used for side effects only.
+	 * @param {RuntimeSpec} runtime the runtime
+	 * @returns {boolean} true, when something changed
+	 */
+	setUsedForSideEffectsOnly(runtime) {
+		return this._sideEffectsOnlyInfo.setUsedConditionally(
+			(used) => used === UsageState.Unused,
+			UsageState.Used,
+			runtime
+		);
+	}
+
+	/**
+	 * Checks whether this exports info is used.
+	 * @param {RuntimeSpec} runtime the runtime
+	 * @returns {boolean} true, when the module exports are used in any way
+	 */
+	isUsed(runtime) {
+		if (this._redirectTo !== undefined) {
+			if (this._redirectTo.isUsed(runtime)) {
+				return true;
+			}
+		} else if (this._otherExportsInfo.getUsed(runtime) !== UsageState.Unused) {
+			return true;
+		}
+		for (const exportInfo of this._exports.values()) {
+			if (exportInfo.getUsed(runtime) !== UsageState.Unused) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Checks whether this exports info is module used.
+	 * @param {RuntimeSpec} runtime the runtime
+	 * @returns {boolean} true, when the module is used in any way
+	 */
+	isModuleUsed(runtime) {
+		if (this.isUsed(runtime)) return true;
+		if (this._sideEffectsOnlyInfo.getUsed(runtime) !== UsageState.Unused) {
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Returns set of used exports, or true (when namespace object is used), or false (when unused), or null (when unknown).
+	 * @param {RuntimeSpec} runtime the runtime
+	 * @returns {SortableSet | boolean | null} set of used exports, or true (when namespace object is used), or false (when unused), or null (when unknown)
+	 */
+	getUsedExports(runtime) {
+		switch (this._otherExportsInfo.getUsed(runtime)) {
+			case UsageState.NoInfo:
+				return null;
+			case UsageState.Unknown:
+			case UsageState.OnlyPropertiesUsed:
+			case UsageState.Used:
+				return true;
+		}
+
+		/** @type {ExportInfoName[]} */
+		const array = [];
+		if (!this._exportsAreOrdered) this._sortExports();
+		for (const exportInfo of this._exports.values()) {
+			switch (exportInfo.getUsed(runtime)) {
+				case UsageState.NoInfo:
+					return null;
+				case UsageState.Unknown:
+					return true;
+				case UsageState.OnlyPropertiesUsed:
+				case UsageState.Used:
+					array.push(exportInfo.name);
+			}
+		}
+		if (this._redirectTo !== undefined) {
+			const inner = this._redirectTo.getUsedExports(runtime);
+			if (inner === null) return null;
+			if (inner === true) return true;
+			if (inner !== false) {
+				for (const item of inner) {
+					array.push(item);
+				}
+			}
+		}
+		if (array.length === 0) {
+			switch (this._sideEffectsOnlyInfo.getUsed(runtime)) {
+				case UsageState.NoInfo:
+					return null;
+				case UsageState.Unused:
+					return false;
+			}
+		}
+		return /** @type {SortableSet} */ (new SortableSet(array));
+	}
+
+	/**
+	 * Gets provided exports.
+	 * @returns {null | true | ExportInfoName[]} list of exports when known
+	 */
+	getProvidedExports() {
+		switch (this._otherExportsInfo.provided) {
+			case undefined:
+				return null;
+			case null:
+				return true;
+			case true:
+				return true;
+		}
+
+		/** @type {ExportInfoName[]} */
+		const array = [];
+		if (!this._exportsAreOrdered) this._sortExports();
+		for (const exportInfo of this._exports.values()) {
+			switch (exportInfo.provided) {
+				case undefined:
+					return null;
+				case null:
+					return true;
+				case true:
+					array.push(exportInfo.name);
+			}
+		}
+		if (this._redirectTo !== undefined) {
+			const inner = this._redirectTo.getProvidedExports();
+			if (inner === null) return null;
+			if (inner === true) return true;
+			for (const item of inner) {
+				if (!array.includes(item)) {
+					array.push(item);
+				}
+			}
+		}
+		return array;
+	}
+
+	/**
+	 * Gets relevant exports.
+	 * @param {RuntimeSpec} runtime the runtime
+	 * @returns {ExportInfo[]} exports that are relevant (not unused and potential provided)
+	 */
+	getRelevantExports(runtime) {
+		/** @type {ExportInfo[]} */
+		const list = [];
+		for (const exportInfo of this._exports.values()) {
+			const used = exportInfo.getUsed(runtime);
+			if (used === UsageState.Unused) continue;
+			if (exportInfo.provided === false) continue;
+			list.push(exportInfo);
+		}
+		if (this._redirectTo !== undefined) {
+			for (const exportInfo of this._redirectTo.getRelevantExports(runtime)) {
+				if (!this._exports.has(exportInfo.name)) list.push(exportInfo);
+			}
+		}
+		if (
+			this._otherExportsInfo.provided !== false &&
+			this._otherExportsInfo.getUsed(runtime) !== UsageState.Unused
+		) {
+			list.push(this._otherExportsInfo);
+		}
+		return list;
+	}
+
+	/**
+	 * Checks whether this exports info is export provided.
+	 * @param {ExportInfoName | ExportInfoName[]} name the name of the export
+	 * @returns {boolean | undefined | null} if the export is provided
+	 */
+	isExportProvided(name) {
+		if (Array.isArray(name)) {
+			/** @type {ExportsInfo} */
+			let exportsInfo = this;
+			const last = name.length - 1;
+			for (let i = 0; ; i++) {
+				const info = exportsInfo.getReadOnlyExportInfo(name[i]);
+				if (info.exportsInfo && i < last) {
+					exportsInfo = info.exportsInfo;
+					continue;
+				}
+				return info.provided ? i === last || undefined : info.provided;
+			}
+		}
+		const info = this.getReadOnlyExportInfo(name);
+		return info.provided;
+	}
+
+	/**
+	 * Returns key representing the usage.
+	 * @param {RuntimeSpec} runtime runtime
+	 * @returns {string} key representing the usage
+	 */
+	getUsageKey(runtime) {
+		/** @type {(string | number)[]} */
+		const key = [];
+		if (this._redirectTo !== undefined) {
+			key.push(this._redirectTo.getUsageKey(runtime));
+		} else {
+			key.push(this._otherExportsInfo.getUsed(runtime));
+		}
+		key.push(this._sideEffectsOnlyInfo.getUsed(runtime));
+		for (const exportInfo of this.orderedOwnedExports) {
+			key.push(exportInfo.getUsed(runtime));
+		}
+		return key.join("|");
+	}
+
+	/**
+	 * Checks whether this exports info is equally used.
+	 * @param {RuntimeSpec} runtimeA first runtime
+	 * @param {RuntimeSpec} runtimeB second runtime
+	 * @returns {boolean} true, when equally used
+	 */
+	isEquallyUsed(runtimeA, runtimeB) {
+		if (this._redirectTo !== undefined) {
+			if (!this._redirectTo.isEquallyUsed(runtimeA, runtimeB)) return false;
+		} else if (
+			this._otherExportsInfo.getUsed(runtimeA) !==
+			this._otherExportsInfo.getUsed(runtimeB)
+		) {
+			return false;
+		}
+		if (
+			this._sideEffectsOnlyInfo.getUsed(runtimeA) !==
+			this._sideEffectsOnlyInfo.getUsed(runtimeB)
+		) {
+			return false;
+		}
+		for (const exportInfo of this.ownedExports) {
+			if (exportInfo.getUsed(runtimeA) !== exportInfo.getUsed(runtimeB)) {
+				return false;
+			}
+		}
+		return true;
+	}
+
+	/**
+	 * Returns usage status.
+	 * @param {ExportInfoName | ExportInfoName[]} name export name
+	 * @param {RuntimeSpec} runtime check usage for this runtime only
+	 * @returns {UsageStateType} usage status
+	 */
+	getUsed(name, runtime) {
+		if (Array.isArray(name)) {
+			if (name.length === 0) return this.otherExportsInfo.getUsed(runtime);
+			// Walk the nested path iteratively to avoid a `slice` per level
+			/** @type {ExportsInfo} */
+			let exportsInfo = this;
+			const last = name.length - 1;
+			for (let i = 0; ; i++) {
+				const info = exportsInfo.getReadOnlyExportInfo(name[i]);
+				if (i === last || !info.exportsInfo) return info.getUsed(runtime);
+				exportsInfo = info.exportsInfo;
+			}
+		}
+		const info = this.getReadOnlyExportInfo(name);
+		return info.getUsed(runtime);
+	}
+
+	/**
+	 * Returns the used name.
+	 * @param {ExportInfoName | ExportInfoName[]} name the export name
+	 * @param {RuntimeSpec} runtime check usage for this runtime only
+	 * @returns {UsedName} the used name
+	 */
+	getUsedName(name, runtime) {
+		if (Array.isArray(name)) {
+			if (name.length === 0) {
+				if (!this.isUsed(runtime)) return false;
+				return name;
+			}
+			// Walk the nested path once, collecting used names into a single array
+			// instead of slicing/spreading at every level.
+			/** @type {ExportsInfo} */
+			let exportsInfo = this;
+			/** @type {ExportInfoName[] | undefined} */
+			let result;
+			const last = name.length - 1;
+			for (let i = 0; ; i++) {
+				const info = exportsInfo.getReadOnlyExportInfo(name[i]);
+				const x = info.getUsedName(name[i], runtime);
+				if (x === false) return false;
+				if (x instanceof InlinedUsedName) {
+					// The inlined value replaces the prefix; trailing ids become its suffix
+					if (i === last) return x;
+					return new InlinedUsedName(x.value, [
+						...x.suffix,
+						...name.slice(i + 1)
+					]);
+				}
+				if (i === last) {
+					if (result === undefined) return x === name[i] ? name : [x];
+					result.push(x);
+					return result;
+				}
+				if (result === undefined) result = [];
+				result.push(x);
+				// Descend only while the parent is reached purely via property access
+				if (
+					info.exportsInfo &&
+					info.getUsed(runtime) === UsageState.OnlyPropertiesUsed
+				) {
+					exportsInfo = info.exportsInfo;
+					continue;
+				}
+				// Cannot descend further: keep the remaining ids unchanged
+				for (let j = i + 1; j <= last; j++) result.push(name[j]);
+				return result;
+			}
+		}
+		const info = this.getReadOnlyExportInfo(name);
+		return info.getUsedName(name, runtime);
+	}
+
+	/**
+	 * Checks whether `getUsedName(name, runtime)` would return an `InlinedUsedName`,
+	 * without allocating intermediate used-name arrays (hot path for connection conditions).
+	 * @param {ExportInfoName[]} name the export name path
+	 * @param {RuntimeSpec} runtime check usage for this runtime only
+	 * @returns {boolean} true when the used name resolves to an inlined value
+	 */
+	hasInlinedUsedName(name, runtime) {
+		if (name.length === 0) return false;
+		/** @type {ExportsInfo} */
+		let exportsInfo = this;
+		const last = name.length - 1;
+		for (let i = 0; ; i++) {
+			// follow redirects for the fast-path flag; InlinedUsedName can only
+			// exist on a level whose (redirected) exportsInfo is marked
+			let hasInlined = false;
+			/** @type {ExportsInfo | undefined} */
+			let e = exportsInfo;
+			while (e !== undefined) {
+				if (e._hasInlinedExports) {
+					hasInlined = true;
+					break;
+				}
+				e = e._redirectTo;
+			}
+			if (!hasInlined && i === last) return false;
+			const info = exportsInfo.getReadOnlyExportInfo(name[i]);
+			if (hasInlined) {
+				const x = info.getUsedName(name[i], runtime);
+				if (x === false) return false;
+				if (x instanceof InlinedUsedName) return true;
+				if (i === last) return false;
+			}
+			if (
+				info.exportsInfo &&
+				info.getUsed(runtime) === UsageState.OnlyPropertiesUsed
+			) {
+				exportsInfo = info.exportsInfo;
+				continue;
+			}
+			return false;
+		}
+	}
+
+	/**
+	 * Updates the hash with the data contributed by this instance.
+	 * @param {Hash} hash the hash
+	 * @param {RuntimeSpec} runtime the runtime
+	 * @returns {void}
+	 */
+	updateHash(hash, runtime) {
+		this._updateHash(hash, runtime, new Set());
+	}
+
+	/**
+	 * Updates hash using the provided hash.
+	 * @param {Hash} hash the hash
+	 * @param {RuntimeSpec} runtime the runtime
+	 * @param {Set} alreadyVisitedExportsInfo for circular references
+	 * @returns {void}
+	 */
+	_updateHash(hash, runtime, alreadyVisitedExportsInfo) {
+		// add/delete keeps path semantics without copying the set per level
+		alreadyVisitedExportsInfo.add(this);
+		for (const exportInfo of this.orderedExports) {
+			if (exportInfo.hasInfo(this._otherExportsInfo, runtime)) {
+				exportInfo._updateHash(hash, runtime, alreadyVisitedExportsInfo);
+			}
+		}
+		this._sideEffectsOnlyInfo._updateHash(
+			hash,
+			runtime,
+			alreadyVisitedExportsInfo
+		);
+		this._otherExportsInfo._updateHash(
+			hash,
+			runtime,
+			alreadyVisitedExportsInfo
+		);
+		if (this._redirectTo !== undefined) {
+			this._redirectTo._updateHash(hash, runtime, alreadyVisitedExportsInfo);
+		}
+		alreadyVisitedExportsInfo.delete(this);
+	}
+
+	/**
+	 * Gets restore provided data.
+	 * @returns {RestoreProvidedData} restore provided data
+	 */
+	getRestoreProvidedData() {
+		const otherProvided = this._otherExportsInfo.provided;
+		const otherCanMangleProvide = this._otherExportsInfo.canMangleProvide;
+		const otherTerminalBinding = this._otherExportsInfo.terminalBinding;
+		/** @type {RestoreProvidedDataExports[]} */
+		const exports = [];
+		for (const exportInfo of this.orderedExports) {
+			const canInlineProvide = exportInfo.canInlineProvide;
+			const pureProvide = exportInfo.pureProvide;
+			// inline-exports data is only stored when present, so builds without the
+			// feature don't pay for it in the persistent cache
+			const hasInlineInfo =
+				canInlineProvide !== undefined || pureProvide !== undefined;
+			if (
+				exportInfo.provided !== otherProvided ||
+				exportInfo.canMangleProvide !== otherCanMangleProvide ||
+				exportInfo.terminalBinding !== otherTerminalBinding ||
+				hasInlineInfo ||
+				exportInfo.exportsInfoOwned
+			) {
+				const exportsInfo = exportInfo.exportsInfoOwned
+					? /** @type {NonNullable} */
+						(exportInfo.exportsInfo).getRestoreProvidedData()
+					: undefined;
+				exports.push(
+					hasInlineInfo
+						? {
+								name: exportInfo.name,
+								provided: exportInfo.provided,
+								canMangleProvide: exportInfo.canMangleProvide,
+								canInlineProvide,
+								terminalBinding: exportInfo.terminalBinding,
+								pureProvide,
+								exportsInfo
+							}
+						: {
+								name: exportInfo.name,
+								provided: exportInfo.provided,
+								canMangleProvide: exportInfo.canMangleProvide,
+								terminalBinding: exportInfo.terminalBinding,
+								exportsInfo
+							}
+				);
+			}
+		}
+		return new RestoreProvidedData(
+			exports,
+			otherProvided,
+			otherCanMangleProvide,
+			otherTerminalBinding
+		);
+	}
+
+	/**
+	 * Processes the provided data.
+	 * @param {RestoreProvidedData} data data
+	 */
+	restoreProvided({
+		otherProvided,
+		otherCanMangleProvide,
+		otherTerminalBinding,
+		exports
+	}) {
+		let wasEmpty = true;
+		for (const exportInfo of this._exports.values()) {
+			wasEmpty = false;
+			exportInfo.provided = otherProvided;
+			exportInfo.canMangleProvide = otherCanMangleProvide;
+			exportInfo.terminalBinding = otherTerminalBinding;
+		}
+		this._otherExportsInfo.provided = otherProvided;
+		this._otherExportsInfo.canMangleProvide = otherCanMangleProvide;
+		this._otherExportsInfo.terminalBinding = otherTerminalBinding;
+		for (const exp of exports) {
+			const exportInfo = this.getExportInfo(exp.name);
+			exportInfo.provided = exp.provided;
+			exportInfo.canMangleProvide = exp.canMangleProvide;
+			exportInfo.canInlineProvide = exp.canInlineProvide;
+			exportInfo.terminalBinding = exp.terminalBinding;
+			exportInfo.pureProvide = exp.pureProvide;
+			if (exp.exportsInfo) {
+				const exportsInfo = exportInfo.createNestedExportsInfo();
+				exportsInfo.restoreProvided(exp.exportsInfo);
+			}
+		}
+		if (wasEmpty) this._exportsAreOrdered = true;
+	}
+}
+
+class ExportInfo {
+	/**
+	 * Creates an instance of ExportInfo.
+	 * @param {ExportInfoName | null} name the original name of the export
+	 * @param {ExportInfo=} initFrom init values from this ExportInfo
+	 */
+	constructor(name, initFrom) {
+		/** @type {ExportInfoName} */
+		this.name = /** @type {ExportInfoName} */ (name);
+		/**
+		 * @private
+		 * @type {ExportInfoUsedName}
+		 */
+		this._usedName = initFrom ? initFrom._usedName : null;
+		/**
+		 * @private
+		 * @type {UsageStateType | undefined}
+		 */
+		this._globalUsed = initFrom ? initFrom._globalUsed : undefined;
+		/**
+		 * @private
+		 * @type {UsedInRuntime | undefined}
+		 */
+		this._usedInRuntime =
+			initFrom && initFrom._usedInRuntime
+				? new Map(initFrom._usedInRuntime)
+				: undefined;
+		/**
+		 * @private
+		 * @type {boolean}
+		 */
+		this._hasUseInRuntimeInfo = initFrom
+			? initFrom._hasUseInRuntimeInfo
+			: false;
+		/**
+		 * true: it is provided
+		 * false: it is not provided
+		 * null: only the runtime knows if it is provided
+		 * undefined: it was not determined if it is provided
+		 * @type {ExportInfoProvided | undefined}
+		 */
+		this.provided = initFrom ? initFrom.provided : undefined;
+		/**
+		 * is the export a terminal binding that should be checked for export star conflicts
+		 * @type {boolean}
+		 */
+		this.terminalBinding = initFrom ? initFrom.terminalBinding : false;
+		/**
+		 * true: it can be mangled
+		 * false: is can not be mangled
+		 * undefined: it was not determined if it can be mangled
+		 * @type {boolean | undefined}
+		 */
+		this.canMangleProvide = initFrom ? initFrom.canMangleProvide : undefined;
+		// only specific export info can be inlined,
+		// so _otherExportsInfo.canInlineProvide is always undefined
+		/**
+		 * value slot for `canInlineProvide`
+		 * @private
+		 * @type {InlinedValue | undefined}
+		 */
+		this._inlinedValue = undefined;
+		/**
+		 * true: it can be mangled
+		 * false: is can not be mangled
+		 * undefined: it was not determined if it can be mangled
+		 * @type {boolean | undefined}
+		 */
+		this.canMangleUse = initFrom ? initFrom.canMangleUse : undefined;
+		/**
+		 * bitfield backing `canInlineUse` and `pureProvide`
+		 * @private
+		 * @type {number}
+		 */
+		this._inlineFlags = 0;
+		/** @type {boolean} */
+		this.exportsInfoOwned = false;
+		/** @type {ExportsInfo | undefined} */
+		this.exportsInfo = undefined;
+		/** @type {Target | undefined} */
+		this._target = undefined;
+		if (initFrom && initFrom._target) {
+			this._target = /** @type {Target} */ (new Map());
+			for (const [key, value] of initFrom._target) {
+				this._target.set(key, {
+					connection: value.connection,
+					export: value.export || [name],
+					priority: value.priority
+				});
+			}
+		}
+		/** @type {Target | undefined} */
+		this._maxTarget = undefined;
+	}
+
+	/**
+	 * defined: the export binds to a small primitive constant and may be inlined
+	 * undefined: not an inlined constant export
+	 * @returns {InlinedValue | undefined} inlined value
+	 */
+	get canInlineProvide() {
+		return this._inlinedValue;
+	}
+
+	set canInlineProvide(value) {
+		this._inlinedValue = value;
+	}
+
+	/**
+	 * true: at least one consumer accepts inlining, none rejected
+	 * false: at least one consumer rejected inlining
+	 * undefined: collecting; no consumer has decided yet
+	 * @returns {boolean | undefined} can inline use
+	 */
+	get canInlineUse() {
+		switch (this._inlineFlags & INLINE_USE_MASK) {
+			case INLINE_USE_TRUE:
+				return true;
+			case INLINE_USE_FALSE:
+				return false;
+			default:
+				return undefined;
+		}
+	}
+
+	set canInlineUse(value) {
+		this._inlineFlags =
+			(this._inlineFlags & ~INLINE_USE_MASK) |
+			(value === undefined ? 0 : value ? INLINE_USE_TRUE : INLINE_USE_FALSE);
+	}
+
+	/**
+	 * Only specific export info can be pure, so other_export_info.pure is always undefined.
+	 * true: calling the export has no observable side effects
+	 * undefined: it was not determined whether the export is pure
+	 * @returns {boolean | undefined} pure provide
+	 */
+	get pureProvide() {
+		return (this._inlineFlags & PURE_PROVIDE_FLAG) !== 0 ? true : undefined;
+	}
+
+	set pureProvide(value) {
+		if (value) this._inlineFlags |= PURE_PROVIDE_FLAG;
+		else this._inlineFlags &= ~PURE_PROVIDE_FLAG;
+	}
+
+	get canMangle() {
+		switch (this.canMangleProvide) {
+			case undefined:
+				return this.canMangleUse === false ? false : undefined;
+			case false:
+				return false;
+			case true:
+				switch (this.canMangleUse) {
+					case undefined:
+						return undefined;
+					case false:
+						return false;
+					case true:
+						return true;
+				}
+		}
+		throw new Error(
+			`Unexpected flags for canMangle ${this.canMangleProvide} ${this.canMangleUse}`
+		);
+	}
+
+	/**
+	 * @returns {InlinedValue | undefined} the inlined value when both provide and use sides agree, otherwise undefined
+	 */
+	canInline() {
+		return this.canInlineProvide !== undefined && this.canInlineUse === true
+			? this.canInlineProvide
+			: undefined;
+	}
+
+	/**
+	 * Sets used in unknown way.
+	 * @param {RuntimeSpec} runtime only apply to this runtime
+	 * @returns {boolean} true, when something changed
+	 */
+	setUsedInUnknownWay(runtime) {
+		let changed = false;
+		if (
+			this.setUsedConditionally(
+				(used) => used < UsageState.Unknown,
+				UsageState.Unknown,
+				runtime
+			)
+		) {
+			changed = true;
+		}
+		if (this.canMangleUse !== false) {
+			this.canMangleUse = false;
+			changed = true;
+		}
+		if (this.canInlineUse !== false) {
+			this.canInlineUse = false;
+			changed = true;
+		}
+		return changed;
+	}
+
+	/**
+	 * Sets used without info.
+	 * @param {RuntimeSpec} runtime only apply to this runtime
+	 * @returns {boolean} true, when something changed
+	 */
+	setUsedWithoutInfo(runtime) {
+		let changed = false;
+		if (this.setUsed(UsageState.NoInfo, runtime)) {
+			changed = true;
+		}
+		if (this.canMangleUse !== false) {
+			this.canMangleUse = false;
+			changed = true;
+		}
+		if (this.canInlineUse !== false) {
+			this.canInlineUse = false;
+			changed = true;
+		}
+		return changed;
+	}
+
+	setHasProvideInfo() {
+		if (this.provided === undefined) {
+			this.provided = false;
+		}
+		if (this.canMangleProvide === undefined) {
+			this.canMangleProvide = true;
+		}
+		// mirror setHasUseInfo: after a provide reset the owned nested info is
+		// undefined again, and FlagDependencyExports only upgrades from false/null
+		if (this.exportsInfoOwned) {
+			/** @type {ExportsInfo} */
+			(this.exportsInfo).setHasProvideInfo();
+		}
+	}
+
+	/**
+	 * Resets provide-side info to the undetermined state (use info untouched).
+	 * @returns {void}
+	 */
+	_resetProvideInfo() {
+		this.provided = undefined;
+		this.terminalBinding = false;
+		this.canMangleProvide = undefined;
+		this._inlinedValue = undefined;
+		if (this.exportsInfoOwned) {
+			// reset the owned nested info in place so its use state survives
+			/** @type {ExportsInfo} */
+			(this.exportsInfo)._resetProvidedExports();
+		} else {
+			this.exportsInfo = undefined;
+		}
+		this._target = undefined;
+		// also drop the memoized max target so it cannot serve stale entries
+		this._maxTarget = undefined;
+		// clear the provide-side pure bit; FlagDependencyExports only ever sets it
+		// true, so an export that turned impure would otherwise stay marked pure
+		this._inlineFlags &= ~PURE_PROVIDE_FLAG;
+	}
+
+	/**
+	 * Resets the use-side info to the undetermined state (provide info untouched,
+	 * incl. the pure-provide bit) so FlagDependencyUsage can re-trace it.
+	 * @returns {void}
+	 */
+	_resetUseInfo() {
+		this._globalUsed = undefined;
+		this._usedInRuntime = undefined;
+		this._hasUseInRuntimeInfo = false;
+		this.canMangleUse = undefined;
+		this._usedName = null;
+		this._inlineFlags &= PURE_PROVIDE_FLAG;
+		// mirror setHasUseInfo: recurse into an owned nested exportsInfo, where
+		// FlagDependencyUsage also writes usage
+		if (this.exportsInfoOwned) {
+			/** @type {ExportsInfo} */
+			(this.exportsInfo)._resetUsedExports();
+		}
+	}
+
+	setHasUseInfo() {
+		if (!this._hasUseInRuntimeInfo) {
+			this._hasUseInRuntimeInfo = true;
+		}
+		if (this.canMangleUse === undefined) {
+			this.canMangleUse = true;
+		}
+		if (this.exportsInfoOwned) {
+			/** @type {ExportsInfo} */
+			(this.exportsInfo).setHasUseInfo();
+		}
+	}
+
+	/**
+	 * Sets used conditionally.
+	 * @param {(condition: UsageStateType) => boolean} condition compare with old value
+	 * @param {UsageStateType} newValue set when condition is true
+	 * @param {RuntimeSpec} runtime only apply to this runtime
+	 * @returns {boolean} true when something has changed
+	 */
+	setUsedConditionally(condition, newValue, runtime) {
+		if (runtime === undefined) {
+			if (this._globalUsed === undefined) {
+				this._globalUsed = newValue;
+				return true;
+			}
+			if (this._globalUsed !== newValue && condition(this._globalUsed)) {
+				this._globalUsed = newValue;
+				return true;
+			}
+		} else if (this._usedInRuntime === undefined) {
+			if (newValue !== UsageState.Unused && condition(UsageState.Unused)) {
+				this._usedInRuntime = new Map();
+				forEachRuntime(runtime, (runtime) =>
+					/** @type {UsedInRuntime} */
+					(this._usedInRuntime).set(/** @type {string} */ (runtime), newValue)
+				);
+				return true;
+			}
+		} else {
+			let changed = false;
+			forEachRuntime(runtime, (runtime_) => {
+				const runtime = /** @type {string} */ (runtime_);
+				const usedInRuntime =
+					/** @type {UsedInRuntime} */
+					(this._usedInRuntime);
+				let oldValue =
+					/** @type {UsageStateType} */
+					(usedInRuntime.get(runtime));
+				if (oldValue === undefined) oldValue = UsageState.Unused;
+				if (newValue !== oldValue && condition(oldValue)) {
+					if (newValue === UsageState.Unused) {
+						usedInRuntime.delete(runtime);
+					} else {
+						usedInRuntime.set(runtime, newValue);
+					}
+					changed = true;
+				}
+			});
+			if (changed) {
+				if (this._usedInRuntime.size === 0) this._usedInRuntime = undefined;
+				return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Updates used using the provided new value.
+	 * @param {UsageStateType} newValue new value of the used state
+	 * @param {RuntimeSpec} runtime only apply to this runtime
+	 * @returns {boolean} true when something has changed
+	 */
+	setUsed(newValue, runtime) {
+		if (runtime === undefined) {
+			if (this._globalUsed !== newValue) {
+				this._globalUsed = newValue;
+				return true;
+			}
+		} else if (this._usedInRuntime === undefined) {
+			if (newValue !== UsageState.Unused) {
+				this._usedInRuntime = new Map();
+				forEachRuntime(runtime, (runtime) =>
+					/** @type {UsedInRuntime} */
+					(this._usedInRuntime).set(/** @type {string} */ (runtime), newValue)
+				);
+				return true;
+			}
+		} else {
+			let changed = false;
+			forEachRuntime(runtime, (_runtime) => {
+				const runtime = /** @type {string} */ (_runtime);
+				const usedInRuntime =
+					/** @type {UsedInRuntime} */
+					(this._usedInRuntime);
+				let oldValue =
+					/** @type {UsageStateType} */
+					(usedInRuntime.get(runtime));
+				if (oldValue === undefined) oldValue = UsageState.Unused;
+				if (newValue !== oldValue) {
+					if (newValue === UsageState.Unused) {
+						usedInRuntime.delete(runtime);
+					} else {
+						usedInRuntime.set(runtime, newValue);
+					}
+					changed = true;
+				}
+			});
+			if (changed) {
+				if (this._usedInRuntime.size === 0) this._usedInRuntime = undefined;
+				return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Returns true, if something has changed.
+	 * @param {Dependency} key the key
+	 * @returns {boolean} true, if something has changed
+	 */
+	unsetTarget(key) {
+		if (!this._target) return false;
+		if (this._target.delete(key)) {
+			this._maxTarget = undefined;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Updates target using the provided key.
+	 * @param {Dependency} key the key
+	 * @param {ModuleGraphConnection} connection the target module if a single one
+	 * @param {ExportInfoName[] | null=} exportName the exported name
+	 * @param {number=} priority priority
+	 * @returns {boolean} true, if something has changed
+	 */
+	setTarget(key, connection, exportName, priority = 0) {
+		// `equals` compares element-wise, so the incoming `exportName` can be used
+		// directly for the change-check; only copy it when actually stored, so the
+		// common no-op (unchanged target) path allocates nothing.
+		if (!this._target) {
+			this._target = /** @type {Target} */ (new Map());
+			this._target.set(key, {
+				connection,
+				export: /** @type {ExportInfoName[]} */ (
+					exportName ? [...exportName] : exportName
+				),
+				priority
+			});
+			return true;
+		}
+		const oldTarget = this._target.get(key);
+		if (!oldTarget) {
+			if (oldTarget === null && !connection) return false;
+			this._target.set(key, {
+				connection,
+				export: /** @type {ExportInfoName[]} */ (
+					exportName ? [...exportName] : exportName
+				),
+				priority
+			});
+			this._maxTarget = undefined;
+			return true;
+		}
+		if (
+			oldTarget.connection !== connection ||
+			oldTarget.priority !== priority ||
+			(exportName
+				? !oldTarget.export || !equals(oldTarget.export, exportName)
+				: oldTarget.export)
+		) {
+			oldTarget.connection = connection;
+			oldTarget.export = /** @type {ExportInfoName[]} */ (
+				exportName ? [...exportName] : exportName
+			);
+			oldTarget.priority = priority;
+			this._maxTarget = undefined;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Returns usage state.
+	 * @param {RuntimeSpec} runtime for this runtime
+	 * @returns {UsageStateType} usage state
+	 */
+	getUsed(runtime) {
+		if (!this._hasUseInRuntimeInfo) return UsageState.NoInfo;
+		if (this._globalUsed !== undefined) return this._globalUsed;
+		if (this._usedInRuntime === undefined) {
+			return UsageState.Unused;
+		} else if (typeof runtime === "string") {
+			const value = this._usedInRuntime.get(runtime);
+			return value === undefined ? UsageState.Unused : value;
+		} else if (runtime === undefined) {
+			/** @type {UsageStateType} */
+			let max = UsageState.Unused;
+			for (const value of this._usedInRuntime.values()) {
+				if (value === UsageState.Used) {
+					return UsageState.Used;
+				}
+				if (max < value) max = value;
+			}
+			return max;
+		}
+
+		/** @type {UsageStateType} */
+		let max = UsageState.Unused;
+		for (const item of runtime) {
+			const value = this._usedInRuntime.get(item);
+			if (value !== undefined) {
+				if (value === UsageState.Used) {
+					return UsageState.Used;
+				}
+				if (max < value) max = value;
+			}
+		}
+		return max;
+	}
+
+	/**
+	 * Returns used name. May return InlinedUsedName when the export is inlined to a primitive.
+	 * @param {string | undefined} fallbackName fallback name for used exports with no name
+	 * @param {RuntimeSpec} runtime check usage for this runtime only
+	 * @returns {string | InlinedUsedName | false} used name
+	 */
+	getUsedName(fallbackName, runtime) {
+		if (this._hasUseInRuntimeInfo) {
+			if (this._globalUsed !== undefined) {
+				if (this._globalUsed === UsageState.Unused) return false;
+			} else {
+				if (this._usedInRuntime === undefined) return false;
+				if (typeof runtime === "string") {
+					if (!this._usedInRuntime.has(runtime)) {
+						return false;
+					}
+				} else if (runtime !== undefined) {
+					// Unused unless at least one runtime in the set is used; loop avoids
+					// allocating an array from the runtime set on this hot path.
+					let anyUsed = false;
+					for (const r of runtime) {
+						if (this._usedInRuntime.has(r)) {
+							anyUsed = true;
+							break;
+						}
+					}
+					if (!anyUsed) return false;
+				}
+			}
+		}
+		if (this._usedName !== null) return this._usedName;
+		return /** @type {string | false} */ (this.name || fallbackName);
+	}
+
+	/**
+	 * Checks whether this export info has used name.
+	 * @returns {boolean} true, when a mangled name of this export is set
+	 */
+	hasUsedName() {
+		return this._usedName !== null;
+	}
+
+	/**
+	 * Updates used name using the provided name.
+	 * @param {string | InlinedUsedName} name the new name
+	 * @returns {void}
+	 */
+	setUsedName(name) {
+		this._usedName = name;
+	}
+
+	/**
+	 * Gets terminal binding.
+	 * @param {ModuleGraph} moduleGraph the module graph
+	 * @param {ResolveTargetFilter} resolveTargetFilter filter function to further resolve target
+	 * @returns {ExportInfo | ExportsInfo | undefined} the terminal binding export(s) info if known
+	 */
+	getTerminalBinding(moduleGraph, resolveTargetFilter = RETURNS_TRUE) {
+		if (this.terminalBinding) return this;
+		const target = this.getTarget(moduleGraph, resolveTargetFilter);
+		if (!target) return;
+		const exportsInfo = moduleGraph.getExportsInfo(target.module);
+		if (!target.export) return exportsInfo;
+		return exportsInfo.getReadOnlyExportInfoRecursive(target.export);
+	}
+
+	isReexport() {
+		return !this.terminalBinding && this._target && this._target.size > 0;
+	}
+
+	_getMaxTarget() {
+		if (this._maxTarget !== undefined) return this._maxTarget;
+		if (/** @type {Target} */ (this._target).size <= 1) {
+			return (this._maxTarget = this._target);
+		}
+		let maxPriority = -Infinity;
+		let minPriority = Infinity;
+		for (const { priority } of /** @type {Target} */ (this._target).values()) {
+			if (maxPriority < priority) maxPriority = priority;
+			if (minPriority > priority) minPriority = priority;
+		}
+		// This should be very common
+		if (maxPriority === minPriority) return (this._maxTarget = this._target);
+
+		// This is an edge case
+		/** @type {Target} */
+		const map = new Map();
+		for (const [key, value] of /** @type {Target} */ (this._target)) {
+			if (maxPriority === value.priority) {
+				map.set(key, value);
+			}
+		}
+		this._maxTarget = map;
+		return map;
+	}
+
+	/**
+	 * Returns the target, undefined when there is no target, false when no target is valid.
+	 * @param {ModuleGraph} moduleGraph the module graph
+	 * @param {ValidTargetModuleFilter} validTargetModuleFilter a valid target module
+	 * @returns {TargetItemWithoutConnection | null | undefined | false} the target, undefined when there is no target, false when no target is valid
+	 */
+	findTarget(moduleGraph, validTargetModuleFilter) {
+		return this._findTarget(
+			moduleGraph,
+			validTargetModuleFilter,
+			EMPTY_VISITED
+		);
+	}
+
+	/**
+	 * Returns the target, undefined when there is no target, false when no target is valid.
+	 * @param {ModuleGraph} moduleGraph the module graph
+	 * @param {ValidTargetModuleFilter} validTargetModuleFilter a valid target module
+	 * @param {AlreadyVisitedExportInfo} alreadyVisited set of already visited export info to avoid circular references
+	 * @returns {TargetItemWithoutConnection | null | undefined | false} the target, undefined when there is no target, false when no target is valid
+	 */
+	_findTarget(moduleGraph, validTargetModuleFilter, alreadyVisited) {
+		if (!this._target || this._target.size === 0) return;
+		const rawTarget =
+			/** @type {Target} */
+			(this._getMaxTarget()).values().next().value;
+		if (!rawTarget) return;
+		/** @type {TargetItemWithoutConnection} */
+		let target = {
+			module: rawTarget.connection.module,
+			export: rawTarget.export,
+			deferred: Boolean(
+				rawTarget.connection.dependency &&
+				ImportPhaseUtils.isDefer(
+					/** @type {HarmonyImportDependency} */ (
+						rawTarget.connection.dependency
+					).phase
+				)
+			)
+		};
+		for (;;) {
+			if (validTargetModuleFilter(target.module)) return target;
+			const exportsInfo = moduleGraph.getExportsInfo(target.module);
+			const exportInfo = exportsInfo.getExportInfo(target.export[0]);
+			if (alreadyVisited.has(exportInfo)) return null;
+			const newTarget = exportInfo._findTarget(
+				moduleGraph,
+				validTargetModuleFilter,
+				alreadyVisited
+			);
+			if (!newTarget) return false;
+			if (target.export.length === 1) {
+				target = newTarget;
+			} else {
+				target = {
+					module: newTarget.module,
+					export: newTarget.export
+						? [...newTarget.export, ...target.export.slice(1)]
+						: target.export.slice(1),
+					deferred: newTarget.deferred
+				};
+			}
+		}
+	}
+
+	/**
+	 * Returns the target.
+	 * @param {ModuleGraph} moduleGraph the module graph
+	 * @param {ResolveTargetFilter} resolveTargetFilter filter function to further resolve target
+	 * @returns {TargetItemWithConnection | undefined} the target
+	 */
+	getTarget(moduleGraph, resolveTargetFilter = RETURNS_TRUE) {
+		const result = this._getTarget(moduleGraph, resolveTargetFilter, undefined);
+		if (result === CIRCULAR) return;
+		return result;
+	}
+
+	/**
+	 * Returns the target.
+	 * @param {ModuleGraph} moduleGraph the module graph
+	 * @param {ResolveTargetFilter} resolveTargetFilter filter function to further resolve target
+	 * @param {AlreadyVisitedExportInfo | undefined} alreadyVisited set of already visited export info to avoid circular references
+	 * @returns {TargetItemWithConnection | CIRCULAR | undefined} the target
+	 */
+	_getTarget(moduleGraph, resolveTargetFilter, alreadyVisited) {
+		/**
+		 * Returns resolved target.
+		 * @param {TargetItem | undefined | null} inputTarget unresolved target
+		 * @param {AlreadyVisitedExportInfo} alreadyVisited set of already visited export info to avoid circular references
+		 * @returns {TargetItemWithConnection | CIRCULAR | null} resolved target
+		 */
+		const resolveTarget = (inputTarget, alreadyVisited) => {
+			if (!inputTarget) return null;
+			if (!inputTarget.export) {
+				return {
+					module: inputTarget.connection.module,
+					connection: inputTarget.connection,
+					export: undefined
+				};
+			}
+			/** @type {TargetItemWithConnection} */
+			let target = {
+				module: inputTarget.connection.module,
+				connection: inputTarget.connection,
+				export: inputTarget.export
+			};
+			if (!resolveTargetFilter(target)) return target;
+			let alreadyVisitedOwned = false;
+			for (;;) {
+				const exportsInfo = moduleGraph.getExportsInfo(target.module);
+				const exportInfo = exportsInfo.getExportInfo(
+					/** @type {NonNullable} */
+					(target.export)[0]
+				);
+				if (!exportInfo) return target;
+				if (alreadyVisited.has(exportInfo)) return CIRCULAR;
+				const newTarget = exportInfo._getTarget(
+					moduleGraph,
+					resolveTargetFilter,
+					alreadyVisited
+				);
+				if (newTarget === CIRCULAR) return CIRCULAR;
+				if (!newTarget) return target;
+				if (
+					/** @type {NonNullable} */
+					(target.export).length === 1
+				) {
+					target = newTarget;
+					if (!target.export) return target;
+				} else {
+					target = {
+						module: newTarget.module,
+						connection: newTarget.connection,
+						export: newTarget.export
+							? [
+									...newTarget.export,
+									.../** @type {NonNullable} */
+									(target.export).slice(1)
+								]
+							: /** @type {NonNullable} */
+								(target.export).slice(1)
+					};
+				}
+				if (!resolveTargetFilter(target)) return target;
+				if (!alreadyVisitedOwned) {
+					alreadyVisited = new Set(alreadyVisited);
+					alreadyVisitedOwned = true;
+				}
+				alreadyVisited.add(exportInfo);
+			}
+		};
+
+		if (!this._target || this._target.size === 0) return;
+		if (alreadyVisited && alreadyVisited.has(this)) return CIRCULAR;
+		const newAlreadyVisited = new Set(alreadyVisited);
+		newAlreadyVisited.add(this);
+		const values = /** @type {Target} */ (this._getMaxTarget()).values();
+		const target = resolveTarget(values.next().value, newAlreadyVisited);
+		if (target === CIRCULAR) return CIRCULAR;
+		if (target === null) return;
+		let result = values.next();
+		while (!result.done) {
+			const t = resolveTarget(result.value, newAlreadyVisited);
+			if (t === CIRCULAR) return CIRCULAR;
+			if (t === null) return;
+			if (t.module !== target.module) return;
+			if (!t.export !== !target.export) return;
+			if (
+				target.export &&
+				!equals(/** @type {ArrayLike} */ (t.export), target.export)
+			) {
+				return;
+			}
+			result = values.next();
+		}
+		return target;
+	}
+
+	/**
+	 * Move the target forward as long resolveTargetFilter is fulfilled
+	 * @param {ModuleGraph} moduleGraph the module graph
+	 * @param {ResolveTargetFilter} resolveTargetFilter filter function to further resolve target
+	 * @param {((target: TargetItemWithConnection) => ModuleGraphConnection | undefined)=} updateOriginalConnection updates the original connection instead of using the target connection
+	 * @returns {TargetItemWithConnection | undefined} the resolved target when moved
+	 */
+	moveTarget(moduleGraph, resolveTargetFilter, updateOriginalConnection) {
+		const target = this._getTarget(moduleGraph, resolveTargetFilter, undefined);
+		if (target === CIRCULAR) return;
+		if (!target) return;
+		const originalTarget =
+			/** @type {TargetItem} */
+			(
+				/** @type {Target} */
+				(this._getMaxTarget()).values().next().value
+			);
+		if (
+			originalTarget.connection === target.connection &&
+			originalTarget.export === target.export
+		) {
+			return;
+		}
+		/** @type {Target} */
+		(this._target).clear();
+		/** @type {Target} */
+		(this._target).set(undefined, {
+			connection: updateOriginalConnection
+				? updateOriginalConnection(target) || target.connection
+				: target.connection,
+			export: /** @type {NonNullable} */ (
+				target.export
+			),
+			priority: 0
+		});
+		return target;
+	}
+
+	/**
+	 * Creates a nested exports info.
+	 * @returns {ExportsInfo} an exports info
+	 */
+	createNestedExportsInfo() {
+		if (this.exportsInfoOwned) {
+			return /** @type {ExportsInfo} */ (this.exportsInfo);
+		}
+		this.exportsInfoOwned = true;
+		const oldExportsInfo = this.exportsInfo;
+		this.exportsInfo = new ExportsInfo();
+		this.exportsInfo.setHasProvideInfo();
+		if (oldExportsInfo) {
+			this.exportsInfo.setRedirectNamedTo(oldExportsInfo);
+		}
+		return this.exportsInfo;
+	}
+
+	getNestedExportsInfo() {
+		return this.exportsInfo;
+	}
+
+	/**
+	 * Checks whether this export info contains the base info.
+	 * @param {ExportInfo} baseInfo base info
+	 * @param {RuntimeSpec} runtime runtime
+	 * @returns {boolean} true when has info, otherwise false
+	 */
+	hasInfo(baseInfo, runtime) {
+		return (
+			(this._usedName && this._usedName !== this.name) ||
+			this.provided ||
+			this.terminalBinding ||
+			this.getUsed(runtime) !== baseInfo.getUsed(runtime)
+		);
+	}
+
+	/**
+	 * Updates the hash with the data contributed by this instance.
+	 * @param {Hash} hash the hash
+	 * @param {RuntimeSpec} runtime the runtime
+	 * @returns {void}
+	 */
+	updateHash(hash, runtime) {
+		this._updateHash(hash, runtime, new Set());
+	}
+
+	/**
+	 * Updates hash using the provided hash.
+	 * @param {Hash} hash the hash
+	 * @param {RuntimeSpec} runtime the runtime
+	 * @param {Set} alreadyVisitedExportsInfo for circular references
+	 */
+	_updateHash(hash, runtime, alreadyVisitedExportsInfo) {
+		const usedNameHash =
+			this._usedName instanceof InlinedUsedName
+				? `inlined:${this._usedName.render("")}`
+				: this._usedName || this.name;
+		hash.update(
+			`${usedNameHash}${this.getUsed(runtime)}${this.provided}${
+				this.terminalBinding
+			}${this.pureProvide ? "P" : ""}`
+		);
+		if (this.exportsInfo && !alreadyVisitedExportsInfo.has(this.exportsInfo)) {
+			this.exportsInfo._updateHash(hash, runtime, alreadyVisitedExportsInfo);
+		}
+	}
+
+	getUsedInfo() {
+		if (this._globalUsed !== undefined) {
+			switch (this._globalUsed) {
+				case UsageState.Unused:
+					return "unused";
+				case UsageState.NoInfo:
+					return "no usage info";
+				case UsageState.Unknown:
+					return "maybe used (runtime-defined)";
+				case UsageState.Used:
+					return "used";
+				case UsageState.OnlyPropertiesUsed:
+					return "only properties used";
+			}
+		} else if (this._usedInRuntime !== undefined) {
+			/** @type {Map} */
+			const map = new Map();
+			for (const [runtime, used] of this._usedInRuntime) {
+				const list = map.get(used);
+				if (list !== undefined) list.push(runtime);
+				else map.set(used, [runtime]);
+			}
+			// eslint-disable-next-line array-callback-return
+			const specificInfo = Array.from(map, ([used, runtimes]) => {
+				switch (used) {
+					case UsageState.NoInfo:
+						return `no usage info in ${runtimes.join(", ")}`;
+					case UsageState.Unknown:
+						return `maybe used in ${runtimes.join(", ")} (runtime-defined)`;
+					case UsageState.Used:
+						return `used in ${runtimes.join(", ")}`;
+					case UsageState.OnlyPropertiesUsed:
+						return `only properties used in ${runtimes.join(", ")}`;
+				}
+			});
+			if (specificInfo.length > 0) {
+				return specificInfo.join("; ");
+			}
+		}
+		return this._hasUseInRuntimeInfo ? "unused" : "no usage info";
+	}
+
+	getProvidedInfo() {
+		switch (this.provided) {
+			case undefined:
+				return "no provided info";
+			case null:
+				return "maybe provided (runtime-defined)";
+			case true:
+				return "provided";
+			case false:
+				return "not provided";
+		}
+	}
+
+	getRenameInfo() {
+		if (this._usedName !== null && this._usedName !== this.name) {
+			return `renamed to ${JSON.stringify(this._usedName).slice(1, -1)}`;
+		}
+		switch (this.canMangleProvide) {
+			case undefined:
+				switch (this.canMangleUse) {
+					case undefined:
+						return "missing provision and use info prevents renaming";
+					case false:
+						return "usage prevents renaming (no provision info)";
+					case true:
+						return "missing provision info prevents renaming";
+				}
+				break;
+			case true:
+				switch (this.canMangleUse) {
+					case undefined:
+						return "missing usage info prevents renaming";
+					case false:
+						return "usage prevents renaming";
+					case true:
+						return "could be renamed";
+				}
+				break;
+			case false:
+				switch (this.canMangleUse) {
+					case undefined:
+						return "provision prevents renaming (no use info)";
+					case false:
+						return "usage and provision prevents renaming";
+					case true:
+						return "provision prevents renaming";
+				}
+				break;
+		}
+		throw new Error(
+			`Unexpected flags for getRenameInfo ${this.canMangleProvide} ${this.canMangleUse}`
+		);
+	}
+}
+
+module.exports = ExportsInfo;
+module.exports.ExportInfo = ExportInfo;
+module.exports.RestoreProvidedData = RestoreProvidedData;
+module.exports.UsageState = UsageState;
diff --git a/lib/ExportsInfoApiPlugin.js b/lib/ExportsInfoApiPlugin.js
new file mode 100644
index 00000000000..2d6bce9d45d
--- /dev/null
+++ b/lib/ExportsInfoApiPlugin.js
@@ -0,0 +1,88 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const {
+	JAVASCRIPT_MODULE_TYPE_AUTO,
+	JAVASCRIPT_MODULE_TYPE_DYNAMIC,
+	JAVASCRIPT_MODULE_TYPE_ESM
+} = require("./ModuleTypeConstants");
+const ConstDependency = require("./dependencies/ConstDependency");
+const ExportsInfoDependency = require("./dependencies/ExportsInfoDependency");
+
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */
+/** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */
+/** @typedef {import("./javascript/JavascriptParser").Range} Range */
+
+const PLUGIN_NAME = "ExportsInfoApiPlugin";
+
+class ExportsInfoApiPlugin {
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
+	apply(compiler) {
+		compiler.hooks.compilation.tap(
+			PLUGIN_NAME,
+			(compilation, { normalModuleFactory }) => {
+				compilation.dependencyTemplates.set(
+					ExportsInfoDependency,
+					new ExportsInfoDependency.Template()
+				);
+				/**
+				 * Handles the hook callback for this code path.
+				 * @param {JavascriptParser} parser the parser
+				 * @returns {void}
+				 */
+				const handler = (parser) => {
+					parser.hooks.expressionMemberChain
+						.for("__webpack_exports_info__")
+						.tap(PLUGIN_NAME, (expr, members) => {
+							const dep =
+								members.length >= 2
+									? new ExportsInfoDependency(
+											/** @type {Range} */ (expr.range),
+											members.slice(0, -1),
+											members[members.length - 1]
+										)
+									: new ExportsInfoDependency(
+											/** @type {Range} */ (expr.range),
+											null,
+											members[0]
+										);
+							dep.loc = parser.getLocation(expr);
+							parser.state.module.addDependency(dep);
+							return true;
+						});
+					parser.hooks.expression
+						.for("__webpack_exports_info__")
+						.tap(PLUGIN_NAME, (expr) => {
+							const dep = new ConstDependency(
+								"true",
+								/** @type {Range} */ (expr.range)
+							);
+							dep.loc = parser.getLocation(expr);
+							parser.state.module.addPresentationalDependency(dep);
+							return true;
+						});
+				};
+				normalModuleFactory.hooks.parser
+					.for(JAVASCRIPT_MODULE_TYPE_AUTO)
+					.tap(PLUGIN_NAME, handler);
+				normalModuleFactory.hooks.parser
+					.for(JAVASCRIPT_MODULE_TYPE_DYNAMIC)
+					.tap(PLUGIN_NAME, handler);
+				normalModuleFactory.hooks.parser
+					.for(JAVASCRIPT_MODULE_TYPE_ESM)
+					.tap(PLUGIN_NAME, handler);
+			}
+		);
+	}
+}
+
+module.exports = ExportsInfoApiPlugin;
diff --git a/lib/ExtendedAPIPlugin.js b/lib/ExtendedAPIPlugin.js
deleted file mode 100644
index a894a9faf7a..00000000000
--- a/lib/ExtendedAPIPlugin.js
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const Template = require("./Template");
-const ConstDependency = require("./dependencies/ConstDependency");
-const ParserHelpers = require("./ParserHelpers");
-const NullFactory = require("./NullFactory");
-
-const REPLACEMENTS = {
-	__webpack_hash__: "__webpack_require__.h", // eslint-disable-line camelcase
-	__webpack_chunkname__: "__webpack_require__.cn" // eslint-disable-line camelcase
-};
-const REPLACEMENT_TYPES = {
-	__webpack_hash__: "string", // eslint-disable-line camelcase
-	__webpack_chunkname__: "string" // eslint-disable-line camelcase
-};
-
-class ExtendedAPIPlugin {
-	apply(compiler) {
-		compiler.hooks.compilation.tap(
-			"ExtendedAPIPlugin",
-			(compilation, { normalModuleFactory }) => {
-				compilation.dependencyFactories.set(ConstDependency, new NullFactory());
-				compilation.dependencyTemplates.set(
-					ConstDependency,
-					new ConstDependency.Template()
-				);
-
-				const mainTemplate = compilation.mainTemplate;
-				mainTemplate.hooks.requireExtensions.tap(
-					"ExtendedAPIPlugin",
-					(source, chunk, hash) => {
-						const buf = [source];
-						buf.push("");
-						buf.push("// __webpack_hash__");
-						buf.push(`${mainTemplate.requireFn}.h = ${JSON.stringify(hash)};`);
-						buf.push("");
-						buf.push("// __webpack_chunkname__");
-						buf.push(
-							`${mainTemplate.requireFn}.cn = ${JSON.stringify(chunk.name)};`
-						);
-						return Template.asString(buf);
-					}
-				);
-				mainTemplate.hooks.globalHash.tap("ExtendedAPIPlugin", () => true);
-
-				const handler = (parser, parserOptions) => {
-					Object.keys(REPLACEMENTS).forEach(key => {
-						parser.hooks.expression
-							.for(key)
-							.tap(
-								"ExtendedAPIPlugin",
-								ParserHelpers.toConstantDependencyWithWebpackRequire(
-									parser,
-									REPLACEMENTS[key]
-								)
-							);
-						parser.hooks.evaluateTypeof
-							.for(key)
-							.tap(
-								"ExtendedAPIPlugin",
-								ParserHelpers.evaluateToString(REPLACEMENT_TYPES[key])
-							);
-					});
-				};
-
-				normalModuleFactory.hooks.parser
-					.for("javascript/auto")
-					.tap("ExtendedAPIPlugin", handler);
-				normalModuleFactory.hooks.parser
-					.for("javascript/dynamic")
-					.tap("ExtendedAPIPlugin", handler);
-				normalModuleFactory.hooks.parser
-					.for("javascript/esm")
-					.tap("ExtendedAPIPlugin", handler);
-			}
-		);
-	}
-}
-
-module.exports = ExtendedAPIPlugin;
diff --git a/lib/ExternalModule.js b/lib/ExternalModule.js
index 98d1560b883..621caa02b42 100644
--- a/lib/ExternalModule.js
+++ b/lib/ExternalModule.js
@@ -2,158 +2,1572 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
+const { SyncBailHook } = require("tapable");
 const { OriginalSource, RawSource } = require("webpack-sources");
+const ConcatenationScope = require("./ConcatenationScope");
+const { UsageState } = require("./ExportsInfo");
+const InitFragment = require("./InitFragment");
 const Module = require("./Module");
-const WebpackMissingModule = require("./dependencies/WebpackMissingModule");
+const {
+	ASSET_URL_TYPE,
+	ASSET_URL_TYPES,
+	CSS_IMPORT_TYPES,
+	JAVASCRIPT_TYPE,
+	JAVASCRIPT_TYPES
+} = require("./ModuleSourceTypeConstants");
+const { JAVASCRIPT_MODULE_TYPE_DYNAMIC } = require("./ModuleTypeConstants");
+const RuntimeGlobals = require("./RuntimeGlobals");
 const Template = require("./Template");
+const WebpackError = require("./WebpackError");
+const { DEFAULTS } = require("./config/defaults");
+const { ImportPhaseUtils } = require("./dependencies/ImportPhase");
+const StaticExportsDependency = require("./dependencies/StaticExportsDependency");
+const { coreModules } = require("./node/nodeBuiltins");
+const createHash = require("./util/createHash");
+const createHooksRegistry = require("./util/createHooksRegistry");
+const extractUrlAndGlobal = require("./util/extractUrlAndGlobal");
+const makeSerializable = require("./util/makeSerializable");
+const memoize = require("./util/memoize");
+const { propertyAccess } = require("./util/property");
+const { register } = require("./util/serialization");
+
+const getEnvironmentNotSupportAsyncWarning = memoize(() =>
+	require("./errors/EnvironmentNotSupportAsyncWarning")
+);
+
+/** @typedef {import("webpack-sources").Source} Source */
+/** @typedef {import("../declarations/WebpackOptions").ExternalsType} ExternalsType */
+/** @typedef {import("../declarations/WebpackOptions").HashFunction} HashFunction */
+/** @typedef {import("./config/defaults").WebpackOptionsNormalizedWithDefaults} WebpackOptions */
+/** @typedef {import("./Chunk")} Chunk */
+/** @typedef {import("./ChunkGraph")} ChunkGraph */
+/** @typedef {import("./Compilation")} Compilation */
+/** @typedef {import("./Compilation").UnsafeCacheData} UnsafeCacheData */
+/** @typedef {import("./Dependency").UpdateHashContext} UpdateHashContext */
+/** @typedef {import("./ExportsInfo")} ExportsInfo */
+/** @typedef {import("./Generator").GenerateContext} GenerateContext */
+/** @typedef {import("./Generator").SourceTypes} SourceTypes */
+/** @typedef {import("./Module").BuildMeta} BuildMeta */
+/** @typedef {import("./Module").ModuleId} ModuleId */
+/** @typedef {import("./Module").BuildCallback} BuildCallback */
+/** @typedef {import("./Module").BuildInfo} BuildInfo */
+/** @typedef {import("./Module").CodeGenerationContext} CodeGenerationContext */
+/** @typedef {import("./Module").CodeGenerationResult} CodeGenerationResult */
+/** @typedef {import("./Module").CodeGenerationResultData} CodeGenerationResultData */
+/** @typedef {import("./Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */
+/** @typedef {import("./Module").LibIdentOptions} LibIdentOptions */
+/** @typedef {import("./Module").LibIdent} LibIdent */
+/** @typedef {import("./Module").NeedBuildCallback} NeedBuildCallback */
+/** @typedef {import("./Module").NeedBuildContext} NeedBuildContext */
+/** @typedef {import("./Module").RuntimeRequirements} RuntimeRequirements */
+/** @typedef {import("./Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */
+/** @typedef {import("./Module").Sources} Sources */
+/** @typedef {import("./ModuleGraph")} ModuleGraph */
+/** @typedef {import("./ModuleGraphConnection").ConnectionState} ConnectionState */
+/** @typedef {import("./NormalModuleFactory")} NormalModuleFactory */
+/** @typedef {import("./RequestShortener")} RequestShortener */
+/** @typedef {import("./ResolverFactory").ResolverWithOptions} ResolverWithOptions */
+/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */
+/** @typedef {import("./javascript/JavascriptModulesPlugin").ChunkRenderContext} ChunkRenderContext */
+/** @typedef {import("./javascript/JavascriptParser").ImportAttributes} ImportAttributes */
+/** @typedef {import("./dependencies/ImportPhase").ImportPhaseType} ImportPhaseType */
+/** @typedef {import("../declarations/WebpackOptions").ExternalItemInterop} ExternalInterop */
+/** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext<[ExternalModuleRequest, ExternalsType, string, DependencyMeta | undefined, ExternalInterop | undefined, boolean | undefined]>} ObjectDeserializerContext */
+/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext<[ExternalModuleRequest, ExternalsType, string, DependencyMeta | undefined, ExternalInterop | undefined, boolean | undefined]>} ObjectSerializerContext */
+/** @typedef {import("./util/Hash")} Hash */
+/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
+/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */
+
+/** @typedef {{ attributes?: ImportAttributes, phase?: ImportPhaseType, externalType: "import" | "module" | undefined }} ImportDependencyMeta */
+/** @typedef {{ layer?: string, supports?: string, media?: string }} CssImportDependencyMeta */
+/** @typedef {{ sourceType: "asset-url" | "css-url" }} AssetDependencyMeta */
+
+/** @typedef {ImportDependencyMeta | CssImportDependencyMeta | AssetDependencyMeta} DependencyMeta */
+
+/**
+ * Defines the source data type used by this module.
+ * @typedef {object} SourceData
+ * @property {boolean=} iife
+ * @property {string=} init
+ * @property {string} expression
+ * @property {InitFragment[]=} chunkInitFragments
+ * @property {ReadOnlyRuntimeRequirements=} runtimeRequirements
+ * @property {[string, string][]=} specifiers
+ */
+
+/** @typedef {true | [string, string][]} Imported */
+
+/** @type {RuntimeRequirements} */
+const RUNTIME_REQUIREMENTS = new Set([RuntimeGlobals.module]);
+/** @type {RuntimeRequirements} */
+const RUNTIME_REQUIREMENTS_FOR_SCRIPT = new Set([RuntimeGlobals.loadScript]);
+/** @type {RuntimeRequirements} */
+const RUNTIME_REQUIREMENTS_FOR_MODULE = new Set([
+	RuntimeGlobals.definePropertyGetters
+]);
+/** @type {RuntimeRequirements} */
+const EMPTY_RUNTIME_REQUIREMENTS = new Set();
+
+const NODE_PREFIX = "node:";
+
+// externals of these types are wired by the library wrapper, which does its own per-key lookup on object externals
+const LIBRARY_WIRED_EXTERNAL_TYPES = new Set([
+	"amd",
+	"amd-require",
+	"umd",
+	"umd2",
+	"system",
+	"jsonp"
+]);
+// External types that emit a node.js-resolvable specifier (`require`/`import`).
+const NODE_RESOLVING_EXTERNAL_TYPES = new Set([
+	"commonjs",
+	"commonjs2",
+	"commonjs-module",
+	"commonjs-static",
+	"node-commonjs",
+	"import",
+	"module"
+]);
+
+/**
+ * Normalizes a node.js core module specifier for the target. Strips `node:`
+ * when the target can't resolve a prefixed request, adds `node:` for a universal
+ * bundle (which may run on deno/bun where the prefix is required), and otherwise
+ * keeps the author's original notation.
+ * @param {string | string[]} request the external request
+ * @param {ExternalsType} externalType the resolved external type
+ * @param {RuntimeTemplate} runtimeTemplate the runtime template
+ * @returns {string | string[]} the normalized request
+ */
+const normalizeNodeCoreModuleRequest = (
+	request,
+	externalType,
+	runtimeTemplate
+) => {
+	if (!NODE_RESOLVING_EXTERNAL_TYPES.has(externalType)) return request;
+	const name = Array.isArray(request) ? request[0] : request;
+	if (typeof name !== "string") return request;
+	/**
+	 * @param {string} next replacement specifier
+	 * @returns {string | string[]} request with the specifier swapped
+	 */
+	const withName = (next) =>
+		Array.isArray(request) ? [next, ...request.slice(1)] : next;
+
+	if (
+		runtimeTemplate.outputOptions.environment.nodePrefixForCoreModules === false
+	) {
+		if (!name.startsWith(NODE_PREFIX)) return request;
+		const stripped = name.slice(NODE_PREFIX.length);
+		return coreModules.has(stripped) ? withName(stripped) : request;
+	}
+	if (runtimeTemplate.isUniversalTarget() && !name.startsWith(NODE_PREFIX)) {
+		return coreModules.has(name) ? withName(NODE_PREFIX + name) : request;
+	}
+	return request;
+};
+
+/**
+ * Gets source for global variable external.
+ * @param {string | string[]} variableName the variable name or path
+ * @param {string} type the module system
+ * @returns {SourceData} the generated source
+ */
+const getSourceForGlobalVariableExternal = (variableName, type) => {
+	if (!Array.isArray(variableName)) {
+		// make it an array as the look up works the same basically
+		variableName = [variableName];
+	}
+
+	// needed for e.g. window["some"]["thing"]
+	const objectLookup = variableName
+		.map((r) => `[${JSON.stringify(r)}]`)
+		.join("");
+	return {
+		iife: type === "this",
+		expression: `${type}${objectLookup}`
+	};
+};
+
+/** @typedef {string | string[]} ModuleAndSpecifiers */
+
+/**
+ * Gets source for common js external.
+ * @param {ModuleAndSpecifiers} moduleAndSpecifiers the module request
+ * @returns {SourceData} the generated source
+ */
+const getSourceForCommonJsExternal = (moduleAndSpecifiers) => {
+	if (!Array.isArray(moduleAndSpecifiers)) {
+		return {
+			expression: `require(${JSON.stringify(moduleAndSpecifiers)})`
+		};
+	}
+	const moduleName = moduleAndSpecifiers[0];
+	return {
+		expression: `require(${JSON.stringify(moduleName)})${propertyAccess(
+			moduleAndSpecifiers,
+			1
+		)}`
+	};
+};
+
+/**
+ * Gets external module node commonjs init fragment.
+ * @param {RuntimeTemplate} runtimeTemplate the runtime template
+ * @param {boolean=} universal target may also run outside node (e.g. `["node", "web"]`)
+ * @returns {InitFragment} code
+ */
+const getExternalModuleNodeCommonjsInitFragment = (
+	runtimeTemplate,
+	universal = false
+) => {
+	const importMetaName = runtimeTemplate.outputOptions.importMetaName;
+	const moduleId = runtimeTemplate.renderNodePrefixForCoreModule("module");
+
+	if (!universal) {
+		return new InitFragment(
+			`import { createRequire as __WEBPACK_EXTERNAL_createRequire } from ${moduleId};\n${runtimeTemplate.renderConst()} __WEBPACK_EXTERNAL_createRequire_require = __WEBPACK_EXTERNAL_createRequire(${importMetaName}.url);\n`,
+			InitFragment.STAGE_HARMONY_IMPORTS,
+			0,
+			"external module node-commonjs"
+		);
+	}
+
+	// defensively obtain `createRequire` so the universal bundle still loads off node
+	const content = `${runtimeTemplate.renderConst()} __WEBPACK_EXTERNAL_createRequire_require = ${runtimeTemplate.getBuiltinModule(
+		moduleId,
+		`.createRequire(${importMetaName}.url)`
+	)};\n`;
+
+	return new InitFragment(
+		content,
+		InitFragment.STAGE_HARMONY_IMPORTS,
+		0,
+		"external module node-commonjs universal"
+	);
+};
+
+/**
+ * Gets source for common js external in node module.
+ * @param {ModuleAndSpecifiers} moduleAndSpecifiers the module request
+ * @param {RuntimeTemplate} runtimeTemplate the runtime template
+ * @param {boolean=} universal target may also run outside node (e.g. `["node", "web"]`)
+ * @returns {SourceData} the generated source
+ */
+const getSourceForCommonJsExternalInNodeModule = (
+	moduleAndSpecifiers,
+	runtimeTemplate,
+	universal = false
+) => {
+	const chunkInitFragments = [
+		getExternalModuleNodeCommonjsInitFragment(runtimeTemplate, universal)
+	];
+	if (!Array.isArray(moduleAndSpecifiers)) {
+		return {
+			chunkInitFragments,
+			expression: `__WEBPACK_EXTERNAL_createRequire_require(${JSON.stringify(
+				moduleAndSpecifiers
+			)})`
+		};
+	}
+	const moduleName = moduleAndSpecifiers[0];
+	return {
+		chunkInitFragments,
+		expression: `__WEBPACK_EXTERNAL_createRequire_require(${JSON.stringify(
+			moduleName
+		)})${propertyAccess(moduleAndSpecifiers, 1)}`
+	};
+};
+
+/**
+ * Gets source for import external.
+ * @param {ModuleAndSpecifiers} moduleAndSpecifiers the module request
+ * @param {RuntimeTemplate} runtimeTemplate the runtime template
+ * @param {ImportDependencyMeta=} dependencyMeta the dependency meta
+ * @returns {SourceData} the generated source
+ */
+const getSourceForImportExternal = (
+	moduleAndSpecifiers,
+	runtimeTemplate,
+	dependencyMeta
+) => {
+	const baseImportName = runtimeTemplate.outputOptions.importFunctionName;
+	if (
+		!runtimeTemplate.supportsDynamicImport() &&
+		(baseImportName === "import" || baseImportName === "module-import")
+	) {
+		throw new Error(
+			"The target environment doesn't support 'import()' so it's not possible to use external type 'import'"
+		);
+	}
+	const phase = dependencyMeta && dependencyMeta.phase;
+	// `import.defer(…)` and `import.source(…)` are only valid forms of the
+	// native `import(…)` function, so we only emit the phase suffix when the
+	// importFunctionName is the default `"import"`.
+	const importName =
+		baseImportName === "import" && ImportPhaseUtils.isDefer(phase)
+			? "import.defer"
+			: baseImportName === "import" && ImportPhaseUtils.isSource(phase)
+				? "import.source"
+				: baseImportName;
+	const attributes =
+		dependencyMeta && dependencyMeta.attributes
+			? dependencyMeta.attributes._isLegacyAssert
+				? `, { assert: ${JSON.stringify(
+						dependencyMeta.attributes,
+						importAssertionReplacer
+					)} }`
+				: `, { with: ${JSON.stringify(dependencyMeta.attributes)} }`
+			: "";
+	if (!Array.isArray(moduleAndSpecifiers)) {
+		return {
+			expression: `${importName}(${JSON.stringify(
+				moduleAndSpecifiers
+			)}${attributes});`
+		};
+	}
+	if (moduleAndSpecifiers.length === 1) {
+		return {
+			expression: `${importName}(${JSON.stringify(
+				moduleAndSpecifiers[0]
+			)}${attributes});`
+		};
+	}
+	const moduleName = moduleAndSpecifiers[0];
+	return {
+		expression: `${importName}(${JSON.stringify(
+			moduleName
+		)}${attributes}).then(${runtimeTemplate.returningFunction(
+			`module${propertyAccess(moduleAndSpecifiers, 1)}`,
+			"module"
+		)});`
+	};
+};
+
+/**
+ * Import assertion replacer.
+ * @param {string} key key
+ * @param {ImportAttributes | string | boolean | undefined} value value
+ * @returns {ImportAttributes | string | boolean | undefined} replaced value
+ */
+const importAssertionReplacer = (key, value) => {
+	if (key === "_isLegacyAssert") {
+		return;
+	}
+
+	return value;
+};
+
+/**
+ * Represents ModuleExternalInitFragment.
+ * @extends {InitFragment}
+ */
+class ModuleExternalInitFragment extends InitFragment {
+	/**
+	 * Creates an instance of ModuleExternalInitFragment.
+	 * @param {string} request import source
+	 * @param {Imported} imported the imported specifiers
+	 * @param {string=} ident recomputed ident
+	 * @param {ImportDependencyMeta=} dependencyMeta the dependency meta
+	 * @param {HashFunction=} hashFunction the hash function to use
+	 */
+	constructor(
+		request,
+		imported,
+		ident,
+		dependencyMeta,
+		hashFunction = DEFAULTS.HASH_FUNCTION
+	) {
+		if (ident === undefined) {
+			ident = Template.toIdentifier(request);
+			if (ident !== request) {
+				ident += `_${createHash(hashFunction)
+					.update(request)
+					.digest("hex")
+					.slice(0, 8)}`;
+			}
+		}
+
+		super(
+			"",
+			InitFragment.STAGE_HARMONY_IMPORTS,
+			0,
+			`external module import ${ident} ${
+				imported === true ? imported : imported.join(" ")
+			}`
+		);
+		/** @type {string} */
+		this._ident = ident;
+		/** @type {string} */
+		this._request = request;
+		/** @type {ImportDependencyMeta | undefined} */
+		this._dependencyMeta = dependencyMeta;
+		/** @type {string} */
+		this._identifier = this.buildIdentifier(ident);
+		/** @type {Imported} */
+		this._imported = this.buildImported(imported);
+	}
+
+	/**
+	 * Returns imported.
+	 * @returns {Imported} imported
+	 */
+	getImported() {
+		return this._imported;
+	}
+
+	/**
+	 * Updates imported using the provided imported.
+	 * @param {Imported} imported imported
+	 */
+	setImported(imported) {
+		this._imported = imported;
+	}
+
+	/**
+	 * Returns the source code that will be included as initialization code.
+	 * @param {GenerateContext} context context
+	 * @returns {string | Source | undefined} the source code that will be included as initialization code
+	 */
+	getContent(context) {
+		const {
+			_dependencyMeta: dependencyMeta,
+			_imported: imported,
+			_request: request,
+			_identifier: identifier
+		} = this;
+		const attributes =
+			dependencyMeta && dependencyMeta.attributes
+				? dependencyMeta.attributes._isLegacyAssert
+					? ` assert ${JSON.stringify(
+							dependencyMeta.attributes,
+							importAssertionReplacer
+						)}`
+					: ` with ${JSON.stringify(dependencyMeta.attributes)}`
+				: "";
+		const phase = dependencyMeta && dependencyMeta.phase;
+		let content = "";
+		if (imported === true) {
+			// namespace
+			const phaseKeyword = ImportPhaseUtils.isDefer(phase) ? "defer " : "";
+			content = `import ${phaseKeyword}* as ${identifier} from ${JSON.stringify(
+				request
+			)}${attributes};\n`;
+		} else if (imported.length === 0) {
+			// just import, no use
+			content = `import ${JSON.stringify(request)}${attributes};\n`;
+		} else if (
+			ImportPhaseUtils.isSource(phase) &&
+			imported.length === 1 &&
+			imported[0][0] === "default"
+		) {
+			// `import source x from "…"` — the source-phase form binds the source
+			// object directly to a single identifier (no namespace, no destructuring).
+			content = `import source ${imported[0][1]} from ${JSON.stringify(
+				request
+			)}${attributes};\n`;
+		} else {
+			content = `import { ${imported
+				.map(([name, finalName]) => {
+					if (name !== finalName) {
+						return `${name} as ${finalName}`;
+					}
+					return name;
+				})
+				.join(", ")} } from ${JSON.stringify(request)}${attributes};\n`;
+		}
+		return content;
+	}
+
+	getNamespaceIdentifier() {
+		return this._identifier;
+	}
+
+	/**
+	 * Returns identifier.
+	 * @param {string} ident ident
+	 * @returns {string} identifier
+	 */
+	buildIdentifier(ident) {
+		return `__WEBPACK_EXTERNAL_MODULE_${ident}__`;
+	}
+
+	/**
+	 * Returns normalized imported.
+	 * @param {Imported} imported imported
+	 * @returns {Imported} normalized imported
+	 */
+	buildImported(imported) {
+		if (Array.isArray(imported)) {
+			return imported.map(([name]) => {
+				const ident = `${this._ident}_${name}`;
+				return [name, this.buildIdentifier(ident)];
+			});
+		}
+		return imported;
+	}
+}
+
+register(
+	ModuleExternalInitFragment,
+	"webpack/lib/ExternalModule",
+	"ModuleExternalInitFragment",
+	{
+		serialize(obj, { write }) {
+			write(obj._request);
+			write(obj._imported);
+			write(obj._ident);
+			write(obj._dependencyMeta);
+		},
+		deserialize({ read }) {
+			return new ModuleExternalInitFragment(read(), read(), read(), read());
+		}
+	}
+);
+
+/**
+ * Generates module remapping.
+ * @param {string} input input
+ * @param {ExportsInfo} exportsInfo the exports info
+ * @param {RuntimeSpec=} runtime the runtime
+ * @param {RuntimeTemplate=} runtimeTemplate the runtime template
+ * @returns {string | undefined} the module remapping
+ */
+const generateModuleRemapping = (
+	input,
+	exportsInfo,
+	runtime,
+	runtimeTemplate
+) => {
+	if (exportsInfo.otherExportsInfo.getUsed(runtime) === UsageState.Unused) {
+		/** @type {string[]} */
+		const properties = [];
+		for (const exportInfo of exportsInfo.orderedExports) {
+			const used = exportInfo.getUsedName(exportInfo.name, runtime);
+			if (!used) continue;
+			const nestedInfo = exportInfo.getNestedExportsInfo();
+			if (nestedInfo) {
+				const nestedExpr = generateModuleRemapping(
+					`${input}${propertyAccess([exportInfo.name])}`,
+					nestedInfo
+				);
+				if (nestedExpr) {
+					properties.push(`[${JSON.stringify(used)}]: y(${nestedExpr})`);
+					continue;
+				}
+			}
+			properties.push(
+				`[${JSON.stringify(used)}]: ${
+					/** @type {RuntimeTemplate} */ (runtimeTemplate).returningFunction(
+						`${input}${propertyAccess([exportInfo.name])}`
+					)
+				}`
+			);
+		}
+		return `x({ ${properties.join(", ")} })`;
+	}
+};
+
+/**
+ * Gets source for module external.
+ * @param {ModuleAndSpecifiers} moduleAndSpecifiers the module request
+ * @param {ExportsInfo} exportsInfo exports info of this module
+ * @param {RuntimeSpec} runtime the runtime
+ * @param {RuntimeTemplate} runtimeTemplate the runtime template
+ * @param {ImportDependencyMeta} dependencyMeta the dependency meta
+ * @param {ConcatenationScope=} concatenationScope concatenationScope
+ * @returns {SourceData} the generated source
+ */
+const getSourceForModuleExternal = (
+	moduleAndSpecifiers,
+	exportsInfo,
+	runtime,
+	runtimeTemplate,
+	dependencyMeta,
+	concatenationScope
+) => {
+	const phase = dependencyMeta && dependencyMeta.phase;
+	/** @type {Imported} */
+	let imported = true;
+	if (concatenationScope) {
+		const usedExports = exportsInfo.getUsedExports(runtime);
+		switch (usedExports) {
+			case true:
+			case null:
+				// unknown exports
+				imported = true;
+				break;
+			case false:
+				// no used exports
+				imported = [];
+				break;
+			default:
+				imported = [...usedExports.entries()];
+		}
+	}
+
+	if (!Array.isArray(moduleAndSpecifiers)) {
+		moduleAndSpecifiers = [moduleAndSpecifiers];
+	}
+
+	// Return to `namespace` when the external request includes a specific export
+	if (moduleAndSpecifiers.length > 1) {
+		imported = true;
+	}
+
+	// `import defer …` is only valid as `import defer * as ns from "…"`, so
+	// keep the namespace form even if usage analysis would otherwise narrow
+	// the import down to specific names. Defer + concatenation is semantically
+	// at odds (lazy vs. eager), so we preserve the user-written shape here.
+	if (ImportPhaseUtils.isDefer(phase)) {
+		imported = true;
+	}
+
+	const initFragment = new ModuleExternalInitFragment(
+		moduleAndSpecifiers[0],
+		imported,
+		undefined,
+		dependencyMeta,
+		runtimeTemplate.outputOptions.hashFunction
+	);
+	const normalizedImported = initFragment.getImported();
+
+	const baseAccess = `${initFragment.getNamespaceIdentifier()}${propertyAccess(
+		moduleAndSpecifiers,
+		1
+	)}`;
+	let expression = baseAccess;
+
+	const useNamespace = imported === true;
+	/** @type {undefined | string} */
+	let moduleRemapping;
+	if (useNamespace) {
+		moduleRemapping = generateModuleRemapping(
+			baseAccess,
+			exportsInfo,
+			runtime,
+			runtimeTemplate
+		);
+		expression = moduleRemapping || baseAccess;
+	}
+	return {
+		expression,
+		init: moduleRemapping
+			? `${runtimeTemplate.renderConst()} x = ${runtimeTemplate.basicFunction(
+					"y",
+					`${runtimeTemplate.renderConst()} x = {}; ${RuntimeGlobals.definePropertyGetters}(x, y); return x`
+				)} \n${runtimeTemplate.renderConst()} y = ${runtimeTemplate.returningFunction(
+					runtimeTemplate.returningFunction("x"),
+					"x"
+				)}`
+			: undefined,
+		specifiers: normalizedImported === true ? undefined : normalizedImported,
+		runtimeRequirements: moduleRemapping
+			? RUNTIME_REQUIREMENTS_FOR_MODULE
+			: undefined,
+		chunkInitFragments: [
+			/** @type {InitFragment} */ (initFragment)
+		]
+	};
+};
+
+/**
+ * Gets source for script external.
+ * @param {string | string[]} urlAndGlobal the script request
+ * @param {RuntimeTemplate} runtimeTemplate the runtime template
+ * @returns {SourceData} the generated source
+ */
+const getSourceForScriptExternal = (urlAndGlobal, runtimeTemplate) => {
+	if (typeof urlAndGlobal === "string") {
+		urlAndGlobal = extractUrlAndGlobal(urlAndGlobal);
+	}
+	const url = urlAndGlobal[0];
+	const globalName = urlAndGlobal[1];
+	return {
+		init: `${runtimeTemplate.renderConst()} __webpack_error__ = new Error();`,
+		expression: `new Promise(${runtimeTemplate.basicFunction(
+			"resolve, reject",
+			[
+				`if(typeof ${globalName} !== "undefined") return resolve();`,
+				`${RuntimeGlobals.loadScript}(${JSON.stringify(
+					url
+				)}, ${runtimeTemplate.basicFunction("event", [
+					`if(typeof ${globalName} !== "undefined") return resolve();`,
+					`${runtimeTemplate.renderConst()} errorType = event && (event.type === 'load' ? 'missing' : event.type);`,
+					`${runtimeTemplate.renderConst()} realSrc = event && event.target && event.target.src;`,
+					"__webpack_error__.message = 'Loading script failed.\\n(' + errorType + ': ' + realSrc + ')';",
+					"__webpack_error__.name = 'ScriptExternalLoadError';",
+					"__webpack_error__.type = errorType;",
+					"__webpack_error__.request = realSrc;",
+					"__webpack_error__.event = event;",
+					"reject(__webpack_error__);"
+				])}, ${JSON.stringify(globalName)});`
+			]
+		)}).then(${runtimeTemplate.returningFunction(
+			`${globalName}${propertyAccess(urlAndGlobal, 2)}`
+		)})`,
+		runtimeRequirements: RUNTIME_REQUIREMENTS_FOR_SCRIPT
+	};
+};
+
+/**
+ * Gets source for amd-async external. Loads the module at runtime via the
+ * asynchronous AMD `require([...])` API and exposes it as an async module,
+ * so no AMD library wrapper around the chunk is needed.
+ * @param {string | string[]} request the request path
+ * @param {RuntimeTemplate} runtimeTemplate the runtime template
+ * @returns {SourceData} the generated source
+ */
+const getSourceForAmdAsyncExternal = (request, runtimeTemplate) => {
+	if (!Array.isArray(request)) {
+		request = [request];
+	}
+	const { library } = runtimeTemplate.outputOptions;
+	const amdRequire =
+		library && library.amdContainer
+			? `${library.amdContainer}.require`
+			: "require";
+	return {
+		expression: `new Promise(${runtimeTemplate.basicFunction(
+			"resolve, reject",
+			[
+				`if (typeof ${amdRequire} !== "function") return reject(new Error(${JSON.stringify(
+					`AMD 'require' is not available to load external module ${request[0]}`
+				)}));`,
+				`${amdRequire}(${JSON.stringify([
+					request[0]
+				])}, ${runtimeTemplate.basicFunction(
+					"module",
+					`resolve(module${propertyAccess(request, 1)});`
+				)}, reject);`
+			]
+		)})`
+	};
+};
+
+/**
+ * Checks external variable.
+ * @param {string} variableName the variable name to check
+ * @param {string} request the request path
+ * @param {RuntimeTemplate} runtimeTemplate the runtime template
+ * @returns {string} the generated source
+ */
+const checkExternalVariable = (variableName, request, runtimeTemplate) =>
+	`if(typeof ${variableName} === 'undefined') { ${runtimeTemplate.throwMissingModuleErrorBlock(
+		{ request }
+	)} }\n`;
+
+/**
+ * Gets source for amd or umd external.
+ * @param {ModuleId | string} id the module id
+ * @param {boolean} optional true, if the module is optional
+ * @param {string | string[]} request the request path
+ * @param {RuntimeTemplate} runtimeTemplate the runtime template
+ * @returns {SourceData} the generated source
+ */
+const getSourceForAmdOrUmdExternal = (
+	id,
+	optional,
+	request,
+	runtimeTemplate
+) => {
+	const externalVariable = `__WEBPACK_EXTERNAL_MODULE_${Template.toIdentifier(
+		`${id}`
+	)}__`;
+	return {
+		init: optional
+			? checkExternalVariable(
+					externalVariable,
+					Array.isArray(request) ? request.join(".") : request,
+					runtimeTemplate
+				)
+			: undefined,
+		expression: externalVariable
+	};
+};
+
+/**
+ * Gets source for default case.
+ * @param {boolean} optional true, if the module is optional
+ * @param {string | string[]} request the request path
+ * @param {RuntimeTemplate} runtimeTemplate the runtime template
+ * @returns {SourceData} the generated source
+ */
+const getSourceForDefaultCase = (optional, request, runtimeTemplate) => {
+	if (!Array.isArray(request)) {
+		// make it an array as the look up works the same basically
+		request = [request];
+	}
+
+	const variableName = request[0];
+	const objectLookup = propertyAccess(request, 1);
+	return {
+		init: optional
+			? checkExternalVariable(variableName, request.join("."), runtimeTemplate)
+			: undefined,
+		expression: `${variableName}${objectLookup}`
+	};
+};
+
+/** @typedef {Record} RequestRecord */
+/** @typedef {string | string[] | RequestRecord} ExternalModuleRequest */
+
+const createCompilationHooks = () => ({
+	/**
+	 * @type {SyncBailHook<[Chunk, Compilation], boolean>}
+	 * @since 5.106.0
+	 */
+	chunkCondition: new SyncBailHook(["chunk", "compilation"])
+});
+
+/**
+ * @typedef {ReturnType} ExternalModuleHooks
+ */
+
+/**
+ * Defines the build info properties specific to external modules.
+ * @typedef {object} KnownExternalModuleBuildInfo
+ * @property {boolean=} javascriptModule true when emitting an ESM external (`output.module`)
+ */
+
+/** @typedef {BuildInfo & KnownExternalModuleBuildInfo} ExternalModuleBuildInfo */
 
 class ExternalModule extends Module {
-	constructor(request, type, userRequest) {
-		super("javascript/dynamic", null);
+	/**
+	 * Creates an instance of ExternalModule.
+	 * @param {ExternalModuleRequest} request request
+	 * @param {ExternalsType} type type
+	 * @param {string} userRequest user request
+	 * @param {DependencyMeta=} dependencyMeta dependency meta
+	 * @param {ExternalInterop=} interop how the external's exports interoperate with ES module imports
+	 * @param {boolean=} sideEffects whether importing the external has side effects (undefined = assume it has)
+	 */
+	constructor(
+		request,
+		type,
+		userRequest,
+		dependencyMeta,
+		interop,
+		sideEffects
+	) {
+		super(JAVASCRIPT_MODULE_TYPE_DYNAMIC, null);
+
+		// Redeclared with the external module specific shape
+		/** @type {ExternalModuleBuildInfo | undefined} */
+		this.buildInfo = undefined;
 
 		// Info from Factory
+		/** @type {ExternalModuleRequest} */
 		this.request = request;
+		/** @type {ExternalsType} */
 		this.externalType = type;
+		/** @type {string} */
 		this.userRequest = userRequest;
-		this.external = true;
+		/** @type {DependencyMeta=} */
+		this.dependencyMeta = dependencyMeta;
+		/** @type {ExternalInterop | undefined} interop shape of the external's exports (undefined = importer-driven default) */
+		this.interop = interop;
+		/** @type {boolean | undefined} declared side-effects state (undefined = assume side effects, webpack can't analyze an external) */
+		this.sideEffects = sideEffects;
+		if (sideEffects !== undefined) {
+			this.factoryMeta = { sideEffectFree: !sideEffects };
+		}
+	}
+
+	/**
+	 * Returns the source types this module can generate.
+	 * @returns {SourceTypes} types available (do not mutate)
+	 */
+	getSourceTypes() {
+		// resolved, so a type rendering no javascript is never reported as a
+		// javascript module — that would emit a dead `false` factory in the chunk
+		const type = this._resolveExternalType(this.externalType);
+		// TODO webpack 6 drop "css-url" once the alias is removed
+		if (type === ASSET_URL_TYPE || type === "css-url") {
+			return ASSET_URL_TYPES;
+		}
+		if (type === "css-import") return CSS_IMPORT_TYPES;
+
+		return JAVASCRIPT_TYPES;
 	}
 
-	libIdent() {
+	/**
+	 * Gets the library identifier.
+	 * @param {LibIdentOptions} options options
+	 * @returns {LibIdent | null} an identifier for library inclusion
+	 */
+	libIdent(options) {
 		return this.userRequest;
 	}
 
-	chunkCondition(chunk) {
-		return chunk.hasEntryModule();
+	/**
+	 * Returns true if the module can be placed in the chunk.
+	 * @param {Chunk} chunk the chunk which condition should be checked
+	 * @param {Compilation} compilation the compilation
+	 * @returns {boolean} true if the module can be placed in the chunk
+	 */
+	chunkCondition(chunk, compilation) {
+		const { chunkCondition } = ExternalModule.getCompilationHooks(compilation);
+		const condition = chunkCondition.call(chunk, compilation);
+		if (condition !== undefined) return condition;
+
+		const type = this._resolveExternalType(this.externalType);
+
+		// For `import()` externals, keep them in the initial chunk to avoid loading
+		// them asynchronously twice and to improve runtime performance.
+		if (["css-import", "module"].includes(type)) {
+			return true;
+		}
+		return compilation.chunkGraph.getNumberOfEntryModules(chunk) > 0;
 	}
 
+	/**
+	 * Gets side effects connection state.
+	 * @param {ModuleGraph} moduleGraph the module graph
+	 * @returns {ConnectionState} how this module should be connected to referencing modules when consumed for side-effects only
+	 */
+	getSideEffectsConnectionState(moduleGraph) {
+		if (this.factoryMeta !== undefined) {
+			if (this.factoryMeta.sideEffectFree) return false;
+			if (this.factoryMeta.sideEffectFree === false) return true;
+		}
+		return true;
+	}
+
+	/**
+	 * Returns the unique identifier used to reference this module.
+	 * @returns {string} a unique identifier of the module
+	 */
 	identifier() {
-		return "external " + JSON.stringify(this.request);
+		let id = `external ${this._resolveExternalType(
+			this.externalType
+		)} ${JSON.stringify(this.request)}`;
+		const meta = /** @type {ImportDependencyMeta | undefined} */ (
+			this.dependencyMeta
+		);
+		if (meta) {
+			if (meta.phase) {
+				id += `|phase=${ImportPhaseUtils.stringify(meta.phase)}`;
+			}
+			if (meta.attributes) {
+				id += `|attributes=${JSON.stringify(meta.attributes)}`;
+			}
+		}
+		if (this.interop) id += `|interop=${this.interop}`;
+		// two externals for the same target with a different side-effects state
+		// must not be merged into one module
+		if (this.sideEffects !== undefined) {
+			id += `|sideEffects=${this.sideEffects}`;
+		}
+		return id;
 	}
 
-	readableIdentifier() {
-		return "external " + JSON.stringify(this.request);
+	/**
+	 * Returns a human-readable identifier for this module.
+	 * @param {RequestShortener} requestShortener the request shortener
+	 * @returns {string} a user readable identifier of the module
+	 */
+	readableIdentifier(requestShortener) {
+		return `external ${JSON.stringify(this.request)}`;
 	}
 
-	needRebuild() {
-		return false;
+	/**
+	 * Checks whether the module needs to be rebuilt for the current build state.
+	 * @param {NeedBuildContext} context context info
+	 * @param {NeedBuildCallback} callback callback function, returns true, if the module needs a rebuild
+	 * @returns {void}
+	 */
+	needBuild(context, callback) {
+		return callback(null, !this.buildMeta);
 	}
 
+	/**
+	 * Builds the module using the provided compilation context.
+	 * @param {WebpackOptions} options webpack options
+	 * @param {Compilation} compilation the compilation
+	 * @param {ResolverWithOptions} resolver the resolver
+	 * @param {InputFileSystem} fs the file system
+	 * @param {BuildCallback} callback callback function
+	 * @returns {void}
+	 */
 	build(options, compilation, resolver, fs, callback) {
-		this.built = true;
-		this.buildMeta = {};
-		this.buildInfo = {};
+		this.buildMeta = {
+			async: false,
+			exportsType: undefined
+		};
+		this.buildInfo = {
+			strict: true,
+			topLevelDeclarations: new Set(),
+			javascriptModule: compilation.outputOptions.module
+		};
+		const { request, externalType } = this._getRequestAndExternalType();
+		if (
+			request === undefined &&
+			typeof this.request === "object" &&
+			!Array.isArray(this.request) &&
+			!LIBRARY_WIRED_EXTERNAL_TYPES.has(this.externalType)
+		) {
+			this.addError(
+				new WebpackError(
+					`Missing external configuration for type "${
+						this.externalType
+					}" in external ${JSON.stringify(
+						this.request
+					)}. Add a ${JSON.stringify(
+						this.externalType
+					)} entry to it or set 'output.externalsType'/'output.library.type' to one of: ${Object.keys(
+						this.request
+					)
+						.map((key) => JSON.stringify(key))
+						.join(", ")}`
+				)
+			);
+		}
+		this.buildMeta.exportsType = "dynamic";
+		let canMangle = false;
+		this.clearDependenciesAndBlocks();
+		switch (externalType) {
+			case "this":
+				this.buildInfo.strict = false;
+				break;
+			case "system":
+				if (!Array.isArray(request) || request.length === 1) {
+					this.buildMeta.exportsType = "namespace";
+					canMangle = true;
+				}
+				break;
+			case "module":
+				if (this.buildInfo.javascriptModule) {
+					if (!Array.isArray(request) || request.length === 1) {
+						this.buildMeta.exportsType = "namespace";
+						canMangle = true;
+					}
+				} else {
+					this.buildMeta.async = true;
+					getEnvironmentNotSupportAsyncWarning().check(
+						this,
+						compilation.runtimeTemplate,
+						"external module"
+					);
+					if (!Array.isArray(request) || request.length === 1) {
+						this.buildMeta.exportsType = "namespace";
+						canMangle = false;
+					}
+				}
+				break;
+			case "script":
+				this.buildMeta.async = true;
+				getEnvironmentNotSupportAsyncWarning().check(
+					this,
+					compilation.runtimeTemplate,
+					"external script"
+				);
+				break;
+			case "promise":
+				this.buildMeta.async = true;
+				getEnvironmentNotSupportAsyncWarning().check(
+					this,
+					compilation.runtimeTemplate,
+					"external promise"
+				);
+				break;
+			case "amd-async":
+				this.buildMeta.async = true;
+				getEnvironmentNotSupportAsyncWarning().check(
+					this,
+					compilation.runtimeTemplate,
+					"external amd-async"
+				);
+				break;
+			case "import":
+				this.buildMeta.async = true;
+				getEnvironmentNotSupportAsyncWarning().check(
+					this,
+					compilation.runtimeTemplate,
+					"external import"
+				);
+				if (!Array.isArray(request) || request.length === 1) {
+					this.buildMeta.exportsType = "namespace";
+					canMangle = false;
+				}
+				break;
+		}
+		// Opt-in override for how the external's exports interoperate with ESM
+		// imports, independent of the importer's strictness (see Rollup's
+		// `output.interop`). Only meaningful for a single-request external.
+		if (this.interop && (!Array.isArray(request) || request.length === 1)) {
+			if (this.interop === "esModule") {
+				// external already is an ES module namespace: `default` unboxes to `.default`
+				this.buildMeta.exportsType = "namespace";
+			} else {
+				// "default": treat as CommonJS, `default` is the whole exports (Node.js semantics)
+				this.buildMeta.exportsType = "default";
+				this.buildMeta.defaultObject = "redirect";
+			}
+			canMangle = false;
+		}
+		this.addDependency(new StaticExportsDependency(true, canMangle));
 		callback();
 	}
 
-	getSourceForGlobalVariableExternal(variableName, type) {
-		if (!Array.isArray(variableName)) {
-			// make it an array as the look up works the same basically
-			variableName = [variableName];
-		}
-
-		// needed for e.g. window["some"]["thing"]
-		const objectLookup = variableName
-			.map(r => `[${JSON.stringify(r)}]`)
-			.join("");
-		return `(function() { module.exports = ${type}${objectLookup}; }());`;
+	/**
+	 * restore unsafe cache data
+	 * @param {UnsafeCacheData} unsafeCacheData data from getUnsafeCacheData
+	 * @param {NormalModuleFactory} normalModuleFactory the normal module factory handling the unsafe caching
+	 */
+	restoreFromUnsafeCache(unsafeCacheData, normalModuleFactory) {
+		this._restoreFromUnsafeCache(unsafeCacheData, normalModuleFactory);
 	}
 
-	getSourceForCommonJsExternal(moduleAndSpecifiers) {
-		if (!Array.isArray(moduleAndSpecifiers)) {
-			return `module.exports = require(${JSON.stringify(
-				moduleAndSpecifiers
-			)});`;
+	/**
+	 * Returns the reason this module cannot be concatenated, when one exists.
+	 * @param {ConcatenationBailoutReasonContext} context context
+	 * @returns {string | undefined} reason why this module can't be concatenated, undefined when it can be concatenated
+	 */
+	getConcatenationBailoutReason(context) {
+		switch (this.externalType) {
+			case "amd":
+			case "amd-require":
+			case "umd":
+			case "umd2":
+			case "system":
+			case "jsonp":
+				return `${this.externalType} externals can't be concatenated`;
 		}
-
-		const moduleName = moduleAndSpecifiers[0];
-		const objectLookup = moduleAndSpecifiers
-			.slice(1)
-			.map(r => `[${JSON.stringify(r)}]`)
-			.join("");
-		return `module.exports = require(${moduleName})${objectLookup};`;
+		return undefined;
 	}
 
-	checkExternalVariable(variableToCheck, request) {
-		return `if(typeof ${variableToCheck} === 'undefined') {${WebpackMissingModule.moduleCode(
-			request
-		)}}\n`;
+	/**
+	 * @returns {boolean} true, when it may be wrapped
+	 */
+	canBeWrappedInConcatenation() {
+		if (/** @type {BuildMeta} */ (this.buildMeta).async) {
+			return false;
+		}
+		// every other synchronous type is a plain expression; only "module"
+		// renders as a hoisted `import` declaration, and passing it the
+		// concatenation scope is what narrows it to named specifiers
+		return this._resolveExternalType(this.externalType) !== "module";
 	}
 
-	getSourceForAmdOrUmdExternal(id, optional, request) {
-		const externalVariable = `__WEBPACK_EXTERNAL_MODULE_${Template.toIdentifier(
-			`${id}`
-		)}__`;
-		const missingModuleError = optional
-			? this.checkExternalVariable(externalVariable, request)
-			: "";
-		return `${missingModuleError}module.exports = ${externalVariable};`;
+	/**
+	 * Get request and external type.
+	 * @private
+	 * @returns {{ request: string | string[], externalType: ExternalsType }} the request and external type
+	 */
+	_getRequestAndExternalType() {
+		let { request, externalType } = this;
+		if (typeof request === "object" && !Array.isArray(request)) {
+			request = request[externalType];
+		}
+		externalType = this._resolveExternalType(externalType);
+		return { request, externalType };
 	}
 
-	getSourceForDefaultCase(optional, request) {
-		const missingModuleError = optional
-			? this.checkExternalVariable(request, request)
-			: "";
-		return `${missingModuleError}module.exports = ${request};`;
+	/**
+	 * Resolve the detailed external type from the raw external type.
+	 * e.g. resolve "module" or "import" from "module-import" type
+	 * @param {ExternalsType} externalType raw external type
+	 * @returns {ExternalsType} resolved external type
+	 */
+	_resolveExternalType(externalType) {
+		if (externalType === "module-import") {
+			if (
+				this.dependencyMeta &&
+				/** @type {ImportDependencyMeta} */
+				(this.dependencyMeta).externalType
+			) {
+				return /** @type {ImportDependencyMeta} */ (this.dependencyMeta)
+					.externalType;
+			}
+			return "module";
+		} else if (
+			// TODO webpack 6 drop "css-url" once the alias is removed
+			externalType === "asset" ||
+			externalType === ASSET_URL_TYPE ||
+			externalType === "css-url"
+		) {
+			// the consumer decides: a css `url()` reads the url out of the module
+			// (marked by the factory), a `new URL()` requires the `asset` wrapper
+			if (
+				this.dependencyMeta &&
+				/** @type {AssetDependencyMeta} */
+				(this.dependencyMeta).sourceType
+			) {
+				return /** @type {AssetDependencyMeta} */ (this.dependencyMeta)
+					.sourceType;
+			}
+
+			return "asset";
+		}
+
+		return externalType;
 	}
 
-	getSourceString(runtime) {
-		const request =
-			typeof this.request === "object"
-				? this.request[this.externalType]
-				: this.request;
-		switch (this.externalType) {
+	/**
+	 * Returns the source data.
+	 * @private
+	 * @param {string | string[]} request request
+	 * @param {ExternalsType} externalType the external type
+	 * @param {RuntimeTemplate} runtimeTemplate the runtime template
+	 * @param {ModuleGraph} moduleGraph the module graph
+	 * @param {ChunkGraph} chunkGraph the chunk graph
+	 * @param {RuntimeSpec} runtime the runtime
+	 * @param {DependencyMeta | undefined} dependencyMeta the dependency meta
+	 * @param {ConcatenationScope=} concatenationScope concatenationScope
+	 * @returns {SourceData} the source data
+	 */
+	_getSourceData(
+		request,
+		externalType,
+		runtimeTemplate,
+		moduleGraph,
+		chunkGraph,
+		runtime,
+		dependencyMeta,
+		concatenationScope
+	) {
+		request = normalizeNodeCoreModuleRequest(
+			request,
+			externalType,
+			runtimeTemplate
+		);
+		switch (externalType) {
 			case "this":
 			case "window":
 			case "self":
-				return this.getSourceForGlobalVariableExternal(
-					request,
-					this.externalType
-				);
+				return getSourceForGlobalVariableExternal(request, this.externalType);
 			case "global":
-				return this.getSourceForGlobalVariableExternal(
-					runtime.outputOptions.globalObject,
-					this.externalType
+				return getSourceForGlobalVariableExternal(
+					request,
+					runtimeTemplate.globalObject
 				);
 			case "commonjs":
 			case "commonjs2":
-				return this.getSourceForCommonJsExternal(request);
+			case "commonjs-module":
+			case "commonjs-static":
+			case "node-commonjs": {
+				const { node } = runtimeTemplate.compilation.options.externalsPresets;
+				// ESM has no `require`; load via `createRequire` when the target is node
+				const createRequireInModule =
+					/** @type {BuildInfo} */ (this.buildInfo).javascriptModule &&
+					(externalType === "node-commonjs" || Boolean(node));
+				if (!createRequireInModule) {
+					return getSourceForCommonJsExternal(request);
+				}
+				// for a universal target (e.g. `["node", "web"]`) load defensively so the browser doesn't crash
+				return getSourceForCommonJsExternalInNodeModule(
+					request,
+					runtimeTemplate,
+					runtimeTemplate.isUniversalTarget()
+				);
+			}
+			case "amd-async":
+				return getSourceForAmdAsyncExternal(request, runtimeTemplate);
 			case "amd":
+			case "amd-require":
 			case "umd":
 			case "umd2":
-				return this.getSourceForAmdOrUmdExternal(
-					this.id,
-					this.optional,
-					request
+			case "system":
+			case "jsonp": {
+				const id = chunkGraph.getModuleId(this);
+				return getSourceForAmdOrUmdExternal(
+					id !== null ? id : this.identifier(),
+					this.isOptional(moduleGraph),
+					request,
+					runtimeTemplate
+				);
+			}
+			case "import":
+				return getSourceForImportExternal(
+					request,
+					runtimeTemplate,
+					/** @type {ImportDependencyMeta} */ (dependencyMeta)
 				);
+			case "script":
+				return getSourceForScriptExternal(request, runtimeTemplate);
+			case "module": {
+				if (!(/** @type {BuildInfo} */ (this.buildInfo).javascriptModule)) {
+					if (!runtimeTemplate.supportsDynamicImport()) {
+						throw new Error(
+							`The target environment doesn't support dynamic import() syntax so it's not possible to use external type 'module' within a script${
+								runtimeTemplate.supportsEcmaScriptModuleSyntax()
+									? "\nDid you mean to build a EcmaScript Module ('output.module: true')?"
+									: ""
+							}`
+						);
+					}
+					return getSourceForImportExternal(
+						request,
+						runtimeTemplate,
+						/** @type {ImportDependencyMeta} */ (dependencyMeta)
+					);
+				}
+				if (!runtimeTemplate.supportsEcmaScriptModuleSyntax()) {
+					throw new Error(
+						"The target environment doesn't support EcmaScriptModule syntax so it's not possible to use external type 'module'"
+					);
+				}
+				return getSourceForModuleExternal(
+					request,
+					moduleGraph.getExportsInfo(this),
+					runtime,
+					runtimeTemplate,
+					/** @type {ImportDependencyMeta} */ (dependencyMeta),
+					concatenationScope
+				);
+			}
+			case "var":
+			case "promise":
+			case "assign":
 			default:
-				return this.getSourceForDefaultCase(this.optional, request);
+				return getSourceForDefaultCase(
+					this.isOptional(moduleGraph),
+					request,
+					runtimeTemplate
+				);
 		}
 	}
 
-	getSource(sourceString) {
-		if (this.useSourceMap) {
-			return new OriginalSource(sourceString, this.identifier());
+	/**
+	 * Generates code and runtime requirements for this module.
+	 * @param {CodeGenerationContext} context context for code generation
+	 * @returns {CodeGenerationResult} result
+	 */
+	codeGeneration({
+		runtimeTemplate,
+		moduleGraph,
+		chunkGraph,
+		runtime,
+		concatenationScope
+	}) {
+		const { request, externalType } = this._getRequestAndExternalType();
+		switch (externalType) {
+			case "asset": {
+				/** @type {Sources} */
+				const sources = new Map();
+				sources.set(
+					JAVASCRIPT_TYPE,
+					new RawSource(`module.exports = ${JSON.stringify(request)};`)
+				);
+				/** @type {CodeGenerationResultData} */
+				const data = new Map();
+				data.set("url", { javascript: /** @type {string} */ (request) });
+				return { sources, runtimeRequirements: RUNTIME_REQUIREMENTS, data };
+			}
+			// TODO webpack 6 remove "css-url" alias
+			case "css-url":
+			case "asset-url": {
+				/** @type {Sources} */
+				const sources = new Map();
+				/** @type {CodeGenerationResultData} */
+				const data = new Map();
+				data.set("url", { [ASSET_URL_TYPE]: /** @type {string} */ (request) });
+				return { sources, runtimeRequirements: RUNTIME_REQUIREMENTS, data };
+			}
+			case "css-import": {
+				/** @type {Sources} */
+				const sources = new Map();
+				const dependencyMeta = /** @type {CssImportDependencyMeta} */ (
+					this.dependencyMeta
+				);
+				const layer =
+					dependencyMeta.layer !== undefined
+						? ` layer(${dependencyMeta.layer})`
+						: "";
+				const supports = dependencyMeta.supports
+					? ` supports(${dependencyMeta.supports})`
+					: "";
+				const media = dependencyMeta.media ? ` ${dependencyMeta.media}` : "";
+				sources.set(
+					"css-import",
+					new RawSource(
+						`@import url(${JSON.stringify(
+							request
+						)})${layer}${supports}${media};`
+					)
+				);
+				return {
+					sources,
+					runtimeRequirements: EMPTY_RUNTIME_REQUIREMENTS
+				};
+			}
+			default: {
+				// A wrapped external runs as a module body inside the wrapper, so it
+				// renders the plain `module.exports =` form: its exports are read
+				// through the wrapper accessor, not through a shared-scope binding.
+				const wrapped = Boolean(
+					concatenationScope &&
+					/** @type {ConcatenationScope} */ (concatenationScope).isWrapped()
+				);
+				const sourceData = this._getSourceData(
+					request,
+					externalType,
+					runtimeTemplate,
+					moduleGraph,
+					chunkGraph,
+					runtime,
+					this.dependencyMeta,
+					wrapped ? undefined : concatenationScope
+				);
+
+				// sourceString can be empty str only when there is concatenationScope
+				let sourceString = sourceData.expression;
+				if (sourceData.iife) {
+					sourceString = `(function() { return ${sourceString}; }())`;
+				}
+
+				const specifiers = sourceData.specifiers;
+				if (specifiers) {
+					sourceString = "";
+					const scope = /** @type {ConcatenationScope} */ (concatenationScope);
+					for (const [specifier, finalName] of specifiers) {
+						// the hoisted `import { x as finalName }` binding is a top-level
+						// name of the chunk, reserve it before members get renamed
+						scope.usedNames.add(finalName);
+						scope.registerRawExport(specifier, finalName);
+					}
+				} else if (concatenationScope && !wrapped) {
+					sourceString = `${runtimeTemplate.renderConst()} ${
+						ConcatenationScope.NAMESPACE_OBJECT_EXPORT
+					} = ${sourceString};`;
+					concatenationScope.registerNamespaceExport(
+						ConcatenationScope.NAMESPACE_OBJECT_EXPORT
+					);
+				} else {
+					sourceString = `module.exports = ${sourceString};`;
+				}
+				if (sourceData.init) {
+					sourceString = `${sourceData.init}\n${sourceString}`;
+				}
+
+				/** @type {undefined | CodeGenerationResultData} */
+				let data;
+				if (sourceData.chunkInitFragments) {
+					data = new Map();
+					data.set("chunkInitFragments", sourceData.chunkInitFragments);
+				}
+
+				/** @type {Sources} */
+				const sources = new Map();
+				if (this.useSourceMap || this.useSimpleSourceMap) {
+					sources.set(
+						JAVASCRIPT_TYPE,
+						new OriginalSource(sourceString, this.identifier())
+					);
+				} else {
+					sources.set(JAVASCRIPT_TYPE, new RawSource(sourceString));
+				}
+
+				let runtimeRequirements = sourceData.runtimeRequirements;
+				if (!concatenationScope) {
+					if (!runtimeRequirements) {
+						runtimeRequirements = RUNTIME_REQUIREMENTS;
+					} else {
+						const set = new Set(runtimeRequirements);
+						set.add(RuntimeGlobals.module);
+						runtimeRequirements = set;
+					}
+				}
+
+				return {
+					sources,
+					runtimeRequirements:
+						runtimeRequirements || EMPTY_RUNTIME_REQUIREMENTS,
+					data
+				};
+			}
 		}
+	}
 
-		return new RawSource(sourceString);
+	/**
+	 * Returns the estimated size for the requested source type.
+	 * @param {string=} type the source type for which the size should be estimated
+	 * @returns {number} the estimated size of the module (must be non-zero)
+	 */
+	size(type) {
+		return 42;
 	}
 
-	source(dependencyTemplates, runtime) {
-		return this.getSource(this.getSourceString(runtime));
+	/**
+	 * Updates the hash with the data contributed by this instance.
+	 * @param {Hash} hash the hash used to track dependencies
+	 * @param {UpdateHashContext} context context
+	 * @returns {void}
+	 */
+	updateHash(hash, context) {
+		const { chunkGraph } = context;
+		hash.update(
+			`${this._resolveExternalType(this.externalType)}${JSON.stringify(
+				this.request
+			)}${this.isOptional(chunkGraph.moduleGraph)}`
+		);
+		const meta = /** @type {ImportDependencyMeta | undefined} */ (
+			this.dependencyMeta
+		);
+		if (meta) {
+			if (meta.phase) {
+				hash.update(`|phase=${ImportPhaseUtils.stringify(meta.phase)}`);
+			}
+			if (meta.attributes) {
+				hash.update(`|attributes=${JSON.stringify(meta.attributes)}`);
+			}
+		}
+		if (this.interop) hash.update(`|interop=${this.interop}`);
+		if (this.sideEffects !== undefined) {
+			hash.update(`|sideEffects=${this.sideEffects}`);
+		}
+		super.updateHash(hash, context);
 	}
 
-	size() {
-		return 42;
+	/**
+	 * Serializes this instance into the provided serializer context.
+	 * @param {ObjectSerializerContext} context context
+	 */
+	serialize(context) {
+		context
+			.write(this.request)
+			.write(this.externalType)
+			.write(this.userRequest)
+			.write(this.dependencyMeta)
+			.write(this.interop)
+			.write(this.sideEffects);
+
+		super.serialize(context);
 	}
 
-	updateHash(hash) {
-		hash.update(this.externalType);
-		hash.update(JSON.stringify(this.request));
-		hash.update(JSON.stringify(Boolean(this.optional)));
-		super.updateHash(hash);
+	/**
+	 * Restores this instance from the provided deserializer context.
+	 * @param {ObjectDeserializerContext} context context
+	 */
+	deserialize(context) {
+		this.request = context.read();
+		const c1 = context.rest;
+		this.externalType = c1.read();
+		const c2 = c1.rest;
+		this.userRequest = c2.read();
+		const c3 = c2.rest;
+		this.dependencyMeta = c3.read();
+		const c4 = c3.rest;
+		this.interop = c4.read();
+		const c5 = c4.rest;
+		this.sideEffects = c5.read();
+
+		super.deserialize(c5.rest);
 	}
 }
 
+ExternalModule.getCompilationHooks = createHooksRegistry(
+	createCompilationHooks
+);
+
+makeSerializable(ExternalModule, "webpack/lib/ExternalModule");
+
 module.exports = ExternalModule;
+module.exports.ModuleExternalInitFragment = ModuleExternalInitFragment;
+module.exports.getExternalModuleNodeCommonjsInitFragment =
+	getExternalModuleNodeCommonjsInitFragment;
diff --git a/lib/ExternalModuleFactoryPlugin.js b/lib/ExternalModuleFactoryPlugin.js
index 40c2156609e..bec4f5a7c28 100644
--- a/lib/ExternalModuleFactoryPlugin.js
+++ b/lib/ExternalModuleFactoryPlugin.js
@@ -2,52 +2,324 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
+const util = require("util");
 const ExternalModule = require("./ExternalModule");
+const { ASSET_URL_TYPE } = require("./ModuleSourceTypeConstants");
+const ContextElementDependency = require("./dependencies/ContextElementDependency");
+const CssImportDependency = require("./dependencies/CssImportDependency");
+const CssUrlDependency = require("./dependencies/CssUrlDependency");
+const HarmonyImportDependency = require("./dependencies/HarmonyImportDependency");
+const ImportDependency = require("./dependencies/ImportDependency");
+const { coreModules } = require("./node/nodeBuiltins");
+const { cachedSetProperty, resolveByProperty } = require("./util/cleverMerge");
+
+/** @typedef {import("enhanced-resolve").ResolveContext} ResolveContext */
+/** @typedef {import("../declarations/WebpackOptions").ResolveOptions} ResolveOptions */
+/** @typedef {import("../declarations/WebpackOptions").ExternalsType} ExternalsType */
+/** @typedef {import("../declarations/WebpackOptions").ExternalItem} ExternalItem */
+/** @typedef {import("../declarations/WebpackOptions").ExternalItemValue} ExternalItemValue */
+/** @typedef {import("../declarations/WebpackOptions").ExternalItemObjectKnown} ExternalItemObjectKnown */
+/** @typedef {import("../declarations/WebpackOptions").ExternalItemObjectUnknown} ExternalItemObjectUnknown */
+/** @typedef {import("../declarations/WebpackOptions").ExternalItemInterop} ExternalItemInterop */
+/** @typedef {import("../declarations/WebpackOptions").Externals} Externals */
+/** @typedef {import("./Dependency")} Dependency */
+/** @typedef {import("./ExternalModule").DependencyMeta} DependencyMeta */
+/** @typedef {import("./ExternalModule").ExternalModuleRequest} ExternalModuleRequest */
+/** @typedef {import("./ModuleFactory").IssuerLayer} IssuerLayer */
+/** @typedef {import("./ModuleFactory").ModuleFactoryCreateDataContextInfo} ModuleFactoryCreateDataContextInfo */
+/** @typedef {import("./NormalModuleFactory")} NormalModuleFactory */
+
+/** @typedef {((context: string, request: string, callback: (err?: Error | null, result?: string | false, resolveRequest?: import("enhanced-resolve").ResolveRequest) => void) => void)} ExternalItemFunctionDataGetResolveCallbackResult */
+/** @typedef {((context: string, request: string) => Promise)} ExternalItemFunctionDataGetResolveResult */
+/** @typedef {(options?: ResolveOptions) => ExternalItemFunctionDataGetResolveCallbackResult | ExternalItemFunctionDataGetResolveResult} ExternalItemFunctionDataGetResolve */
+
+/**
+ * Defines the external item function data type used by this module.
+ * @typedef {object} ExternalItemFunctionData
+ * @property {string} context the directory in which the request is placed
+ * @property {ModuleFactoryCreateDataContextInfo} contextInfo contextual information
+ * @property {string} dependencyType the category of the referencing dependency
+ * @property {ExternalItemFunctionDataGetResolve} getResolve get a resolve function with the current resolver options
+ * @property {string} request the request as written by the user in the require/import expression/statement
+ */
+
+/** @typedef {((data: ExternalItemFunctionData, callback: (err?: (Error | null), result?: ExternalItemValue) => void) => void)} ExternalItemFunctionCallback */
+/** @typedef {((data: import("../lib/ExternalModuleFactoryPlugin").ExternalItemFunctionData) => Promise)} ExternalItemFunctionPromise */
+
+const UNSPECIFIED_EXTERNAL_TYPE_REGEXP = /^[a-z0-9-]+ /;
+const EMPTY_RESOLVE_OPTIONS = {};
+const NODE_PREFIX = "node:";
+
+/**
+ * For a node.js core module request, returns its `node:`-prefixed/unprefixed
+ * counterpart so externals match regardless of which form the code uses.
+ * @param {string} request the request as written in the import/require
+ * @returns {string | undefined} the alternate form, or undefined when not a core module
+ */
+const getAlternateCoreModuleRequest = (request) => {
+	if (request.startsWith(NODE_PREFIX)) {
+		const name = request.slice(NODE_PREFIX.length);
+		return coreModules.has(name) ? name : undefined;
+	}
+	return coreModules.has(request) ? NODE_PREFIX + request : undefined;
+};
+
+// TODO webpack 6 remove this
+const callDeprecatedExternals = util.deprecate(
+	/**
+	 * Handles the callback logic for this hook.
+	 * @param {EXPECTED_FUNCTION} externalsFunction externals function
+	 * @param {string} context context
+	 * @param {string} request request
+	 * @param {(err: Error | null | undefined, value: ExternalValue | undefined, ty: ExternalsType | undefined) => void} cb cb
+	 */
+	(externalsFunction, context, request, cb) => {
+		// eslint-disable-next-line no-useless-call
+		externalsFunction.call(null, context, request, cb);
+	},
+	"The externals-function should be defined like ({context, request}, cb) => { ... }",
+	"DEP_WEBPACK_EXTERNALS_FUNCTION_PARAMETERS"
+);
+
+/** @typedef {(layer: string | null) => ExternalItem} ExternalItemByLayerFn */
+/** @typedef {ExternalItemObjectKnown & ExternalItemObjectUnknown} ExternalItemObject */
+
+/**
+ * Defines the external weak cache type used by this module.
+ * @template {ExternalItemObject} T
+ * @typedef {WeakMap>>} ExternalWeakCache
+ */
+
+/** @type {ExternalWeakCache} */
+const cache = new WeakMap();
+
+/**
+ * Returns result.
+ * @param {ExternalItemObject} obj obj
+ * @param {IssuerLayer} layer layer
+ * @returns {Omit} result
+ */
+const resolveLayer = (obj, layer) => {
+	let map = cache.get(obj);
+	if (map === undefined) {
+		map = new Map();
+		cache.set(obj, map);
+	} else {
+		const cacheEntry = map.get(layer);
+		if (cacheEntry !== undefined) return cacheEntry;
+	}
+	const result = resolveByProperty(obj, "byLayer", layer);
+	map.set(layer, result);
+	return result;
+};
+
+// the keys `ExternalItemValueWithOptions` allows, mirroring the schema
+const OPTIONS_FORM_KEYS = new Set(["external", "sideEffects"]);
+
+/** @typedef {string | string[] | boolean | Record} ExternalTargetValue */
+/** @typedef {{ external: ExternalTargetValue, sideEffects?: boolean }} ExternalValueWithOptions */
+/** @typedef {ExternalTargetValue | ExternalValueWithOptions} ExternalValue */
+
+const PLUGIN_NAME = "ExternalModuleFactoryPlugin";
 
 class ExternalModuleFactoryPlugin {
+	/**
+	 * Creates an instance of ExternalModuleFactoryPlugin.
+	 * @param {ExternalsType | ((dependency: Dependency) => ExternalsType)} type default external type
+	 * @param {Externals} externals externals config
+	 */
 	constructor(type, externals) {
 		this.type = type;
+		/** @type {Externals} */
 		this.externals = externals;
 	}
 
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {NormalModuleFactory} normalModuleFactory the normal module factory
+	 * @returns {void}
+	 */
 	apply(normalModuleFactory) {
 		const globalType = this.type;
-		normalModuleFactory.hooks.factory.tap(
-			"ExternalModuleFactoryPlugin",
-			factory => (data, callback) => {
+		normalModuleFactory.hooks.factorize.tapAsync(
+			PLUGIN_NAME,
+			(data, callback) => {
 				const context = data.context;
+				const contextInfo = data.contextInfo;
 				const dependency = data.dependencies[0];
+				const dependencyType = data.dependencyType;
+
+				/** @typedef {(err?: Error | null, externalModule?: ExternalModule) => void} HandleExternalCallback */
 
+				/**
+				 * Processes the provided value.
+				 * @param {ExternalValue} value the external config
+				 * @param {ExternalsType | undefined} type type of external
+				 * @param {HandleExternalCallback} callback callback
+				 * @returns {void}
+				 */
 				const handleExternal = (value, type, callback) => {
-					if (typeof type === "function") {
-						callback = type;
-						type = undefined;
+					/** @type {boolean | undefined} */
+					let sideEffects;
+					/** @type {ExternalTargetValue} */
+					let target;
+					// the options form carries the target under `external`; a target
+					// map holding an `external` type key keeps its own meaning
+					if (
+						typeof value === "object" &&
+						value !== null &&
+						!Array.isArray(value) &&
+						Object.prototype.hasOwnProperty.call(value, "external") &&
+						Object.keys(value).every((key) => OPTIONS_FORM_KEYS.has(key))
+					) {
+						const withOptions =
+							/** @type {ExternalValueWithOptions} */
+							(value);
+						sideEffects = withOptions.sideEffects;
+						target = withOptions.external;
+					} else {
+						target = /** @type {ExternalTargetValue} */ (value);
+					}
+					if (target === false) {
+						// Not externals, fallback to original factory
+						return callback();
+					}
+					/** @type {ExternalModuleRequest} */
+					let externalConfig = target === true ? dependency.request : target;
+					// `interop` is a reserved key on the object form, not a target;
+					// pull it out so the rest stays a pure externalsType->request map.
+					/** @type {ExternalItemInterop | undefined} */
+					let interop;
+					if (
+						typeof externalConfig === "object" &&
+						externalConfig !== null &&
+						!Array.isArray(externalConfig) &&
+						Object.prototype.hasOwnProperty.call(externalConfig, "interop")
+					) {
+						const { interop: interopValue, ...rest } =
+							/** @type {Record & { interop?: ExternalItemInterop }} */ (
+								externalConfig
+							);
+						interop = interopValue;
+						externalConfig = rest;
+					}
+					// When no explicit type is specified, extract it from the externalConfig
+					if (type === undefined) {
+						if (
+							typeof externalConfig === "string" &&
+							UNSPECIFIED_EXTERNAL_TYPE_REGEXP.test(externalConfig)
+						) {
+							const idx = externalConfig.indexOf(" ");
+							type =
+								/** @type {ExternalsType} */
+								(externalConfig.slice(0, idx));
+							externalConfig = externalConfig.slice(idx + 1);
+						} else if (
+							Array.isArray(externalConfig) &&
+							externalConfig.length > 0 &&
+							UNSPECIFIED_EXTERNAL_TYPE_REGEXP.test(externalConfig[0])
+						) {
+							const firstItem = externalConfig[0];
+							const idx = firstItem.indexOf(" ");
+							type = /** @type {ExternalsType} */ (firstItem.slice(0, idx));
+							externalConfig = [
+								firstItem.slice(idx + 1),
+								...externalConfig.slice(1)
+							];
+						}
+					}
+
+					const defaultType =
+						typeof globalType === "function"
+							? globalType(dependency)
+							: globalType;
+					const resolvedType = type || defaultType;
+
+					// TODO make it pluggable/add hooks to `ExternalModule` to allow output modules own externals?
+					/** @type {DependencyMeta | undefined} */
+					let dependencyMeta;
+
+					if (
+						dependency instanceof HarmonyImportDependency ||
+						dependency instanceof ImportDependency ||
+						dependency instanceof ContextElementDependency
+					) {
+						const externalType =
+							dependency instanceof HarmonyImportDependency
+								? "module"
+								: dependency instanceof ImportDependency
+									? "import"
+									: undefined;
+
+						dependencyMeta = {
+							attributes: dependency.attributes,
+							phase:
+								dependency instanceof HarmonyImportDependency ||
+								dependency instanceof ImportDependency
+									? dependency.phase
+									: undefined,
+							externalType
+						};
+					} else if (dependency instanceof CssImportDependency) {
+						dependencyMeta = {
+							layer: dependency.layer,
+							supports: dependency.supports,
+							media: dependency.media
+						};
 					}
-					if (value === false) return factory(data, callback);
-					if (value === true) value = dependency.request;
-					if (typeof type === "undefined" && /^[a-z0-9]+ /.test(value)) {
-						const idx = value.indexOf(" ");
-						type = value.substr(0, idx);
-						value = value.substr(idx + 1);
+
+					// a css `url()` reads the url out of the external: no js wrapper
+					// TODO webpack 6 drop "css-url" once the alias is removed
+					if (
+						(resolvedType === "asset" ||
+							resolvedType === ASSET_URL_TYPE ||
+							resolvedType === "css-url") &&
+						dependency instanceof CssUrlDependency
+					) {
+						dependencyMeta = { sourceType: ASSET_URL_TYPE };
 					}
+
 					callback(
 						null,
-						new ExternalModule(value, type || globalType, dependency.request)
+						new ExternalModule(
+							externalConfig,
+							resolvedType,
+							dependency.request,
+							dependencyMeta,
+							interop,
+							sideEffects
+						)
 					);
-					return true;
 				};
 
+				/**
+				 * Processes the provided external.
+				 * @param {Externals} externals externals config
+				 * @param {HandleExternalCallback} callback callback
+				 * @returns {void}
+				 */
 				const handleExternals = (externals, callback) => {
 					if (typeof externals === "string") {
-						if (externals === dependency.request) {
-							return handleExternal(dependency.request, callback);
+						if (
+							externals === dependency.request ||
+							externals === getAlternateCoreModuleRequest(dependency.request)
+						) {
+							return handleExternal(dependency.request, undefined, callback);
 						}
 					} else if (Array.isArray(externals)) {
 						let i = 0;
 						const next = () => {
+							/** @type {boolean | undefined} */
 							let asyncFlag;
+							/**
+							 * Handle externals and callback.
+							 * @param {(Error | null)=} err err
+							 * @param {ExternalModule=} module module
+							 * @returns {void}
+							 */
 							const handleExternalsAndCallback = (err, module) => {
 								if (err) return callback(err);
 								if (!module) {
@@ -64,7 +336,7 @@ class ExternalModuleFactoryPlugin {
 								asyncFlag = true;
 								if (i >= externals.length) return callback();
 								handleExternals(externals[i++], handleExternalsAndCallback);
-							} while (!asyncFlag); // eslint-disable-line keyword-spacing
+							} while (!asyncFlag);
 							asyncFlag = false;
 						};
 
@@ -72,39 +344,131 @@ class ExternalModuleFactoryPlugin {
 						return;
 					} else if (externals instanceof RegExp) {
 						if (externals.test(dependency.request)) {
-							return handleExternal(dependency.request, callback);
+							return handleExternal(dependency.request, undefined, callback);
 						}
 					} else if (typeof externals === "function") {
-						externals.call(
-							null,
-							context,
-							dependency.request,
-							(err, value, type) => {
-								if (err) return callback(err);
-								if (typeof value !== "undefined") {
-									handleExternal(value, type, callback);
-								} else {
-									callback();
-								}
+						/**
+						 * Processes the provided err.
+						 * @param {Error | null | undefined} err err
+						 * @param {ExternalValue=} value value
+						 * @param {ExternalsType=} type type
+						 * @returns {void}
+						 */
+						const cb = (err, value, type) => {
+							if (err) return callback(err);
+							if (value !== undefined) {
+								handleExternal(value, type, callback);
+							} else {
+								callback();
 							}
-						);
+						};
+						if (externals.length === 3) {
+							// TODO webpack 6 remove this
+							callDeprecatedExternals(
+								externals,
+								context,
+								dependency.request,
+								cb
+							);
+						} else {
+							const promise = externals(
+								{
+									context,
+									request: dependency.request,
+									dependencyType,
+									contextInfo,
+									getResolve: (options) => (context, request, callback) => {
+										/** @type {ResolveContext} */
+										const resolveContext = {
+											fileDependencies: data.fileDependencies,
+											missingDependencies: data.missingDependencies,
+											contextDependencies: data.contextDependencies
+										};
+										let resolver = normalModuleFactory.getResolver(
+											"normal",
+											dependencyType
+												? cachedSetProperty(
+														data.resolveOptions || EMPTY_RESOLVE_OPTIONS,
+														"dependencyType",
+														dependencyType
+													)
+												: data.resolveOptions
+										);
+										if (options) resolver = resolver.withOptions(options);
+										if (callback) {
+											resolver.resolve(
+												{},
+												context,
+												request,
+												resolveContext,
+												callback
+											);
+										} else {
+											return new Promise((resolve, reject) => {
+												resolver.resolve(
+													{},
+													context,
+													request,
+													resolveContext,
+													(err, result) => {
+														if (err) reject(err);
+														else resolve(result);
+													}
+												);
+											});
+										}
+									}
+								},
+								cb
+							);
+							if (promise && promise.then) {
+								promise.then((r) => cb(null, r), cb);
+							}
+						}
 						return;
-					} else if (
-						typeof externals === "object" &&
-						Object.prototype.hasOwnProperty.call(externals, dependency.request)
-					) {
-						return handleExternal(externals[dependency.request], callback);
+					} else if (typeof externals === "object") {
+						const resolvedExternals = resolveLayer(
+							externals,
+							/** @type {IssuerLayer} */
+							(contextInfo.issuerLayer)
+						);
+						if (
+							Object.prototype.hasOwnProperty.call(
+								resolvedExternals,
+								dependency.request
+							)
+						) {
+							return handleExternal(
+								resolvedExternals[dependency.request],
+								undefined,
+								callback
+							);
+						}
+						// Fall back to the `node:`-prefixed/unprefixed core module form.
+						const alternateRequest = getAlternateCoreModuleRequest(
+							dependency.request
+						);
+						if (
+							alternateRequest !== undefined &&
+							Object.prototype.hasOwnProperty.call(
+								resolvedExternals,
+								alternateRequest
+							)
+						) {
+							return handleExternal(
+								resolvedExternals[alternateRequest],
+								undefined,
+								callback
+							);
+						}
 					}
 					callback();
 				};
 
-				handleExternals(this.externals, (err, module) => {
-					if (err) return callback(err);
-					if (!module) return handleExternal(false, callback);
-					return callback(null, module);
-				});
+				handleExternals(this.externals, callback);
 			}
 		);
 	}
 }
+
 module.exports = ExternalModuleFactoryPlugin;
diff --git a/lib/ExternalsPlugin.js b/lib/ExternalsPlugin.js
index 697f1aaa0f1..b015dc9173b 100644
--- a/lib/ExternalsPlugin.js
+++ b/lib/ExternalsPlugin.js
@@ -2,21 +2,93 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
+const { ModuleExternalInitFragment } = require("./ExternalModule");
 const ExternalModuleFactoryPlugin = require("./ExternalModuleFactoryPlugin");
+const ConcatenatedModule = require("./optimize/ConcatenatedModule");
+
+/** @typedef {import("../declarations/WebpackOptions").ExternalsType} ExternalsType */
+/** @typedef {import("../declarations/WebpackOptions").Externals} Externals */
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./ExternalModule").Imported} Imported */
+/** @typedef {import("./Dependency")} Dependency */
+
+const PLUGIN_NAME = "ExternalsPlugin";
 
 class ExternalsPlugin {
+	/**
+	 * Creates an instance of ExternalsPlugin.
+	 * @param {ExternalsType | ((dependency: Dependency) => ExternalsType)} type default external type
+	 * @param {Externals} externals externals config
+	 */
 	constructor(type, externals) {
 		this.type = type;
+		/** @type {Externals} */
 		this.externals = externals;
 	}
+
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
 	apply(compiler) {
-		compiler.hooks.compile.tap("ExternalsPlugin", ({ normalModuleFactory }) => {
+		compiler.hooks.compile.tap(PLUGIN_NAME, ({ normalModuleFactory }) => {
 			new ExternalModuleFactoryPlugin(this.type, this.externals).apply(
 				normalModuleFactory
 			);
 		});
+
+		compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
+			const { concatenatedModuleInfo } =
+				ConcatenatedModule.getCompilationHooks(compilation);
+			concatenatedModuleInfo.tap(PLUGIN_NAME, (updatedInfo, moduleInfo) => {
+				const rawExportMap = updatedInfo.rawExportMap;
+
+				if (!rawExportMap) {
+					return;
+				}
+
+				const chunkInitFragments = moduleInfo.chunkInitFragments;
+				const moduleExternalInitFragments =
+					/** @type {ModuleExternalInitFragment[]} */
+					(
+						chunkInitFragments
+							? /** @type {unknown[]} */
+								(chunkInitFragments).filter(
+									(fragment) => fragment instanceof ModuleExternalInitFragment
+								)
+							: []
+					);
+
+				let initFragmentChanged = false;
+
+				for (const fragment of moduleExternalInitFragments) {
+					const imported = fragment.getImported();
+
+					if (Array.isArray(imported)) {
+						const newImported =
+							/** @type {Imported} */
+							(
+								imported.map(([specifier, finalName]) => [
+									specifier,
+									rawExportMap.has(specifier)
+										? rawExportMap.get(specifier)
+										: finalName
+								])
+							);
+						fragment.setImported(newImported);
+						initFragmentChanged = true;
+					}
+				}
+
+				if (initFragmentChanged) {
+					return true;
+				}
+			});
+		});
 	}
 }
 
diff --git a/lib/FileSystemInfo.js b/lib/FileSystemInfo.js
new file mode 100644
index 00000000000..b6820c85021
--- /dev/null
+++ b/lib/FileSystemInfo.js
@@ -0,0 +1,4616 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const nodeModule = require("module");
+const { isAbsolute } = require("path");
+const { create: createResolver } = require("enhanced-resolve");
+const asyncLib = require("neo-async");
+const { DEFAULTS } = require("./config/defaults");
+const { collectCjsRequireSpecifiers } = require("./javascript/syntax");
+const AsyncQueue = require("./util/AsyncQueue");
+const StackedCacheMap = require("./util/StackedCacheMap");
+const createHash = require("./util/createHash");
+const { dirname, join, lstatReadlinkAbsolute, relative } = require("./util/fs");
+const makeSerializable = require("./util/makeSerializable");
+const memoize = require("./util/memoize");
+const processAsyncTree = require("./util/processAsyncTree");
+
+// only the mjs build-dependency walk needs it
+const getEsModuleLexer = memoize(() => require("es-module-lexer"));
+
+/** @typedef {import("enhanced-resolve").ResolveRequest} ResolveRequest */
+/** @typedef {import("enhanced-resolve").ResolveFunctionAsync} ResolveFunctionAsync */
+/** @typedef {import("../declarations/WebpackOptions").HashFunction} HashFunction */
+/** @typedef {import("./logging/Logger").Logger} Logger */
+/** @typedef {import("./errors/WebpackError")} WebpackError */
+/** @typedef {import("./util/fs").JsonObject} JsonObject */
+/** @typedef {import("./util/fs").IStats} IStats */
+/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
+/** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */
+/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */
+/**
+ * Defines the processor callback type used by this module.
+ * @template T
+ * @typedef {import("./util/AsyncQueue").Callback} ProcessorCallback
+ */
+/**
+ * Defines the processor type used by this module.
+ * @template T, R
+ * @typedef {import("./util/AsyncQueue").Processor} Processor
+ */
+
+const supportsEsm = Number(process.versions.modules) >= 83;
+
+/** @type {Set} */
+const builtinModules = new Set(nodeModule.builtinModules);
+
+let FS_ACCURACY = 2000;
+
+/** @type {Set} */
+const EMPTY_SET = new Set();
+
+const RBDT_RESOLVE_INITIAL = 0;
+const RBDT_RESOLVE_FILE = 1;
+const RBDT_RESOLVE_DIRECTORY = 2;
+const RBDT_RESOLVE_CJS_FILE = 3;
+const RBDT_RESOLVE_CJS_FILE_AS_CHILD = 4;
+const RBDT_RESOLVE_ESM_FILE = 5;
+const RBDT_DIRECTORY = 6;
+const RBDT_FILE = 7;
+const RBDT_DIRECTORY_DEPENDENCIES = 8;
+const RBDT_FILE_DEPENDENCIES = 9;
+
+/** @typedef {RBDT_RESOLVE_INITIAL | RBDT_RESOLVE_FILE | RBDT_RESOLVE_DIRECTORY | RBDT_RESOLVE_CJS_FILE | RBDT_RESOLVE_CJS_FILE_AS_CHILD | RBDT_RESOLVE_ESM_FILE | RBDT_DIRECTORY | RBDT_FILE | RBDT_DIRECTORY_DEPENDENCIES | RBDT_FILE_DEPENDENCIES} JobType */
+
+const INVALID = Symbol("invalid");
+
+// eslint-disable-next-line jsdoc/ts-no-empty-object-type
+/** @typedef {{  }} ExistenceOnlyTimeEntry */
+
+/**
+ * Defines the file system info entry type used by this module.
+ * @typedef {object} FileSystemInfoEntry
+ * @property {number} safeTime
+ * @property {number=} timestamp
+ */
+
+/**
+ * Defines the resolved context file system info entry type used by this module.
+ * @typedef {object} ResolvedContextFileSystemInfoEntry
+ * @property {number} safeTime
+ * @property {string=} timestampHash
+ */
+
+/** @typedef {Set} Symlinks */
+
+/**
+ * Defines the context file system info entry type used by this module.
+ * @typedef {object} ContextFileSystemInfoEntry
+ * @property {number} safeTime
+ * @property {string=} timestampHash
+ * @property {ResolvedContextFileSystemInfoEntry=} resolved
+ * @property {Symlinks=} symlinks
+ */
+
+/**
+ * Defines the timestamp and hash type used by this module.
+ * @typedef {object} TimestampAndHash
+ * @property {number} safeTime
+ * @property {number=} timestamp
+ * @property {string} hash
+ */
+
+/**
+ * Defines the resolved context timestamp and hash type used by this module.
+ * @typedef {object} ResolvedContextTimestampAndHash
+ * @property {number} safeTime
+ * @property {string=} timestampHash
+ * @property {string} hash
+ */
+
+/**
+ * Defines the context timestamp and hash type used by this module.
+ * @typedef {object} ContextTimestampAndHash
+ * @property {number} safeTime
+ * @property {string=} timestampHash
+ * @property {string} hash
+ * @property {ResolvedContextTimestampAndHash=} resolved
+ * @property {Symlinks=} symlinks
+ */
+
+/**
+ * Defines the context hash type used by this module.
+ * @typedef {object} ContextHash
+ * @property {string} hash
+ * @property {string=} resolved
+ * @property {Symlinks=} symlinks
+ */
+
+/** @typedef {Set} SnapshotContent */
+
+/**
+ * Defines the snapshot optimization entry type used by this module.
+ * @typedef {object} SnapshotOptimizationEntry
+ * @property {Snapshot} snapshot
+ * @property {number} shared
+ * @property {SnapshotContent | undefined} snapshotContent
+ * @property {Set | undefined} children
+ */
+
+/** @typedef {Map} ResolveResults */
+
+/** @typedef {Set} Files */
+/** @typedef {Set} Directories */
+/** @typedef {Set} Missing */
+
+/**
+ * Defines the resolve dependencies type used by this module.
+ * @typedef {object} ResolveDependencies
+ * @property {Files} files list of files
+ * @property {Directories} directories list of directories
+ * @property {Missing} missing list of missing entries
+ */
+
+/**
+ * Defines the resolve build dependencies result type used by this module.
+ * @typedef {object} ResolveBuildDependenciesResult
+ * @property {Files} files list of files
+ * @property {Directories} directories list of directories
+ * @property {Missing} missing list of missing entries
+ * @property {ResolveResults} resolveResults stored resolve results
+ * @property {ResolveDependencies} resolveDependencies dependencies of the resolving
+ */
+
+/**
+ * Defines the snapshot options type used by this module.
+ * @typedef {object} SnapshotOptions
+ * @property {boolean=} hash should use hash to snapshot
+ * @property {boolean=} timestamp should use timestamp to snapshot
+ */
+
+const DONE_ITERATOR_RESULT = new Set().keys().next();
+
+// cspell:word tshs
+// Tsh = Timestamp + Hash
+// Tshs = Timestamp + Hash combinations
+
+class SnapshotIterator {
+	/**
+	 * Creates an instance of SnapshotIterator.
+	 * @param {() => IteratorResult} next next
+	 */
+	constructor(next) {
+		this.next = next;
+	}
+}
+
+/**
+ * Defines the get maps function type used by this module.
+ * @template T
+ * @typedef {(snapshot: Snapshot) => T[]} GetMapsFunction
+ */
+
+/**
+ * Represents SnapshotIterable.
+ * @template T
+ */
+class SnapshotIterable {
+	/**
+	 * Creates an instance of SnapshotIterable.
+	 * @param {Snapshot} snapshot snapshot
+	 * @param {GetMapsFunction} getMaps get maps function
+	 */
+	constructor(snapshot, getMaps) {
+		/** @type {Snapshot} */
+		this.snapshot = snapshot;
+		/** @type {GetMapsFunction} */
+		this.getMaps = getMaps;
+	}
+
+	[Symbol.iterator]() {
+		let state = 0;
+		/** @type {IterableIterator} */
+		let it;
+		/** @type {GetMapsFunction} */
+		let getMaps;
+		/** @type {T[]} */
+		let maps;
+		/** @type {Snapshot} */
+		let snapshot;
+		/** @type {Snapshot[] | undefined} */
+		let queue;
+		return new SnapshotIterator(() => {
+			for (;;) {
+				switch (state) {
+					case 0:
+						snapshot = this.snapshot;
+						getMaps = this.getMaps;
+						maps = getMaps(snapshot);
+						state = 1;
+					/* falls through */
+					case 1:
+						if (maps.length > 0) {
+							const map = maps.pop();
+							if (map !== undefined) {
+								it =
+									/** @type {Set | Map} */
+									(map).keys();
+								state = 2;
+							} else {
+								break;
+							}
+						} else {
+							state = 3;
+							break;
+						}
+					/* falls through */
+					case 2: {
+						const result = it.next();
+						if (!result.done) return result;
+						state = 1;
+						break;
+					}
+					case 3: {
+						const children = snapshot.children;
+						if (children !== undefined) {
+							if (children.size === 1) {
+								// shortcut for a single child
+								// avoids allocation of queue
+								for (const child of children) snapshot = child;
+								maps = getMaps(snapshot);
+								state = 1;
+								break;
+							}
+							if (queue === undefined) queue = [];
+							for (const child of children) {
+								queue.push(child);
+							}
+						}
+						if (queue !== undefined && queue.length > 0) {
+							snapshot = /** @type {Snapshot} */ (queue.pop());
+							maps = getMaps(snapshot);
+							state = 1;
+							break;
+						} else {
+							state = 4;
+						}
+					}
+					/* falls through */
+					case 4:
+						return DONE_ITERATOR_RESULT;
+				}
+			}
+		});
+	}
+}
+
+/** @typedef {Map} FileTimestamps */
+/** @typedef {Map} FileHashes */
+/** @typedef {Map} FileTshs */
+/** @typedef {Map} ContextTimestamps */
+/** @typedef {Map} ContextHashes */
+/** @typedef {Map} ContextTshs */
+/** @typedef {Map} MissingExistence */
+/** @typedef {Map} ManagedItemInfo */
+/** @typedef {Set} ManagedFiles */
+/** @typedef {Set} ManagedContexts */
+/** @typedef {Set} ManagedMissing */
+/** @typedef {Set} Children */
+
+// `Snapshot._flags` bit for each optional field. Serialized as a whole integer,
+// so these values are a persisted-cache format contract — never renumber them.
+const SNAPSHOT_FLAG_START_TIME = 1;
+const SNAPSHOT_FLAG_FILE_TIMESTAMPS = 2;
+const SNAPSHOT_FLAG_FILE_HASHES = 4;
+const SNAPSHOT_FLAG_FILE_TSHS = 8;
+const SNAPSHOT_FLAG_CONTEXT_TIMESTAMPS = 0x10;
+const SNAPSHOT_FLAG_CONTEXT_HASHES = 0x20;
+const SNAPSHOT_FLAG_CONTEXT_TSHS = 0x40;
+const SNAPSHOT_FLAG_MISSING_EXISTENCE = 0x80;
+const SNAPSHOT_FLAG_MANAGED_ITEM_INFO = 0x100;
+const SNAPSHOT_FLAG_MANAGED_FILES = 0x200;
+const SNAPSHOT_FLAG_MANAGED_CONTEXTS = 0x400;
+const SNAPSHOT_FLAG_MANAGED_MISSING = 0x800;
+const SNAPSHOT_FLAG_CHILDREN = 0x1000;
+
+class Snapshot {
+	constructor() {
+		/** @type {number} */
+		this._flags = 0;
+		/** @type {Iterable | undefined} */
+		this._cachedFileIterable = undefined;
+		/** @type {Iterable | undefined} */
+		this._cachedContextIterable = undefined;
+		/** @type {Iterable | undefined} */
+		this._cachedMissingIterable = undefined;
+		/** @type {number | undefined} */
+		this.startTime = undefined;
+		/** @type {FileTimestamps | undefined} */
+		this.fileTimestamps = undefined;
+		/** @type {FileHashes | undefined} */
+		this.fileHashes = undefined;
+		/** @type {FileTshs | undefined} */
+		this.fileTshs = undefined;
+		/** @type {ContextTimestamps | undefined} */
+		this.contextTimestamps = undefined;
+		/** @type {ContextHashes | undefined} */
+		this.contextHashes = undefined;
+		/** @type {ContextTshs | undefined} */
+		this.contextTshs = undefined;
+		/** @type {MissingExistence | undefined} */
+		this.missingExistence = undefined;
+		/** @type {ManagedItemInfo | undefined} */
+		this.managedItemInfo = undefined;
+		/** @type {ManagedFiles | undefined} */
+		this.managedFiles = undefined;
+		/** @type {ManagedContexts | undefined} */
+		this.managedContexts = undefined;
+		/** @type {ManagedMissing | undefined} */
+		this.managedMissing = undefined;
+		/** @type {Children | undefined} */
+		this.children = undefined;
+	}
+
+	hasStartTime() {
+		return (this._flags & SNAPSHOT_FLAG_START_TIME) !== 0;
+	}
+
+	/**
+	 * Updates start time using the provided value.
+	 * @param {number} value start value
+	 */
+	setStartTime(value) {
+		this._flags |= SNAPSHOT_FLAG_START_TIME;
+		this.startTime = value;
+	}
+
+	/**
+	 * Sets merged start time.
+	 * @param {number | undefined} value value
+	 * @param {Snapshot} snapshot snapshot
+	 */
+	setMergedStartTime(value, snapshot) {
+		if (value) {
+			if (snapshot.hasStartTime()) {
+				this.setStartTime(
+					Math.min(
+						value,
+						/** @type {NonNullable} */
+						(snapshot.startTime)
+					)
+				);
+			} else {
+				this.setStartTime(value);
+			}
+		} else if (snapshot.hasStartTime()) {
+			this.setStartTime(
+				/** @type {NonNullable} */
+				(snapshot.startTime)
+			);
+		}
+	}
+
+	hasFileTimestamps() {
+		return (this._flags & SNAPSHOT_FLAG_FILE_TIMESTAMPS) !== 0;
+	}
+
+	/**
+	 * Sets file timestamps.
+	 * @param {FileTimestamps} value file timestamps
+	 */
+	setFileTimestamps(value) {
+		this._flags |= SNAPSHOT_FLAG_FILE_TIMESTAMPS;
+		this.fileTimestamps = value;
+	}
+
+	hasFileHashes() {
+		return (this._flags & SNAPSHOT_FLAG_FILE_HASHES) !== 0;
+	}
+
+	/**
+	 * Updates file hashes using the provided value.
+	 * @param {FileHashes} value file hashes
+	 */
+	setFileHashes(value) {
+		this._flags |= SNAPSHOT_FLAG_FILE_HASHES;
+		this.fileHashes = value;
+	}
+
+	hasFileTshs() {
+		return (this._flags & SNAPSHOT_FLAG_FILE_TSHS) !== 0;
+	}
+
+	/**
+	 * Updates file tshs using the provided value.
+	 * @param {FileTshs} value file tshs
+	 */
+	setFileTshs(value) {
+		this._flags |= SNAPSHOT_FLAG_FILE_TSHS;
+		this.fileTshs = value;
+	}
+
+	hasContextTimestamps() {
+		return (this._flags & SNAPSHOT_FLAG_CONTEXT_TIMESTAMPS) !== 0;
+	}
+
+	/**
+	 * Sets context timestamps.
+	 * @param {ContextTimestamps} value context timestamps
+	 */
+	setContextTimestamps(value) {
+		this._flags |= SNAPSHOT_FLAG_CONTEXT_TIMESTAMPS;
+		this.contextTimestamps = value;
+	}
+
+	hasContextHashes() {
+		return (this._flags & SNAPSHOT_FLAG_CONTEXT_HASHES) !== 0;
+	}
+
+	/**
+	 * Sets context hashes.
+	 * @param {ContextHashes} value context hashes
+	 */
+	setContextHashes(value) {
+		this._flags |= SNAPSHOT_FLAG_CONTEXT_HASHES;
+		this.contextHashes = value;
+	}
+
+	hasContextTshs() {
+		return (this._flags & SNAPSHOT_FLAG_CONTEXT_TSHS) !== 0;
+	}
+
+	/**
+	 * Updates context tshs using the provided value.
+	 * @param {ContextTshs} value context tshs
+	 */
+	setContextTshs(value) {
+		this._flags |= SNAPSHOT_FLAG_CONTEXT_TSHS;
+		this.contextTshs = value;
+	}
+
+	hasMissingExistence() {
+		return (this._flags & SNAPSHOT_FLAG_MISSING_EXISTENCE) !== 0;
+	}
+
+	/**
+	 * Sets missing existence.
+	 * @param {MissingExistence} value context tshs
+	 */
+	setMissingExistence(value) {
+		this._flags |= SNAPSHOT_FLAG_MISSING_EXISTENCE;
+		this.missingExistence = value;
+	}
+
+	hasManagedItemInfo() {
+		return (this._flags & SNAPSHOT_FLAG_MANAGED_ITEM_INFO) !== 0;
+	}
+
+	/**
+	 * Sets managed item info.
+	 * @param {ManagedItemInfo} value managed item info
+	 */
+	setManagedItemInfo(value) {
+		this._flags |= SNAPSHOT_FLAG_MANAGED_ITEM_INFO;
+		this.managedItemInfo = value;
+	}
+
+	hasManagedFiles() {
+		return (this._flags & SNAPSHOT_FLAG_MANAGED_FILES) !== 0;
+	}
+
+	/**
+	 * Sets managed files.
+	 * @param {ManagedFiles} value managed files
+	 */
+	setManagedFiles(value) {
+		this._flags |= SNAPSHOT_FLAG_MANAGED_FILES;
+		this.managedFiles = value;
+	}
+
+	hasManagedContexts() {
+		return (this._flags & SNAPSHOT_FLAG_MANAGED_CONTEXTS) !== 0;
+	}
+
+	/**
+	 * Sets managed contexts.
+	 * @param {ManagedContexts} value managed contexts
+	 */
+	setManagedContexts(value) {
+		this._flags |= SNAPSHOT_FLAG_MANAGED_CONTEXTS;
+		this.managedContexts = value;
+	}
+
+	hasManagedMissing() {
+		return (this._flags & SNAPSHOT_FLAG_MANAGED_MISSING) !== 0;
+	}
+
+	/**
+	 * Sets managed missing.
+	 * @param {ManagedMissing} value managed missing
+	 */
+	setManagedMissing(value) {
+		this._flags |= SNAPSHOT_FLAG_MANAGED_MISSING;
+		this.managedMissing = value;
+	}
+
+	hasChildren() {
+		return (this._flags & SNAPSHOT_FLAG_CHILDREN) !== 0;
+	}
+
+	/**
+	 * Updates children using the provided value.
+	 * @param {Children} value children
+	 */
+	setChildren(value) {
+		this._flags |= SNAPSHOT_FLAG_CHILDREN;
+		this.children = value;
+	}
+
+	/**
+	 * Adds the provided child to the snapshot.
+	 * @param {Snapshot} child children
+	 */
+	addChild(child) {
+		if (!this.hasChildren()) {
+			this.setChildren(new Set());
+		}
+		/** @type {Children} */
+		(this.children).add(child);
+	}
+
+	/**
+	 * Serializes this instance into the provided serializer context.
+	 * @param {ObjectSerializerContext} context context
+	 */
+	serialize({ write }) {
+		write(this._flags);
+		if (this.hasStartTime()) write(this.startTime);
+		if (this.hasFileTimestamps()) write(this.fileTimestamps);
+		if (this.hasFileHashes()) write(this.fileHashes);
+		if (this.hasFileTshs()) write(this.fileTshs);
+		if (this.hasContextTimestamps()) write(this.contextTimestamps);
+		if (this.hasContextHashes()) write(this.contextHashes);
+		if (this.hasContextTshs()) write(this.contextTshs);
+		if (this.hasMissingExistence()) write(this.missingExistence);
+		if (this.hasManagedItemInfo()) write(this.managedItemInfo);
+		if (this.hasManagedFiles()) write(this.managedFiles);
+		if (this.hasManagedContexts()) write(this.managedContexts);
+		if (this.hasManagedMissing()) write(this.managedMissing);
+		if (this.hasChildren()) write(this.children);
+	}
+
+	/**
+	 * Restores this instance from the provided deserializer context.
+	 * @param {ObjectDeserializerContext} context context
+	 */
+	deserialize({ read }) {
+		this._flags = read();
+		if (this.hasStartTime()) this.startTime = read();
+		if (this.hasFileTimestamps()) this.fileTimestamps = read();
+		if (this.hasFileHashes()) this.fileHashes = read();
+		if (this.hasFileTshs()) this.fileTshs = read();
+		if (this.hasContextTimestamps()) this.contextTimestamps = read();
+		if (this.hasContextHashes()) this.contextHashes = read();
+		if (this.hasContextTshs()) this.contextTshs = read();
+		if (this.hasMissingExistence()) this.missingExistence = read();
+		if (this.hasManagedItemInfo()) this.managedItemInfo = read();
+		if (this.hasManagedFiles()) this.managedFiles = read();
+		if (this.hasManagedContexts()) this.managedContexts = read();
+		if (this.hasManagedMissing()) this.managedMissing = read();
+		if (this.hasChildren()) this.children = read();
+	}
+
+	/**
+	 * Creates an iterable from the provided get map.
+	 * @template T
+	 * @param {GetMapsFunction} getMaps first
+	 * @returns {SnapshotIterable} iterable
+	 */
+	_createIterable(getMaps) {
+		return new SnapshotIterable(this, getMaps);
+	}
+
+	/**
+	 * Gets file iterable.
+	 * @returns {Iterable} iterable
+	 */
+	getFileIterable() {
+		if (this._cachedFileIterable === undefined) {
+			this._cachedFileIterable = this._createIterable((s) => [
+				s.fileTimestamps,
+				s.fileHashes,
+				s.fileTshs,
+				s.managedFiles
+			]);
+		}
+		return this._cachedFileIterable;
+	}
+
+	/**
+	 * Gets context iterable.
+	 * @returns {Iterable} iterable
+	 */
+	getContextIterable() {
+		if (this._cachedContextIterable === undefined) {
+			this._cachedContextIterable = this._createIterable((s) => [
+				s.contextTimestamps,
+				s.contextHashes,
+				s.contextTshs,
+				s.managedContexts
+			]);
+		}
+		return this._cachedContextIterable;
+	}
+
+	/**
+	 * Gets missing iterable.
+	 * @returns {Iterable} iterable
+	 */
+	getMissingIterable() {
+		if (this._cachedMissingIterable === undefined) {
+			this._cachedMissingIterable = this._createIterable((s) => [
+				s.missingExistence,
+				s.managedMissing
+			]);
+		}
+		return this._cachedMissingIterable;
+	}
+}
+
+makeSerializable(Snapshot, "webpack/lib/FileSystemInfo", "Snapshot");
+
+const MIN_COMMON_SNAPSHOT_SIZE = 3;
+
+/**
+ * Defines the snapshot optimization value type used by this module.
+ * @template U, T
+ * @typedef {U extends true ? Set : Map} SnapshotOptimizationValue
+ */
+
+/**
+ * Represents SnapshotOptimization.
+ * @template T
+ * @template {boolean} [U=false]
+ */
+class SnapshotOptimization {
+	/**
+	 * Creates an instance of SnapshotOptimization.
+	 * @param {(snapshot: Snapshot) => boolean} has has value
+	 * @param {(snapshot: Snapshot) => SnapshotOptimizationValue | undefined} get get value
+	 * @param {(snapshot: Snapshot, value: SnapshotOptimizationValue) => void} set set value
+	 * @param {boolean=} useStartTime use the start time of snapshots
+	 * @param {U=} isSet value is an Set instead of a Map
+	 */
+	constructor(
+		has,
+		get,
+		set,
+		useStartTime = true,
+		isSet = /** @type {U} */ (false)
+	) {
+		this._has = has;
+		this._get = get;
+		this._set = set;
+		/** @type {boolean} */
+		this._useStartTime = useStartTime;
+		/** @type {U} */
+		this._isSet = isSet;
+		/** @type {Map} */
+		this._map = new Map();
+		/** @type {number} */
+		this._statItemsShared = 0;
+		/** @type {number} */
+		this._statItemsUnshared = 0;
+		/** @type {number} */
+		this._statSharedSnapshots = 0;
+		/** @type {number} */
+		this._statReusedSharedSnapshots = 0;
+	}
+
+	getStatisticMessage() {
+		const total = this._statItemsShared + this._statItemsUnshared;
+		if (total === 0) return;
+		return `${
+			this._statItemsShared && Math.round((this._statItemsShared * 100) / total)
+		}% (${this._statItemsShared}/${total}) entries shared via ${
+			this._statSharedSnapshots
+		} shared snapshots (${
+			this._statReusedSharedSnapshots + this._statSharedSnapshots
+		} times referenced)`;
+	}
+
+	clear() {
+		this._map.clear();
+		this._statItemsShared = 0;
+		this._statItemsUnshared = 0;
+		this._statSharedSnapshots = 0;
+		this._statReusedSharedSnapshots = 0;
+	}
+
+	/**
+	 * Processes the provided new snapshot.
+	 * @param {Snapshot} newSnapshot snapshot
+	 * @param {Set} capturedFiles files to snapshot/share
+	 * @returns {void}
+	 */
+	optimize(newSnapshot, capturedFiles) {
+		if (capturedFiles.size === 0) {
+			return;
+		}
+		/**
+		 * Increase shared and store optimization entry.
+		 * @param {SnapshotOptimizationEntry} entry optimization entry
+		 * @returns {void}
+		 */
+		const increaseSharedAndStoreOptimizationEntry = (entry) => {
+			if (entry.children !== undefined) {
+				for (const child of entry.children) {
+					increaseSharedAndStoreOptimizationEntry(child);
+				}
+			}
+			entry.shared++;
+			storeOptimizationEntry(entry);
+		};
+		/**
+		 * Stores optimization entry.
+		 * @param {SnapshotOptimizationEntry} entry optimization entry
+		 * @returns {void}
+		 */
+		const storeOptimizationEntry = (entry) => {
+			for (const path of /** @type {SnapshotContent} */ (
+				entry.snapshotContent
+			)) {
+				const old =
+					/** @type {SnapshotOptimizationEntry} */
+					(this._map.get(path));
+				if (old.shared < entry.shared) {
+					this._map.set(path, entry);
+				}
+				capturedFiles.delete(path);
+			}
+		};
+
+		/** @type {SnapshotOptimizationEntry | undefined} */
+		let newOptimizationEntry;
+
+		const capturedFilesSize = capturedFiles.size;
+
+		/** @type {Set | undefined} */
+		const optimizationEntries = new Set();
+
+		for (const path of capturedFiles) {
+			const optimizationEntry = this._map.get(path);
+			if (optimizationEntry === undefined) {
+				if (newOptimizationEntry === undefined) {
+					newOptimizationEntry = {
+						snapshot: newSnapshot,
+						shared: 0,
+						snapshotContent: undefined,
+						children: undefined
+					};
+				}
+				this._map.set(path, newOptimizationEntry);
+			} else {
+				optimizationEntries.add(optimizationEntry);
+			}
+		}
+
+		optimizationEntriesLabel: for (const optimizationEntry of optimizationEntries) {
+			const snapshot = optimizationEntry.snapshot;
+			if (optimizationEntry.shared > 0) {
+				// It's a shared snapshot
+				// We can't change it, so we can only use it when all files match
+				// and startTime is compatible
+				if (
+					this._useStartTime &&
+					newSnapshot.startTime &&
+					(!snapshot.startTime || snapshot.startTime > newSnapshot.startTime)
+				) {
+					continue;
+				}
+				/** @type {Set} */
+				const nonSharedFiles = new Set();
+				const snapshotContent =
+					/** @type {NonNullable} */
+					(optimizationEntry.snapshotContent);
+				const snapshotEntries =
+					/** @type {SnapshotOptimizationValue} */
+					(this._get(snapshot));
+				for (const path of snapshotContent) {
+					if (!capturedFiles.has(path)) {
+						if (!snapshotEntries.has(path)) {
+							// File is not shared and can't be removed from the snapshot
+							// because it's in a child of the snapshot
+							continue optimizationEntriesLabel;
+						}
+						nonSharedFiles.add(path);
+					}
+				}
+				if (nonSharedFiles.size === 0) {
+					// The complete snapshot is shared
+					// add it as child
+					newSnapshot.addChild(snapshot);
+					increaseSharedAndStoreOptimizationEntry(optimizationEntry);
+					this._statReusedSharedSnapshots++;
+				} else {
+					// Only a part of the snapshot is shared
+					const sharedCount = snapshotContent.size - nonSharedFiles.size;
+					if (sharedCount < MIN_COMMON_SNAPSHOT_SIZE) {
+						// Common part it too small
+						continue;
+					}
+					// Extract common timestamps from both snapshots
+					/** @type {Set | Map} */
+					let commonMap;
+					if (this._isSet) {
+						commonMap = new Set();
+						for (const path of /** @type {Set} */ (snapshotEntries)) {
+							if (nonSharedFiles.has(path)) continue;
+							commonMap.add(path);
+							snapshotEntries.delete(path);
+						}
+					} else {
+						commonMap = new Map();
+						const map = /** @type {Map} */ (snapshotEntries);
+						for (const [path, value] of map) {
+							if (nonSharedFiles.has(path)) continue;
+							commonMap.set(path, value);
+							snapshotEntries.delete(path);
+						}
+					}
+					// Create and attach snapshot
+					const commonSnapshot = new Snapshot();
+					if (this._useStartTime) {
+						commonSnapshot.setMergedStartTime(newSnapshot.startTime, snapshot);
+					}
+					this._set(
+						commonSnapshot,
+						/** @type {SnapshotOptimizationValue} */ (commonMap)
+					);
+					newSnapshot.addChild(commonSnapshot);
+					snapshot.addChild(commonSnapshot);
+					// Create optimization entry
+					const newEntry = {
+						snapshot: commonSnapshot,
+						shared: optimizationEntry.shared + 1,
+						snapshotContent: new Set(commonMap.keys()),
+						children: undefined
+					};
+					if (optimizationEntry.children === undefined) {
+						optimizationEntry.children = new Set();
+					}
+					optimizationEntry.children.add(newEntry);
+					storeOptimizationEntry(newEntry);
+					this._statSharedSnapshots++;
+				}
+			} else {
+				// It's a unshared snapshot
+				// We can extract a common shared snapshot
+				// with all common files
+				const snapshotEntries = this._get(snapshot);
+				if (snapshotEntries === undefined) {
+					// Incomplete snapshot, that can't be used
+					continue;
+				}
+				/** @type {Set | Map} */
+				let commonMap;
+				if (this._isSet) {
+					commonMap = new Set();
+					const set = /** @type {Set} */ (snapshotEntries);
+					if (capturedFiles.size < set.size) {
+						for (const path of capturedFiles) {
+							if (set.has(path)) commonMap.add(path);
+						}
+					} else {
+						for (const path of set) {
+							if (capturedFiles.has(path)) commonMap.add(path);
+						}
+					}
+				} else {
+					commonMap = new Map();
+					const map = /** @type {Map} */ (snapshotEntries);
+					for (const path of capturedFiles) {
+						const ts = map.get(path);
+						if (ts === undefined) continue;
+						commonMap.set(path, ts);
+					}
+				}
+
+				if (commonMap.size < MIN_COMMON_SNAPSHOT_SIZE) {
+					// Common part it too small
+					continue;
+				}
+				// Create and attach snapshot
+				const commonSnapshot = new Snapshot();
+				if (this._useStartTime) {
+					commonSnapshot.setMergedStartTime(newSnapshot.startTime, snapshot);
+				}
+				this._set(
+					commonSnapshot,
+					/** @type {SnapshotOptimizationValue} */
+					(commonMap)
+				);
+				newSnapshot.addChild(commonSnapshot);
+				snapshot.addChild(commonSnapshot);
+				// Remove files from snapshot
+				for (const path of commonMap.keys()) snapshotEntries.delete(path);
+				const sharedCount = commonMap.size;
+				this._statItemsUnshared -= sharedCount;
+				this._statItemsShared += sharedCount;
+				// Create optimization entry
+				storeOptimizationEntry({
+					snapshot: commonSnapshot,
+					shared: 2,
+					snapshotContent: new Set(commonMap.keys()),
+					children: undefined
+				});
+				this._statSharedSnapshots++;
+			}
+		}
+		const unshared = capturedFiles.size;
+		this._statItemsUnshared += unshared;
+		this._statItemsShared += capturedFilesSize - unshared;
+	}
+}
+
+/** @type {Record} */
+const ESCAPES = {
+	n: "\n",
+	r: "\r",
+	t: "\t",
+	b: "\b",
+	f: "\f",
+	v: "\v"
+};
+
+/**
+ * Cooks a JS string/template literal specifier into its evaluated value,
+ * matching ECMAScript escape semantics (sloppy-mode for string literals).
+ * @param {string} str input including the surrounding quotes
+ * @returns {string | null} result, or null when not a quoted literal
+ * @throws {Error} on a malformed escape sequence, like the JS parser would
+ */
+const parseString = (str) => {
+	const q = str[0];
+
+	if (q !== '"' && q !== "'" && q !== "`") {
+		return null;
+	}
+
+	const template = q === "`";
+	const inner = str.slice(1, -1);
+	const len = inner.length;
+	let result = "";
+	let i = 0;
+
+	while (i < len) {
+		const ch = inner[i];
+		if (ch !== "\\") {
+			// Template value normalizes raw  and  to 
+			if (template && ch === "\r") {
+				result += "\n";
+				i += inner[i + 1] === "\n" ? 2 : 1;
+			} else {
+				result += ch;
+				i++;
+			}
+			continue;
+		}
+
+		i++;
+		if (i >= len) throw new Error("Unterminated escape sequence");
+		const esc = inner[i];
+
+		if (esc === "x") {
+			const hex = inner.slice(i + 1, i + 3);
+			if (!/^[0-9a-fA-F]{2}$/.test(hex)) {
+				throw new Error("Invalid hexadecimal escape sequence");
+			}
+			result += String.fromCharCode(Number.parseInt(hex, 16));
+			i += 3;
+		} else if (esc === "u") {
+			if (inner[i + 1] === "{") {
+				// \u{N...}
+				const closeIdx = inner.indexOf("}", i + 2);
+				const codeStr = closeIdx === -1 ? "" : inner.slice(i + 2, closeIdx);
+				const code = Number.parseInt(codeStr, 16);
+				if (!/^[0-9a-fA-F]+$/.test(codeStr) || code > 0x10ffff) {
+					throw new Error("Invalid Unicode escape sequence");
+				}
+				result += String.fromCodePoint(code);
+				i = closeIdx + 1;
+			} else {
+				// \uNNNN
+				const hex = inner.slice(i + 1, i + 5);
+				if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
+					throw new Error("Invalid Unicode escape sequence");
+				}
+				result += String.fromCharCode(Number.parseInt(hex, 16));
+				i += 5;
+			}
+		} else if (esc === "\r") {
+			// Line continuation: \ or \
+			i += inner[i + 1] === "\n" ? 2 : 1;
+		} else if (esc === "\n" || esc === "\u2028" || esc === "\u2029") {
+			// Line continuation: \, \, \
+			i++;
+		} else if (ESCAPES[esc] !== undefined) {
+			result += ESCAPES[esc];
+			i++;
+		} else if (esc >= "0" && esc <= "7") {
+			// \0 not followed by a digit is NUL, otherwise a legacy octal escape
+			const next = inner[i + 1];
+			if (esc === "0" && !(next >= "0" && next <= "9")) {
+				result += "\0";
+				i++;
+				continue;
+			}
+			if (template) throw new Error("Octal escape not allowed in template");
+			// Up to 3 octal digits, value capped at \377
+			let oct = esc;
+			i++;
+			const max = esc <= "3" ? 3 : 2;
+			while (oct.length < max && inner[i] >= "0" && inner[i] <= "7") {
+				oct += inner[i];
+				i++;
+			}
+			result += String.fromCharCode(Number.parseInt(oct, 8));
+		} else if (esc === "8" || esc === "9") {
+			if (template) throw new Error("\\8 and \\9 not allowed in template");
+			result += esc;
+			i++;
+		} else {
+			// \', \", \\, \`, and any other NonEscapeCharacter — the char itself
+			result += esc;
+			i++;
+		}
+	}
+
+	return result;
+};
+
+/* istanbul ignore next */
+/**
+ * Processes the provided mtime.
+ * @param {number} mtime mtime
+ */
+const applyMtime = (mtime) => {
+	if (FS_ACCURACY > 1 && mtime % 2 !== 0) FS_ACCURACY = 1;
+	else if (FS_ACCURACY > 10 && mtime % 20 !== 0) FS_ACCURACY = 10;
+	else if (FS_ACCURACY > 100 && mtime % 200 !== 0) FS_ACCURACY = 100;
+	else if (FS_ACCURACY > 1000 && mtime % 2000 !== 0) FS_ACCURACY = 1000;
+};
+
+/**
+ * Merges the provided values into a single result.
+ * @template T
+ * @template K
+ * @param {Map | undefined} a source map
+ * @param {Map | undefined} b joining map
+ * @returns {Map} joined map
+ */
+const mergeMaps = (a, b) => {
+	if (!b || b.size === 0) return /** @type {Map} */ (a);
+	if (!a || a.size === 0) return /** @type {Map} */ (b);
+	/** @type {Map} */
+	const map = new Map(a);
+	for (const [key, value] of b) {
+		map.set(key, value);
+	}
+	return map;
+};
+
+/**
+ * Merges the provided values into a single result.
+ * @template T
+ * @param {Set | undefined} a source map
+ * @param {Set | undefined} b joining map
+ * @returns {Set} joined map
+ */
+const mergeSets = (a, b) => {
+	if (!b || b.size === 0) return /** @type {Set} */ (a);
+	if (!a || a.size === 0) return /** @type {Set} */ (b);
+	/** @type {Set} */
+	const map = new Set(a);
+	for (const item of b) {
+		map.add(item);
+	}
+	return map;
+};
+
+/**
+ * Finding file or directory to manage
+ * @param {string} managedPath path that is managing by {@link FileSystemInfo}
+ * @param {string} path path to file or directory
+ * @returns {string | null} managed item
+ * @example
+ * getManagedItem(
+ *   '/Users/user/my-project/node_modules/',
+ *   '/Users/user/my-project/node_modules/package/index.js'
+ * ) === '/Users/user/my-project/node_modules/package'
+ * getManagedItem(
+ *   '/Users/user/my-project/node_modules/',
+ *   '/Users/user/my-project/node_modules/package1/node_modules/package2'
+ * ) === '/Users/user/my-project/node_modules/package1/node_modules/package2'
+ * getManagedItem(
+ *   '/Users/user/my-project/node_modules/',
+ *   '/Users/user/my-project/node_modules/.bin/script.js'
+ * ) === null // hidden files are disallowed as managed items
+ * getManagedItem(
+ *   '/Users/user/my-project/node_modules/',
+ *   '/Users/user/my-project/node_modules/package'
+ * ) === '/Users/user/my-project/node_modules/package'
+ */
+const getManagedItem = (managedPath, path) => {
+	let i = managedPath.length;
+	let slashes = 1;
+	let startingPosition = true;
+	loop: while (i < path.length) {
+		switch (path.charCodeAt(i)) {
+			case 47: // slash
+			case 92: // backslash
+				if (--slashes === 0) break loop;
+				startingPosition = true;
+				break;
+			case 46: // .
+				// hidden files are disallowed as managed items
+				// it's probably .yarn-integrity or .cache
+				if (startingPosition) return null;
+				break;
+			case 64: // @
+				if (!startingPosition) return null;
+				slashes++;
+				break;
+			default:
+				startingPosition = false;
+				break;
+		}
+		i++;
+	}
+	if (i === path.length) slashes--;
+	// return null when path is incomplete
+	if (slashes !== 0) return null;
+	// if (path.slice(i + 1, i + 13) === "node_modules")
+	if (
+		path.length >= i + 13 &&
+		path.charCodeAt(i + 1) === 110 &&
+		path.charCodeAt(i + 2) === 111 &&
+		path.charCodeAt(i + 3) === 100 &&
+		path.charCodeAt(i + 4) === 101 &&
+		path.charCodeAt(i + 5) === 95 &&
+		path.charCodeAt(i + 6) === 109 &&
+		path.charCodeAt(i + 7) === 111 &&
+		path.charCodeAt(i + 8) === 100 &&
+		path.charCodeAt(i + 9) === 117 &&
+		path.charCodeAt(i + 10) === 108 &&
+		path.charCodeAt(i + 11) === 101 &&
+		path.charCodeAt(i + 12) === 115
+	) {
+		// if this is the end of the path
+		if (path.length === i + 13) {
+			// return the node_modules directory
+			// it's special
+			return path;
+		}
+		const c = path.charCodeAt(i + 13);
+		// if next symbol is slash or backslash
+		if (c === 47 || c === 92) {
+			// Managed subpath
+			return getManagedItem(path.slice(0, i + 14), path);
+		}
+	}
+	return path.slice(0, i);
+};
+
+/**
+ * Gets resolved timestamp.
+ * @template {ContextFileSystemInfoEntry | ContextTimestampAndHash} T
+ * @param {T | null} entry entry
+ * @returns {T["resolved"] | null | undefined} the resolved entry
+ */
+const getResolvedTimestamp = (entry) => {
+	if (entry === null) return null;
+	if (entry.resolved !== undefined) return entry.resolved;
+	return entry.symlinks === undefined ? entry : undefined;
+};
+
+/**
+ * Gets resolved hash.
+ * @param {ContextHash | null} entry entry
+ * @returns {string | null | undefined} the resolved entry
+ */
+const getResolvedHash = (entry) => {
+	if (entry === null) return null;
+	if (entry.resolved !== undefined) return entry.resolved;
+	return entry.symlinks === undefined ? entry.hash : undefined;
+};
+
+/**
+ * Adds the provided source to the snapshot optimization.
+ * @template T
+ * @param {Set} source source
+ * @param {Set} target target
+ */
+const addAll = (source, target) => {
+	for (const key of source) target.add(key);
+};
+
+// Bun doesn't populate `require.cache` `module.children`, so the CJS build-dependency
+// walk needs a source-parsing fallback there. Node/Deno populate it — never pay for it.
+const IS_BUN = Boolean(process.versions.bun);
+
+/** @typedef {Set} LoggedPaths */
+
+/** @typedef {FileSystemInfoEntry | ExistenceOnlyTimeEntry | "ignore" | null} FileTimestamp */
+/** @typedef {ContextFileSystemInfoEntry | ExistenceOnlyTimeEntry | "ignore" | null} ContextTimestamp */
+/** @typedef {ResolvedContextFileSystemInfoEntry | "ignore" | null} ResolvedContextTimestamp */
+
+/**
+ * `watchpack` may report `{}` (existence-only) for files and directories it
+ * is watching but has no time information for. Such entries cannot be used
+ * for snapshot comparison, so cache lookups treat them as "no cached value"
+ * and fall back to a fresh on-disk read.
+ * @param {FileTimestamp | ContextTimestamp | undefined} entry cache entry
+ * @returns {entry is ExistenceOnlyTimeEntry} true if the entry exists but carries no time info
+ */
+const isExistenceOnly = (entry) => {
+	if (entry === undefined || entry === null || entry === "ignore") return false;
+	return (
+		/** @type {Partial & Partial} */
+		(entry).safeTime === undefined
+	);
+};
+
+/**
+ * Watchpack directory entries may carry `safeTime` / `timestamp` but never
+ * `timestampHash`; such object caches cannot back snapshot checks or symlink walks.
+ * @param {ContextFileSystemInfoEntry | null | "ignore" | undefined} entry cache entry
+ * @returns {boolean} true when a non-null object entry lacks `timestampHash`
+ */
+const contextCacheEntryLacksTimestampHash = (entry) =>
+	entry !== null &&
+	typeof entry === "object" &&
+	/** @type {ContextFileSystemInfoEntry} */ (entry).timestampHash === undefined;
+
+/** @typedef {(err?: WebpackError | null, result?: boolean) => void} CheckSnapshotValidCallback */
+
+/**
+ * Used to access information about the filesystem in a cached way
+ */
+class FileSystemInfo {
+	/**
+	 * Creates an instance of FileSystemInfo.
+	 * @param {InputFileSystem} fs file system
+	 * @param {object} options options
+	 * @param {Iterable=} options.unmanagedPaths paths that are not managed by a package manager and the contents are subject to change
+	 * @param {Iterable=} options.managedPaths paths that are only managed by a package manager
+	 * @param {Iterable=} options.immutablePaths paths that are immutable
+	 * @param {Logger=} options.logger logger used to log invalid snapshots
+	 * @param {HashFunction=} options.hashFunction the hash function to use
+	 */
+	constructor(
+		fs,
+		{
+			unmanagedPaths = [],
+			managedPaths = [],
+			immutablePaths = [],
+			logger,
+			hashFunction = DEFAULTS.HASH_FUNCTION
+		} = {}
+	) {
+		/** @type {InputFileSystem} */
+		this.fs = fs;
+		this.logger = logger;
+		/** @type {number} */
+		this._remainingLogs = logger ? 40 : 0;
+		/** @type {LoggedPaths | undefined} */
+		this._loggedPaths = logger ? new Set() : undefined;
+		/** @type {HashFunction} */
+		this._hashFunction = hashFunction;
+		/** @type {WeakMap} */
+		this._snapshotCache = new WeakMap();
+		/** @type {SnapshotOptimization} */
+		this._fileTimestampsOptimization = new SnapshotOptimization(
+			(s) => s.hasFileTimestamps(),
+			(s) => s.fileTimestamps,
+			(s, v) => s.setFileTimestamps(v)
+		);
+		/** @type {SnapshotOptimization} */
+		this._fileHashesOptimization = new SnapshotOptimization(
+			(s) => s.hasFileHashes(),
+			(s) => s.fileHashes,
+			(s, v) => s.setFileHashes(v),
+			false
+		);
+		/** @type {SnapshotOptimization} */
+		this._fileTshsOptimization = new SnapshotOptimization(
+			(s) => s.hasFileTshs(),
+			(s) => s.fileTshs,
+			(s, v) => s.setFileTshs(v)
+		);
+		/** @type {SnapshotOptimization} */
+		this._contextTimestampsOptimization = new SnapshotOptimization(
+			(s) => s.hasContextTimestamps(),
+			(s) => s.contextTimestamps,
+			(s, v) => s.setContextTimestamps(v)
+		);
+		/** @type {SnapshotOptimization} */
+		this._contextHashesOptimization = new SnapshotOptimization(
+			(s) => s.hasContextHashes(),
+			(s) => s.contextHashes,
+			(s, v) => s.setContextHashes(v),
+			false
+		);
+		/** @type {SnapshotOptimization} */
+		this._contextTshsOptimization = new SnapshotOptimization(
+			(s) => s.hasContextTshs(),
+			(s) => s.contextTshs,
+			(s, v) => s.setContextTshs(v)
+		);
+		/** @type {SnapshotOptimization} */
+		this._missingExistenceOptimization = new SnapshotOptimization(
+			(s) => s.hasMissingExistence(),
+			(s) => s.missingExistence,
+			(s, v) => s.setMissingExistence(v),
+			false
+		);
+		/** @type {SnapshotOptimization} */
+		this._managedItemInfoOptimization = new SnapshotOptimization(
+			(s) => s.hasManagedItemInfo(),
+			(s) => s.managedItemInfo,
+			(s, v) => s.setManagedItemInfo(v),
+			false
+		);
+		this._managedFilesOptimization = new SnapshotOptimization(
+			(s) => s.hasManagedFiles(),
+			(s) => s.managedFiles,
+			(s, v) => s.setManagedFiles(v),
+			false,
+			true
+		);
+		this._managedContextsOptimization = new SnapshotOptimization(
+			(s) => s.hasManagedContexts(),
+			(s) => s.managedContexts,
+			(s, v) => s.setManagedContexts(v),
+			false,
+			true
+		);
+		this._managedMissingOptimization = new SnapshotOptimization(
+			(s) => s.hasManagedMissing(),
+			(s) => s.managedMissing,
+			(s, v) => s.setManagedMissing(v),
+			false,
+			true
+		);
+		/** @type {StackedCacheMap} */
+		this._fileTimestamps = new StackedCacheMap();
+		/** @type {Map} */
+		this._fileHashes = new Map();
+		/** @type {Map} */
+		this._fileTshs = new Map();
+		/** @type {StackedCacheMap} */
+		this._contextTimestamps = new StackedCacheMap();
+		/** @type {Map} */
+		this._contextHashes = new Map();
+		/** @type {Map} */
+		this._contextTshs = new Map();
+		/** @type {Map} */
+		this._managedItems = new Map();
+		// memoized checkManaged results: 0 = not managed, 1 = immutable, string = managed item
+		/** @type {Map} */
+		this._pathClassificationCache = new Map();
+		/** @type {AsyncQueue} */
+		this.fileTimestampQueue = new AsyncQueue({
+			name: "file timestamp",
+			parallelism: 30,
+			processor: this._readFileTimestamp.bind(this)
+		});
+		/** @type {AsyncQueue} */
+		this.fileHashQueue = new AsyncQueue({
+			name: "file hash",
+			parallelism: 10,
+			processor: this._readFileHash.bind(this)
+		});
+		/** @type {AsyncQueue} */
+		this.contextTimestampQueue = new AsyncQueue({
+			name: "context timestamp",
+			parallelism: 2,
+			processor: this._readContextTimestamp.bind(this)
+		});
+		/** @type {AsyncQueue} */
+		this.contextHashQueue = new AsyncQueue({
+			name: "context hash",
+			parallelism: 2,
+			processor: this._readContextHash.bind(this)
+		});
+		/** @type {AsyncQueue} */
+		this.contextTshQueue = new AsyncQueue({
+			name: "context hash and timestamp",
+			parallelism: 2,
+			processor: this._readContextTimestampAndHash.bind(this)
+		});
+		/** @type {AsyncQueue} */
+		this.managedItemQueue = new AsyncQueue({
+			name: "managed item info",
+			parallelism: 10,
+			processor: this._getManagedItemInfo.bind(this)
+		});
+		/** @type {AsyncQueue>} */
+		this.managedItemDirectoryQueue = new AsyncQueue({
+			name: "managed item directory info",
+			parallelism: 10,
+			processor: this._getManagedItemDirectoryInfo.bind(this)
+		});
+		const _unmanagedPaths = [...unmanagedPaths];
+		/** @type {string[]} */
+		this.unmanagedPathsWithSlash = _unmanagedPaths
+			.filter((p) => typeof p === "string")
+			.map((p) => join(fs, p, "_").slice(0, -1));
+		/** @type {RegExp[]} */
+		this.unmanagedPathsRegExps = _unmanagedPaths.filter(
+			(p) => typeof p !== "string"
+		);
+
+		this.managedPaths = [...managedPaths];
+		/** @type {string[]} */
+		this.managedPathsWithSlash = this.managedPaths
+			.filter((p) => typeof p === "string")
+			.map((p) => join(fs, p, "_").slice(0, -1));
+		/** @type {RegExp[]} */
+		this.managedPathsRegExps = this.managedPaths.filter(
+			(p) => typeof p !== "string"
+		);
+
+		this.immutablePaths = [...immutablePaths];
+		/** @type {string[]} */
+		this.immutablePathsWithSlash = this.immutablePaths
+			.filter((p) => typeof p === "string")
+			.map((p) => join(fs, p, "_").slice(0, -1));
+		/** @type {RegExp[]} */
+		this.immutablePathsRegExps = this.immutablePaths.filter(
+			(p) => typeof p !== "string"
+		);
+
+		/** @type {Map | undefined} */
+		this._cachedDeprecatedFileTimestamps = undefined;
+		/** @type {Map | undefined} */
+		this._cachedDeprecatedContextTimestamps = undefined;
+
+		/** @type {boolean} */
+		this._warnAboutExperimentalEsmTracking = false;
+
+		/** @type {number} */
+		this._statCreatedSnapshots = 0;
+		/** @type {number} */
+		this._statTestedSnapshotsCached = 0;
+		/** @type {number} */
+		this._statTestedSnapshotsNotCached = 0;
+		/** @type {number} */
+		this._statTestedChildrenCached = 0;
+		/** @type {number} */
+		this._statTestedChildrenNotCached = 0;
+		/** @type {number} */
+		this._statTestedEntries = 0;
+	}
+
+	logStatistics() {
+		const logger = /** @type {Logger} */ (this.logger);
+		/**
+		 * Processes the provided header.
+		 * @param {string} header header
+		 * @param {string | undefined} message message
+		 */
+		const logWhenMessage = (header, message) => {
+			if (message) {
+				logger.log(`${header}: ${message}`);
+			}
+		};
+		logger.log(`${this._statCreatedSnapshots} new snapshots created`);
+		logger.log(
+			`${
+				this._statTestedSnapshotsNotCached &&
+				Math.round(
+					(this._statTestedSnapshotsNotCached * 100) /
+						(this._statTestedSnapshotsCached +
+							this._statTestedSnapshotsNotCached)
+				)
+			}% root snapshot uncached (${this._statTestedSnapshotsNotCached} / ${
+				this._statTestedSnapshotsCached + this._statTestedSnapshotsNotCached
+			})`
+		);
+		logger.log(
+			`${
+				this._statTestedChildrenNotCached &&
+				Math.round(
+					(this._statTestedChildrenNotCached * 100) /
+						(this._statTestedChildrenCached + this._statTestedChildrenNotCached)
+				)
+			}% children snapshot uncached (${this._statTestedChildrenNotCached} / ${
+				this._statTestedChildrenCached + this._statTestedChildrenNotCached
+			})`
+		);
+		logger.log(`${this._statTestedEntries} entries tested`);
+		logger.log(
+			`File info in cache: ${this._fileTimestamps.size} timestamps ${this._fileHashes.size} hashes ${this._fileTshs.size} timestamp hash combinations`
+		);
+		logWhenMessage(
+			"File timestamp snapshot optimization",
+			this._fileTimestampsOptimization.getStatisticMessage()
+		);
+		logWhenMessage(
+			"File hash snapshot optimization",
+			this._fileHashesOptimization.getStatisticMessage()
+		);
+		logWhenMessage(
+			"File timestamp hash combination snapshot optimization",
+			this._fileTshsOptimization.getStatisticMessage()
+		);
+		logger.log(
+			`Directory info in cache: ${this._contextTimestamps.size} timestamps ${this._contextHashes.size} hashes ${this._contextTshs.size} timestamp hash combinations`
+		);
+		logWhenMessage(
+			"Directory timestamp snapshot optimization",
+			this._contextTimestampsOptimization.getStatisticMessage()
+		);
+		logWhenMessage(
+			"Directory hash snapshot optimization",
+			this._contextHashesOptimization.getStatisticMessage()
+		);
+		logWhenMessage(
+			"Directory timestamp hash combination snapshot optimization",
+			this._contextTshsOptimization.getStatisticMessage()
+		);
+		logWhenMessage(
+			"Missing items snapshot optimization",
+			this._missingExistenceOptimization.getStatisticMessage()
+		);
+		logger.log(`Managed items info in cache: ${this._managedItems.size} items`);
+		logWhenMessage(
+			"Managed items snapshot optimization",
+			this._managedItemInfoOptimization.getStatisticMessage()
+		);
+		logWhenMessage(
+			"Managed files snapshot optimization",
+			this._managedFilesOptimization.getStatisticMessage()
+		);
+		logWhenMessage(
+			"Managed contexts snapshot optimization",
+			this._managedContextsOptimization.getStatisticMessage()
+		);
+		logWhenMessage(
+			"Managed missing snapshot optimization",
+			this._managedMissingOptimization.getStatisticMessage()
+		);
+	}
+
+	/**
+	 * Processes the provided path.
+	 * @private
+	 * @param {string} path path
+	 * @param {string} reason reason
+	 * @param {EXPECTED_ANY[]} args arguments
+	 */
+	_log(path, reason, ...args) {
+		const key = path + reason;
+		const loggedPaths = /** @type {LoggedPaths} */ (this._loggedPaths);
+		if (loggedPaths.has(key)) return;
+		loggedPaths.add(key);
+		/** @type {Logger} */
+		(this.logger).debug(`${path} invalidated because ${reason}`, ...args);
+		if (--this._remainingLogs === 0) {
+			/** @type {Logger} */
+			(this.logger).debug(
+				"Logging limit has been reached and no further logging will be emitted by FileSystemInfo"
+			);
+		}
+	}
+
+	clear() {
+		this._remainingLogs = this.logger ? 40 : 0;
+		if (this._loggedPaths !== undefined) this._loggedPaths.clear();
+
+		this._snapshotCache = new WeakMap();
+		this._fileTimestampsOptimization.clear();
+		this._fileHashesOptimization.clear();
+		this._fileTshsOptimization.clear();
+		this._contextTimestampsOptimization.clear();
+		this._contextHashesOptimization.clear();
+		this._contextTshsOptimization.clear();
+		this._missingExistenceOptimization.clear();
+		this._managedItemInfoOptimization.clear();
+		this._managedFilesOptimization.clear();
+		this._managedContextsOptimization.clear();
+		this._managedMissingOptimization.clear();
+		this._fileTimestamps.clear();
+		this._fileHashes.clear();
+		this._fileTshs.clear();
+		this._contextTimestamps.clear();
+		this._contextHashes.clear();
+		this._contextTshs.clear();
+		this._managedItems.clear();
+		this._pathClassificationCache.clear();
+
+		this._cachedDeprecatedFileTimestamps = undefined;
+		this._cachedDeprecatedContextTimestamps = undefined;
+
+		this._statCreatedSnapshots = 0;
+		this._statTestedSnapshotsCached = 0;
+		this._statTestedSnapshotsNotCached = 0;
+		this._statTestedChildrenCached = 0;
+		this._statTestedChildrenNotCached = 0;
+		this._statTestedEntries = 0;
+	}
+
+	/**
+	 * Adds file timestamps.
+	 * @param {ReadonlyMap} map timestamps
+	 * @param {boolean=} immutable if 'map' is immutable and FileSystemInfo can keep referencing it
+	 * @returns {void}
+	 */
+	addFileTimestamps(map, immutable) {
+		this._fileTimestamps.addAll(map, immutable);
+		this._cachedDeprecatedFileTimestamps = undefined;
+	}
+
+	/**
+	 * Adds context timestamps.
+	 * @param {ReadonlyMap} map timestamps
+	 * @param {boolean=} immutable if 'map' is immutable and FileSystemInfo can keep referencing it
+	 * @returns {void}
+	 */
+	addContextTimestamps(map, immutable) {
+		this._contextTimestamps.addAll(map, immutable);
+		this._cachedDeprecatedContextTimestamps = undefined;
+	}
+
+	/**
+	 * Gets file timestamp.
+	 * @param {string} path file path
+	 * @param {(err?: WebpackError | null, fileTimestamp?: FileSystemInfoEntry | "ignore" | null) => void} callback callback function
+	 * @returns {void}
+	 */
+	getFileTimestamp(path, callback) {
+		const cache = this._fileTimestamps.get(path);
+		if (cache !== undefined && !isExistenceOnly(cache)) {
+			return callback(
+				null,
+				/** @type {FileSystemInfoEntry | "ignore" | null} */ (cache)
+			);
+		}
+		this.fileTimestampQueue.add(path, callback);
+	}
+
+	/**
+	 * Gets context timestamp.
+	 * @param {string} path context path
+	 * @param {(err?: WebpackError | null, resolvedContextTimestamp?: ResolvedContextTimestamp) => void} callback callback function
+	 * @returns {void}
+	 */
+	getContextTimestamp(path, callback) {
+		const cache = this._contextTimestamps.get(path);
+		if (cache !== undefined && !isExistenceOnly(cache)) {
+			if (cache === "ignore") return callback(null, "ignore");
+			const fullEntry =
+				/** @type {ContextFileSystemInfoEntry | null} */
+				(cache);
+			const resolved = getResolvedTimestamp(fullEntry);
+			if (resolved !== undefined) return callback(null, resolved);
+			return this._resolveContextTimestamp(
+				/** @type {ContextFileSystemInfoEntry} */
+				(fullEntry),
+				callback
+			);
+		}
+		this._readFreshContextTimestamp(path, callback);
+	}
+
+	/**
+	 * Reads a context timestamp directly from disk, bypassing any cached
+	 * entry. Used by `getContextTimestamp` and the snapshot validity
+	 * checks when the cached entry is missing or is an `ExistenceOnlyTimeEntry`
+	 * (`{}`) supplied by watchpack — both cases require a fresh read to
+	 * obtain the `timestampHash`.
+	 * @private
+	 * @param {string} path context path
+	 * @param {(err?: WebpackError | null, resolvedContextTimestamp?: ResolvedContextTimestamp) => void} callback callback function
+	 * @returns {void}
+	 */
+	_readFreshContextTimestamp(path, callback) {
+		this.contextTimestampQueue.add(path, (err, _entry) => {
+			if (err) return callback(err);
+			const entry = /** @type {ContextFileSystemInfoEntry | null} */ (_entry);
+			if (entry === null) return callback(null, null);
+			const resolved = getResolvedTimestamp(entry);
+			if (resolved !== undefined) return callback(null, resolved);
+			this._resolveContextTimestamp(entry, callback);
+		});
+	}
+
+	/**
+	 * Get unresolved context timestamp. Existence-only cache entries (`{}`)
+	 * are bypassed so the callback always receives a complete entry, "ignore"
+	 * or null.
+	 * @private
+	 * @param {string} path context path
+	 * @param {(err?: WebpackError | null, contextTimestamp?: ContextFileSystemInfoEntry | "ignore" | null) => void} callback callback function
+	 * @returns {void}
+	 */
+	_getUnresolvedContextTimestamp(path, callback) {
+		const cache = this._contextTimestamps.get(path);
+		if (
+			cache !== undefined &&
+			!isExistenceOnly(cache) &&
+			!contextCacheEntryLacksTimestampHash(
+				/** @type {ContextFileSystemInfoEntry | null | "ignore"} */ (cache)
+			)
+		) {
+			return callback(
+				null,
+				/** @type {ContextFileSystemInfoEntry | "ignore" | null} */ (cache)
+			);
+		}
+		this.contextTimestampQueue.add(path, callback);
+	}
+
+	/**
+	 * Returns file hash.
+	 * @param {string} path file path
+	 * @param {(err?: WebpackError | null, hash?: string | null) => void} callback callback function
+	 * @returns {void}
+	 */
+	getFileHash(path, callback) {
+		const cache = this._fileHashes.get(path);
+		if (cache !== undefined) return callback(null, cache);
+		this.fileHashQueue.add(path, callback);
+	}
+
+	/**
+	 * Returns context hash.
+	 * @param {string} path context path
+	 * @param {(err?: WebpackError | null, contextHash?: string) => void} callback callback function
+	 * @returns {void}
+	 */
+	getContextHash(path, callback) {
+		const cache = this._contextHashes.get(path);
+		if (cache !== undefined) {
+			const resolved = getResolvedHash(cache);
+			if (resolved !== undefined) {
+				return callback(null, /** @type {string} */ (resolved));
+			}
+			return this._resolveContextHash(cache, callback);
+		}
+		this.contextHashQueue.add(path, (err, _entry) => {
+			if (err) return callback(err);
+			const entry = /** @type {ContextHash} */ (_entry);
+			const resolved = getResolvedHash(entry);
+			if (resolved !== undefined) {
+				return callback(null, /** @type {string} */ (resolved));
+			}
+			this._resolveContextHash(entry, callback);
+		});
+	}
+
+	/**
+	 * Get unresolved context hash.
+	 * @private
+	 * @param {string} path context path
+	 * @param {(err?: WebpackError | null, contextHash?: ContextHash | null) => void} callback callback function
+	 * @returns {void}
+	 */
+	_getUnresolvedContextHash(path, callback) {
+		const cache = this._contextHashes.get(path);
+		if (cache !== undefined) return callback(null, cache);
+		this.contextHashQueue.add(path, callback);
+	}
+
+	/**
+	 * Returns context tsh.
+	 * @param {string} path context path
+	 * @param {(err?: WebpackError | null, resolvedContextTimestampAndHash?: ResolvedContextTimestampAndHash | null) => void} callback callback function
+	 * @returns {void}
+	 */
+	getContextTsh(path, callback) {
+		const cache = this._contextTshs.get(path);
+		if (cache !== undefined) {
+			const resolved = getResolvedTimestamp(cache);
+			if (resolved !== undefined) return callback(null, resolved);
+			return this._resolveContextTsh(cache, callback);
+		}
+		this.contextTshQueue.add(path, (err, _entry) => {
+			if (err) return callback(err);
+			const entry = /** @type {ContextTimestampAndHash} */ (_entry);
+			const resolved = getResolvedTimestamp(entry);
+			if (resolved !== undefined) return callback(null, resolved);
+			this._resolveContextTsh(entry, callback);
+		});
+	}
+
+	/**
+	 * Get unresolved context tsh.
+	 * @private
+	 * @param {string} path context path
+	 * @param {(err?: WebpackError | null, contextTimestampAndHash?: ContextTimestampAndHash | null) => void} callback callback function
+	 * @returns {void}
+	 */
+	_getUnresolvedContextTsh(path, callback) {
+		const cache = this._contextTshs.get(path);
+		if (cache !== undefined) return callback(null, cache);
+		this.contextTshQueue.add(path, callback);
+	}
+
+	_createBuildDependenciesResolvers() {
+		const resolveContext = createResolver({
+			resolveToContext: true,
+			exportsFields: [],
+			fileSystem: this.fs
+		});
+		const resolveCjs = createResolver({
+			extensions: [".js", ".json", ".node"],
+			conditionNames: ["require", "module-sync", "node"],
+			exportsFields: ["exports"],
+			fileSystem: this.fs
+		});
+		const resolveCjsAsChild = createResolver({
+			extensions: [".js", ".json", ".node"],
+			conditionNames: ["require", "module-sync", "node"],
+			exportsFields: [],
+			fileSystem: this.fs
+		});
+		const resolveEsm = createResolver({
+			extensions: [".js", ".json", ".node"],
+			fullySpecified: true,
+			conditionNames: ["import", "module-sync", "node"],
+			exportsFields: ["exports"],
+			fileSystem: this.fs
+		});
+		return { resolveContext, resolveEsm, resolveCjs, resolveCjsAsChild };
+	}
+
+	/**
+	 * Resolves build dependencies.
+	 * @param {string} context context directory
+	 * @param {Iterable} deps dependencies
+	 * @param {(err?: Error | null, resolveBuildDependenciesResult?: ResolveBuildDependenciesResult) => void} callback callback function
+	 * @returns {void}
+	 */
+	resolveBuildDependencies(context, deps, callback) {
+		const { resolveContext, resolveEsm, resolveCjs, resolveCjsAsChild } =
+			this._createBuildDependenciesResolvers();
+
+		/** @type {Files} */
+		const files = new Set();
+		/** @type {Symlinks} */
+		const fileSymlinks = new Set();
+		/** @type {Directories} */
+		const directories = new Set();
+		/** @type {Symlinks} */
+		const directorySymlinks = new Set();
+		/** @type {Missing} */
+		const missing = new Set();
+		/** @type {ResolveDependencies["files"]} */
+		const resolveFiles = new Set();
+		/** @type {ResolveDependencies["directories"]} */
+		const resolveDirectories = new Set();
+		/** @type {ResolveDependencies["missing"]} */
+		const resolveMissing = new Set();
+		/** @type {ResolveResults} */
+		const resolveResults = new Map();
+		/** @type {Set} */
+		const invalidResolveResults = new Set();
+		const resolverContext = {
+			fileDependencies: resolveFiles,
+			contextDependencies: resolveDirectories,
+			missingDependencies: resolveMissing
+		};
+		/**
+		 * Expected to string.
+		 * @param {undefined | boolean | string} expected expected result
+		 * @returns {string} expected result
+		 */
+		const expectedToString = (expected) =>
+			expected ? ` (expected ${expected})` : "";
+		/** @typedef {{ type: JobType, context: string | undefined, path: string, issuer: Job | undefined, expected: undefined | boolean | string }} Job */
+
+		/**
+		 * Returns result.
+		 * @param {Job} job job
+		 * @returns {string} result
+		 */
+		const jobToString = (job) => {
+			switch (job.type) {
+				case RBDT_RESOLVE_FILE:
+					return `resolve file ${job.path}${expectedToString(job.expected)}`;
+				case RBDT_RESOLVE_DIRECTORY:
+					return `resolve directory ${job.path}`;
+				case RBDT_RESOLVE_CJS_FILE:
+					return `resolve commonjs file ${job.path}${expectedToString(
+						job.expected
+					)}`;
+				case RBDT_RESOLVE_ESM_FILE:
+					return `resolve esm file ${job.path}${expectedToString(
+						job.expected
+					)}`;
+				case RBDT_DIRECTORY:
+					return `directory ${job.path}`;
+				case RBDT_FILE:
+					return `file ${job.path}`;
+				case RBDT_DIRECTORY_DEPENDENCIES:
+					return `directory dependencies ${job.path}`;
+				case RBDT_FILE_DEPENDENCIES:
+					return `file dependencies ${job.path}`;
+			}
+			return `unknown ${job.type} ${job.path}`;
+		};
+		/**
+		 * Returns string value.
+		 * @param {Job} job job
+		 * @returns {string} string value
+		 */
+		const pathToString = (job) => {
+			let result = ` at ${jobToString(job)}`;
+			/** @type {Job | undefined} */
+			(job) = job.issuer;
+			while (job !== undefined) {
+				result += `\n at ${jobToString(job)}`;
+				job = /** @type {Job} */ (job.issuer);
+			}
+			return result;
+		};
+		const logger = /** @type {Logger} */ (this.logger);
+		processAsyncTree(
+			Array.from(
+				deps,
+				(dep) =>
+					/** @type {Job} */ ({
+						type: RBDT_RESOLVE_INITIAL,
+						context,
+						path: dep,
+						expected: undefined,
+						issuer: undefined
+					})
+			),
+			20,
+			(job, push, callback) => {
+				const { type, context, path, expected } = job;
+				/**
+				 * Resolves directory.
+				 * @param {string} path path
+				 * @returns {void}
+				 */
+				const resolveDirectory = (path) => {
+					const key = `d\n${context}\n${path}`;
+					if (resolveResults.has(key)) {
+						return callback();
+					}
+					resolveResults.set(key, undefined);
+					resolveContext(
+						/** @type {string} */ (context),
+						path,
+						resolverContext,
+						(err, _, result) => {
+							if (err) {
+								if (expected === false) {
+									resolveResults.set(key, false);
+									return callback();
+								}
+								invalidResolveResults.add(key);
+								err.message += `\nwhile resolving '${path}' in ${context} to a directory`;
+								return callback(err);
+							}
+							const resultPath = /** @type {ResolveRequest} */ (result).path;
+							resolveResults.set(key, resultPath);
+							push({
+								type: RBDT_DIRECTORY,
+								context: undefined,
+								path: /** @type {string} */ (resultPath),
+								expected: undefined,
+								issuer: job
+							});
+							callback();
+						}
+					);
+				};
+				/**
+				 * Processes the provided path.
+				 * @param {string} path path
+				 * @param {("f" | "c" | "e")=} symbol symbol
+				 * @param {(ResolveFunctionAsync)=} resolve resolve fn
+				 * @returns {void}
+				 */
+				const resolveFile = (path, symbol, resolve) => {
+					const key = `${symbol}\n${context}\n${path}`;
+					if (resolveResults.has(key)) {
+						return callback();
+					}
+					resolveResults.set(key, undefined);
+					/** @type {ResolveFunctionAsync} */
+					(resolve)(
+						/** @type {string} */ (context),
+						path,
+						resolverContext,
+						(err, _, result) => {
+							if (typeof expected === "string") {
+								if (!err && result && result.path === expected) {
+									resolveResults.set(key, result.path);
+								} else {
+									invalidResolveResults.add(key);
+									logger.warn(
+										`Resolving '${path}' in ${context} for build dependencies doesn't lead to expected result '${expected}', but to '${
+											err || (result && result.path)
+										}' instead. Resolving dependencies are ignored for this path.\n${pathToString(
+											job
+										)}`
+									);
+								}
+							} else {
+								if (err) {
+									if (expected === false) {
+										resolveResults.set(key, false);
+										return callback();
+									}
+									invalidResolveResults.add(key);
+									err.message += `\nwhile resolving '${path}' in ${context} as file\n${pathToString(
+										job
+									)}`;
+									return callback(err);
+								}
+								const resultPath = /** @type {ResolveRequest} */ (result).path;
+								resolveResults.set(key, resultPath);
+								push({
+									type: RBDT_FILE,
+									context: undefined,
+									path: /** @type {string} */ (resultPath),
+									expected: undefined,
+									issuer: job
+								});
+							}
+							callback();
+						}
+					);
+				};
+				const resolvedType =
+					type === RBDT_RESOLVE_INITIAL
+						? /[\\/]$/.test(path)
+							? RBDT_RESOLVE_DIRECTORY
+							: RBDT_RESOLVE_FILE
+						: type;
+				switch (resolvedType) {
+					case RBDT_RESOLVE_FILE: {
+						resolveFile(
+							path,
+							"f",
+							/\.mjs$/.test(path) ? resolveEsm : resolveCjs
+						);
+						break;
+					}
+					case RBDT_RESOLVE_DIRECTORY: {
+						resolveDirectory(
+							type === RBDT_RESOLVE_INITIAL ? path.slice(0, -1) : path
+						);
+						break;
+					}
+					case RBDT_RESOLVE_CJS_FILE: {
+						resolveFile(path, "f", resolveCjs);
+						break;
+					}
+					case RBDT_RESOLVE_CJS_FILE_AS_CHILD: {
+						resolveFile(path, "c", resolveCjsAsChild);
+						break;
+					}
+					case RBDT_RESOLVE_ESM_FILE: {
+						resolveFile(path, "e", resolveEsm);
+						break;
+					}
+					case RBDT_FILE: {
+						if (files.has(path)) {
+							callback();
+							break;
+						}
+						files.add(path);
+						/** @type {NonNullable} */
+						(this.fs.realpath)(path, (err, _realPath) => {
+							if (err) return callback(err);
+							const realPath = /** @type {string} */ (_realPath);
+							if (realPath !== path) {
+								fileSymlinks.add(path);
+								resolveFiles.add(path);
+								if (files.has(realPath)) return callback();
+								files.add(realPath);
+							}
+							push({
+								type: RBDT_FILE_DEPENDENCIES,
+								context: undefined,
+								path: realPath,
+								expected: undefined,
+								issuer: job
+							});
+							callback();
+						});
+						break;
+					}
+					case RBDT_DIRECTORY: {
+						if (directories.has(path)) {
+							callback();
+							break;
+						}
+						directories.add(path);
+						/** @type {NonNullable} */
+						(this.fs.realpath)(path, (err, _realPath) => {
+							if (err) return callback(err);
+							const realPath = /** @type {string} */ (_realPath);
+							if (realPath !== path) {
+								directorySymlinks.add(path);
+								resolveFiles.add(path);
+								if (directories.has(realPath)) return callback();
+								directories.add(realPath);
+							}
+							push({
+								type: RBDT_DIRECTORY_DEPENDENCIES,
+								context: undefined,
+								path: realPath,
+								expected: undefined,
+								issuer: job
+							});
+							callback();
+						});
+						break;
+					}
+					case RBDT_FILE_DEPENDENCIES: {
+						// Check for known files without dependencies
+						if (/\.json5?$|\.yarn-integrity$|yarn\.lock$|\.ya?ml/.test(path)) {
+							process.nextTick(callback);
+							break;
+						}
+						// Check commonjs cache for the module
+						/** @type {NodeModule | undefined} */
+						const module = require.cache[path];
+						if (
+							module &&
+							Array.isArray(module.children) &&
+							// https://github.com/nodejs/node/issues/59868
+							// Force use `es-module-lexer` for mjs
+							!/\.mjs$/.test(path)
+						) {
+							children: for (const child of module.children) {
+								const childPath = child.filename;
+								if (childPath) {
+									push({
+										type: RBDT_FILE,
+										context: undefined,
+										path: childPath,
+										expected: undefined,
+										issuer: job
+									});
+									const context = dirname(this.fs, path);
+									for (const modulePath of module.paths) {
+										if (childPath.startsWith(modulePath)) {
+											const subPath = childPath.slice(modulePath.length + 1);
+											const packageMatch = /^@[^\\/]+[\\/][^\\/]+/.exec(
+												subPath
+											);
+											if (packageMatch) {
+												push({
+													type: RBDT_FILE,
+													context: undefined,
+													path: `${
+														modulePath +
+														childPath[modulePath.length] +
+														packageMatch[0] +
+														childPath[modulePath.length]
+													}package.json`,
+													expected: false,
+													issuer: job
+												});
+											}
+											let request = subPath.replace(/\\/g, "/");
+											if (request.endsWith(".js")) {
+												request = request.slice(0, -3);
+											}
+											push({
+												type: RBDT_RESOLVE_CJS_FILE_AS_CHILD,
+												context,
+												path: request,
+												expected: child.filename,
+												issuer: job
+											});
+											continue children;
+										}
+									}
+									let request = relative(this.fs, context, childPath);
+									if (request.endsWith(".js")) request = request.slice(0, -3);
+									request = request.replace(/\\/g, "/");
+									if (!request.startsWith("../") && !isAbsolute(request)) {
+										request = `./${request}`;
+									}
+									push({
+										type: RBDT_RESOLVE_CJS_FILE,
+										context,
+										path: request,
+										expected: child.filename,
+										issuer: job
+									});
+								}
+							}
+							// On Bun the children array is empty (unpopulated), so recover the
+							// static require() specifiers by parsing the source; Node/Deno don't.
+							/* istanbul ignore next */
+							if (IS_BUN && module.children.length === 0) {
+								this.fs.readFile(path, (err, content) => {
+									if (err) return callback(err);
+									try {
+										const context = dirname(this.fs, path);
+										const source = /** @type {Buffer} */ (content).toString();
+										for (const dependency of collectCjsRequireSpecifiers(
+											source
+										)) {
+											if (dependency.startsWith("node:")) continue;
+											if (builtinModules.has(dependency)) continue;
+											push({
+												type: RBDT_RESOLVE_CJS_FILE,
+												context,
+												path: dependency,
+												// Best effort: tolerate specifiers that don't resolve.
+												expected: false,
+												issuer: job
+											});
+										}
+									} catch (_err) {
+										// Source we can't parse as CommonJS — ignore its dependencies.
+									}
+									process.nextTick(callback);
+								});
+								break;
+							}
+						} else if (supportsEsm && /\.m?js$/.test(path)) {
+							if (!this._warnAboutExperimentalEsmTracking) {
+								logger.log(
+									"Node.js doesn't offer a (nice) way to introspect the ESM dependency graph yet.\n" +
+										"Until a full solution is available webpack uses an experimental ESM tracking based on parsing.\n" +
+										"As best effort webpack parses the ESM files to guess dependencies. But this can lead to expensive and incorrect tracking."
+								);
+								this._warnAboutExperimentalEsmTracking = true;
+							}
+
+							const lexer = getEsModuleLexer();
+
+							lexer.init.then(() => {
+								this.fs.readFile(path, (err, content) => {
+									if (err) return callback(err);
+									try {
+										const context = dirname(this.fs, path);
+										const source = /** @type {Buffer} */ (content).toString();
+										const [imports] = lexer.parse(source);
+										/** @type {Set} */
+										const added = new Set();
+										for (const imp of imports) {
+											try {
+												// import.meta
+												if (imp.d === -2) {
+													continue;
+												}
+
+												/** @type {string | null} */
+												const dependency =
+													imp.n ||
+													parseString(source.slice(imp.s, imp.e).trim());
+
+												if (!dependency) {
+													continue;
+												}
+
+												// We should not track Node.js build dependencies
+												if (dependency.startsWith("node:")) continue;
+												if (builtinModules.has(dependency)) continue;
+												// Avoid extra jobs for identical imports
+												if (added.has(dependency)) continue;
+
+												push({
+													type: RBDT_RESOLVE_ESM_FILE,
+													context,
+													path: dependency,
+													expected: imp.d > -1 ? false : undefined,
+													issuer: job
+												});
+												added.add(dependency);
+											} catch (err1) {
+												logger.warn(
+													`Parsing of ${path} for build dependencies failed at 'import(${source.slice(
+														imp.s,
+														imp.e
+													)})'.\n` +
+														"Build dependencies behind this expression are ignored and might cause incorrect cache invalidation."
+												);
+												logger.debug(pathToString(job));
+												logger.debug(/** @type {Error} */ (err1).stack);
+											}
+										}
+									} catch (err2) {
+										logger.warn(
+											`Parsing of ${path} for build dependencies failed and all dependencies of this file are ignored, which might cause incorrect cache invalidation..`
+										);
+										logger.debug(pathToString(job));
+										logger.debug(/** @type {Error} */ (err2).stack);
+									}
+									process.nextTick(callback);
+								});
+							}, callback);
+							break;
+						} else {
+							logger.log(
+								`Assuming ${path} has no dependencies as we were unable to assign it to any module system.`
+							);
+							logger.debug(pathToString(job));
+						}
+						process.nextTick(callback);
+						break;
+					}
+					case RBDT_DIRECTORY_DEPENDENCIES: {
+						const match =
+							/(^.+[\\/]node_modules[\\/](?:@[^\\/]+[\\/])?[^\\/]+)/.exec(path);
+						const packagePath = match ? match[1] : path;
+						const packageJson = join(this.fs, packagePath, "package.json");
+						this.fs.readFile(packageJson, (err, content) => {
+							if (err) {
+								if (err.code === "ENOENT") {
+									resolveMissing.add(packageJson);
+									const parent = dirname(this.fs, packagePath);
+									if (parent !== packagePath) {
+										push({
+											type: RBDT_DIRECTORY_DEPENDENCIES,
+											context: undefined,
+											path: parent,
+											expected: undefined,
+											issuer: job
+										});
+									}
+									callback();
+									return;
+								}
+								return callback(err);
+							}
+							resolveFiles.add(packageJson);
+							/** @type {JsonObject} */
+							let packageData;
+							try {
+								packageData = JSON.parse(
+									/** @type {Buffer} */
+									(content).toString("utf8")
+								);
+							} catch (parseErr) {
+								return callback(/** @type {Error} */ (parseErr));
+							}
+							const depsObject = packageData.dependencies;
+							const optionalDepsObject = packageData.optionalDependencies;
+							/** @type {Set} */
+							const allDeps = new Set();
+							/** @type {Set} */
+							const optionalDeps = new Set();
+							if (typeof depsObject === "object" && depsObject) {
+								for (const dep of Object.keys(depsObject)) {
+									allDeps.add(dep);
+								}
+							}
+							if (
+								typeof optionalDepsObject === "object" &&
+								optionalDepsObject
+							) {
+								for (const dep of Object.keys(optionalDepsObject)) {
+									allDeps.add(dep);
+									optionalDeps.add(dep);
+								}
+							}
+							for (const dep of allDeps) {
+								push({
+									type: RBDT_RESOLVE_DIRECTORY,
+									context: packagePath,
+									path: dep,
+									expected: !optionalDeps.has(dep),
+									issuer: job
+								});
+							}
+							callback();
+						});
+						break;
+					}
+				}
+			},
+			(err) => {
+				if (err) return callback(err);
+				for (const l of fileSymlinks) files.delete(l);
+				for (const l of directorySymlinks) directories.delete(l);
+				for (const k of invalidResolveResults) resolveResults.delete(k);
+				callback(null, {
+					files,
+					directories,
+					missing,
+					resolveResults,
+					resolveDependencies: {
+						files: resolveFiles,
+						directories: resolveDirectories,
+						missing: resolveMissing
+					}
+				});
+			}
+		);
+	}
+
+	/**
+	 * Checks resolve results valid.
+	 * @param {ResolveResults} resolveResults results from resolving
+	 * @param {(err?: Error | null, result?: boolean) => void} callback callback with true when resolveResults resolve the same way
+	 * @returns {void}
+	 */
+	checkResolveResultsValid(resolveResults, callback) {
+		const { resolveCjs, resolveCjsAsChild, resolveEsm, resolveContext } =
+			this._createBuildDependenciesResolvers();
+		asyncLib.eachLimit(
+			resolveResults,
+			20,
+			([key, expectedResult], callback) => {
+				const [type, context, path] = key.split("\n");
+				switch (type) {
+					case "d":
+						resolveContext(context, path, {}, (err, _, result) => {
+							if (expectedResult === false) {
+								return callback(err ? undefined : INVALID);
+							}
+							if (err) return callback(err);
+							const resultPath = /** @type {ResolveRequest} */ (result).path;
+							if (resultPath !== expectedResult) return callback(INVALID);
+							callback();
+						});
+						break;
+					case "f":
+						resolveCjs(context, path, {}, (err, _, result) => {
+							if (expectedResult === false) {
+								return callback(err ? undefined : INVALID);
+							}
+							if (err) return callback(err);
+							const resultPath = /** @type {ResolveRequest} */ (result).path;
+							if (resultPath !== expectedResult) return callback(INVALID);
+							callback();
+						});
+						break;
+					case "c":
+						resolveCjsAsChild(context, path, {}, (err, _, result) => {
+							if (expectedResult === false) {
+								return callback(err ? undefined : INVALID);
+							}
+							if (err) return callback(err);
+							const resultPath = /** @type {ResolveRequest} */ (result).path;
+							if (resultPath !== expectedResult) return callback(INVALID);
+							callback();
+						});
+						break;
+					case "e":
+						resolveEsm(context, path, {}, (err, _, result) => {
+							if (expectedResult === false) {
+								return callback(err ? undefined : INVALID);
+							}
+							if (err) return callback(err);
+							const resultPath = /** @type {ResolveRequest} */ (result).path;
+							if (resultPath !== expectedResult) return callback(INVALID);
+							callback();
+						});
+						break;
+					default:
+						callback(new Error("Unexpected type in resolve result key"));
+						break;
+				}
+			},
+			/**
+			 * Processes the provided err.
+			 * @param {Error | typeof INVALID=} err error or invalid flag
+			 * @returns {void}
+			 */
+			/** @type {import("neo-async").ErrorCallback} */ (
+				(err) => {
+					if (err === INVALID) {
+						return callback(null, false);
+					}
+					if (err) {
+						return callback(err);
+					}
+					return callback(null, true);
+				}
+			)
+		);
+	}
+
+	/**
+	 * Creates a snapshot.
+	 * @param {number | null | undefined} startTime when processing the files has started
+	 * @param {Iterable | null | undefined} files all files
+	 * @param {Iterable | null | undefined} directories all directories
+	 * @param {Iterable | null | undefined} missing all missing files or directories
+	 * @param {SnapshotOptions | null | undefined} options options object (for future extensions)
+	 * @param {(err: WebpackError | null, snapshot: Snapshot | null) => void} callback callback function
+	 * @returns {void}
+	 */
+	createSnapshot(startTime, files, directories, missing, options, callback) {
+		// collections are allocated lazily to avoid per-snapshot garbage for unused modes
+		/** @type {FileTimestamps | undefined} */
+		let fileTimestamps;
+		/** @type {FileHashes | undefined} */
+		let fileHashes;
+		/** @type {FileTshs | undefined} */
+		let fileTshs;
+		/** @type {ContextTimestamps | undefined} */
+		let contextTimestamps;
+		/** @type {ContextHashes | undefined} */
+		let contextHashes;
+		/** @type {ContextTshs | undefined} */
+		let contextTshs;
+		/** @type {MissingExistence | undefined} */
+		let missingExistence;
+		/** @type {ManagedItemInfo | undefined} */
+		let managedItemInfo;
+		/** @type {ManagedFiles} */
+		const managedFiles = new Set();
+		/** @type {ManagedContexts} */
+		const managedContexts = new Set();
+		/** @type {ManagedMissing} */
+		const managedMissing = new Set();
+
+		const snapshot = new Snapshot();
+		if (startTime) snapshot.setStartTime(startTime);
+
+		/** @type {Set} */
+		const managedItems = new Set();
+
+		/** 1 = timestamp, 2 = hash, 3 = timestamp + hash */
+		const mode = options && options.hash ? (options.timestamp ? 3 : 2) : 1;
+
+		let jobs = 1;
+		const jobDone = () => {
+			if (--jobs === 0) {
+				if (fileTimestamps !== undefined && fileTimestamps.size !== 0) {
+					snapshot.setFileTimestamps(fileTimestamps);
+				}
+				if (fileHashes !== undefined && fileHashes.size !== 0) {
+					snapshot.setFileHashes(fileHashes);
+				}
+				if (fileTshs !== undefined && fileTshs.size !== 0) {
+					snapshot.setFileTshs(fileTshs);
+				}
+				if (contextTimestamps !== undefined && contextTimestamps.size !== 0) {
+					snapshot.setContextTimestamps(contextTimestamps);
+				}
+				if (contextHashes !== undefined && contextHashes.size !== 0) {
+					snapshot.setContextHashes(contextHashes);
+				}
+				if (contextTshs !== undefined && contextTshs.size !== 0) {
+					snapshot.setContextTshs(contextTshs);
+				}
+				if (missingExistence !== undefined && missingExistence.size !== 0) {
+					snapshot.setMissingExistence(missingExistence);
+				}
+				if (managedItemInfo !== undefined && managedItemInfo.size !== 0) {
+					snapshot.setManagedItemInfo(managedItemInfo);
+				}
+				this._managedFilesOptimization.optimize(snapshot, managedFiles);
+				if (managedFiles.size !== 0) {
+					snapshot.setManagedFiles(managedFiles);
+				}
+				this._managedContextsOptimization.optimize(snapshot, managedContexts);
+				if (managedContexts.size !== 0) {
+					snapshot.setManagedContexts(managedContexts);
+				}
+				this._managedMissingOptimization.optimize(snapshot, managedMissing);
+				if (managedMissing.size !== 0) {
+					snapshot.setManagedMissing(managedMissing);
+				}
+				this._snapshotCache.set(snapshot, true);
+				this._statCreatedSnapshots++;
+
+				callback(null, snapshot);
+			}
+		};
+		const jobError = () => {
+			if (jobs > 0) {
+				// large negative number instead of NaN or something else to keep jobs to stay a SMI (v8)
+				jobs = -100000000;
+				callback(null, null);
+			}
+		};
+		const classificationCache = this._pathClassificationCache;
+		/**
+		 * Checks true when managed.
+		 * @param {string} path path
+		 * @param {ManagedFiles} managedSet managed set
+		 * @returns {boolean} true when managed
+		 */
+		const checkManaged = (path, managedSet) => {
+			// classification is a pure function of the path, so it's shared across snapshots
+			const cached = classificationCache.get(path);
+			if (cached !== undefined) {
+				if (cached === 0) return false;
+				if (cached !== 1) managedItems.add(cached);
+				managedSet.add(path);
+				return true;
+			}
+			for (const unmanagedPath of this.unmanagedPathsRegExps) {
+				if (unmanagedPath.test(path)) {
+					classificationCache.set(path, 0);
+					return false;
+				}
+			}
+			for (const unmanagedPath of this.unmanagedPathsWithSlash) {
+				if (path.startsWith(unmanagedPath)) {
+					classificationCache.set(path, 0);
+					return false;
+				}
+			}
+			for (const immutablePath of this.immutablePathsRegExps) {
+				if (immutablePath.test(path)) {
+					classificationCache.set(path, 1);
+					managedSet.add(path);
+					return true;
+				}
+			}
+			for (const immutablePath of this.immutablePathsWithSlash) {
+				if (path.startsWith(immutablePath)) {
+					classificationCache.set(path, 1);
+					managedSet.add(path);
+					return true;
+				}
+			}
+			for (const managedPath of this.managedPathsRegExps) {
+				const match = managedPath.exec(path);
+				if (match) {
+					const managedItem = getManagedItem(match[1], path);
+					if (managedItem) {
+						classificationCache.set(path, managedItem);
+						managedItems.add(managedItem);
+						managedSet.add(path);
+						return true;
+					}
+				}
+			}
+			for (const managedPath of this.managedPathsWithSlash) {
+				if (path.startsWith(managedPath)) {
+					const managedItem = getManagedItem(managedPath, path);
+					if (managedItem) {
+						classificationCache.set(path, managedItem);
+						managedItems.add(managedItem);
+						managedSet.add(path);
+						return true;
+					}
+				}
+			}
+			classificationCache.set(path, 0);
+			return false;
+		};
+		/**
+		 * Capture non managed.
+		 * @param {Iterable} items items
+		 * @param {Set} managedSet managed set
+		 * @returns {Set} result
+		 */
+		const captureNonManaged = (items, managedSet) => {
+			/** @type {Set} */
+			const capturedItems = new Set();
+			for (const path of items) {
+				if (!checkManaged(path, managedSet)) capturedItems.add(path);
+			}
+			return capturedItems;
+		};
+		/**
+		 * Process captured files.
+		 * @param {ManagedFiles} capturedFiles captured files
+		 */
+		const processCapturedFiles = (capturedFiles) => {
+			if (capturedFiles.size === 0) {
+				return;
+			}
+			switch (mode) {
+				case 3: {
+					this._fileTshsOptimization.optimize(snapshot, capturedFiles);
+					if (fileTshs === undefined) fileTshs = new Map();
+					const fileTshsMap = fileTshs;
+					for (const path of capturedFiles) {
+						const cache = this._fileTshs.get(path);
+						if (cache !== undefined) {
+							fileTshsMap.set(path, cache);
+						} else {
+							jobs++;
+							this._getFileTimestampAndHash(path, (err, entry) => {
+								if (err) {
+									if (this.logger) {
+										this.logger.debug(
+											`Error snapshotting file timestamp hash combination of ${path}: ${err.stack}`
+										);
+									}
+									jobError();
+								} else {
+									fileTshsMap.set(
+										path,
+										/** @type {TimestampAndHash} */ (entry)
+									);
+									jobDone();
+								}
+							});
+						}
+					}
+					break;
+				}
+				case 2: {
+					this._fileHashesOptimization.optimize(snapshot, capturedFiles);
+					if (fileHashes === undefined) fileHashes = new Map();
+					const fileHashesMap = fileHashes;
+					for (const path of capturedFiles) {
+						const cache = this._fileHashes.get(path);
+						if (cache !== undefined) {
+							fileHashesMap.set(path, cache);
+						} else {
+							jobs++;
+							this.fileHashQueue.add(path, (err, entry) => {
+								if (err) {
+									if (this.logger) {
+										this.logger.debug(
+											`Error snapshotting file hash of ${path}: ${err.stack}`
+										);
+									}
+									jobError();
+								} else {
+									fileHashesMap.set(path, /** @type {string} */ (entry));
+									jobDone();
+								}
+							});
+						}
+					}
+					break;
+				}
+				case 1: {
+					this._fileTimestampsOptimization.optimize(snapshot, capturedFiles);
+					if (fileTimestamps === undefined) fileTimestamps = new Map();
+					const fileTimestampsMap = fileTimestamps;
+					for (const path of capturedFiles) {
+						const cache = this._fileTimestamps.get(path);
+						if (cache !== undefined && !isExistenceOnly(cache)) {
+							if (cache !== "ignore") {
+								fileTimestampsMap.set(
+									path,
+									/** @type {FileSystemInfoEntry | null} */ (cache)
+								);
+							}
+						} else {
+							jobs++;
+							this.fileTimestampQueue.add(path, (err, entry) => {
+								if (err) {
+									if (this.logger) {
+										this.logger.debug(
+											`Error snapshotting file timestamp of ${path}: ${err.stack}`
+										);
+									}
+									jobError();
+								} else {
+									fileTimestampsMap.set(
+										path,
+										/** @type {FileSystemInfoEntry} */
+										(entry)
+									);
+									jobDone();
+								}
+							});
+						}
+					}
+					break;
+				}
+			}
+		};
+		if (files) {
+			processCapturedFiles(captureNonManaged(files, managedFiles));
+		}
+		/**
+		 * Process captured directories.
+		 * @param {ManagedContexts} capturedDirectories captured directories
+		 */
+		const processCapturedDirectories = (capturedDirectories) => {
+			if (capturedDirectories.size === 0) {
+				return;
+			}
+			switch (mode) {
+				case 3: {
+					this._contextTshsOptimization.optimize(snapshot, capturedDirectories);
+					if (contextTshs === undefined) contextTshs = new Map();
+					const contextTshsMap = contextTshs;
+					for (const path of capturedDirectories) {
+						const cache = this._contextTshs.get(path);
+						/** @type {ResolvedContextTimestampAndHash | null | undefined} */
+						let resolved;
+						if (
+							cache !== undefined &&
+							(resolved = getResolvedTimestamp(cache)) !== undefined
+						) {
+							contextTshsMap.set(path, resolved);
+						} else {
+							jobs++;
+							/**
+							 * Processes the provided err.
+							 * @param {(WebpackError | null)=} err error
+							 * @param {(ResolvedContextTimestampAndHash | null)=} entry entry
+							 * @returns {void}
+							 */
+							const callback = (err, entry) => {
+								if (err) {
+									if (this.logger) {
+										this.logger.debug(
+											`Error snapshotting context timestamp hash combination of ${path}: ${err.stack}`
+										);
+									}
+									jobError();
+								} else {
+									contextTshsMap.set(
+										path,
+										/** @type {ResolvedContextTimestampAndHash | null} */
+										(entry)
+									);
+									jobDone();
+								}
+							};
+							if (cache !== undefined) {
+								this._resolveContextTsh(cache, callback);
+							} else {
+								this.getContextTsh(path, callback);
+							}
+						}
+					}
+					break;
+				}
+				case 2: {
+					this._contextHashesOptimization.optimize(
+						snapshot,
+						capturedDirectories
+					);
+					if (contextHashes === undefined) contextHashes = new Map();
+					const contextHashesMap = contextHashes;
+					for (const path of capturedDirectories) {
+						const cache = this._contextHashes.get(path);
+						/** @type {undefined | null | string} */
+						let resolved;
+						if (
+							cache !== undefined &&
+							(resolved = getResolvedHash(cache)) !== undefined
+						) {
+							contextHashesMap.set(path, resolved);
+						} else {
+							jobs++;
+							/**
+							 * Processes the provided err.
+							 * @param {(WebpackError | null)=} err err
+							 * @param {string=} entry entry
+							 */
+							const callback = (err, entry) => {
+								if (err) {
+									if (this.logger) {
+										this.logger.debug(
+											`Error snapshotting context hash of ${path}: ${err.stack}`
+										);
+									}
+									jobError();
+								} else {
+									contextHashesMap.set(path, /** @type {string} */ (entry));
+									jobDone();
+								}
+							};
+							if (cache !== undefined) {
+								this._resolveContextHash(cache, callback);
+							} else {
+								this.getContextHash(path, callback);
+							}
+						}
+					}
+					break;
+				}
+				case 1: {
+					this._contextTimestampsOptimization.optimize(
+						snapshot,
+						capturedDirectories
+					);
+					if (contextTimestamps === undefined) contextTimestamps = new Map();
+					const contextTimestampsMap = contextTimestamps;
+					for (const path of capturedDirectories) {
+						const cache = this._contextTimestamps.get(path);
+						if (cache === "ignore") continue;
+						/** @type {ContextFileSystemInfoEntry | null | undefined} */
+						const usableCache =
+							cache === undefined || isExistenceOnly(cache)
+								? undefined
+								: /** @type {ContextFileSystemInfoEntry | null} */ (cache);
+						const cacheLacksHash =
+							contextCacheEntryLacksTimestampHash(usableCache);
+						/** @type {undefined | null | ResolvedContextFileSystemInfoEntry} */
+						let resolved;
+						if (
+							usableCache !== undefined &&
+							!cacheLacksHash &&
+							(resolved = getResolvedTimestamp(usableCache)) !== undefined
+						) {
+							contextTimestampsMap.set(path, resolved);
+						} else {
+							jobs++;
+							/**
+							 * Processes the provided err.
+							 * @param {(WebpackError | null)=} err error
+							 * @param {ResolvedContextTimestamp=} entry entry
+							 * @returns {void}
+							 */
+							const callback = (err, entry) => {
+								if (err) {
+									if (this.logger) {
+										this.logger.debug(
+											`Error snapshotting context timestamp of ${path}: ${err.stack}`
+										);
+									}
+									jobError();
+								} else {
+									contextTimestampsMap.set(
+										path,
+										/** @type {ResolvedContextFileSystemInfoEntry | null} */
+										(entry)
+									);
+									jobDone();
+								}
+							};
+							if (cacheLacksHash) {
+								this._readFreshContextTimestamp(path, callback);
+							} else if (usableCache !== undefined && usableCache !== null) {
+								this._resolveContextTimestamp(usableCache, callback);
+							} else {
+								// Force a fresh on-disk read so the snapshot stores a
+								// complete entry (with `timestampHash`).
+								this._readFreshContextTimestamp(path, callback);
+							}
+						}
+					}
+					break;
+				}
+			}
+		};
+		if (directories) {
+			processCapturedDirectories(
+				captureNonManaged(directories, managedContexts)
+			);
+		}
+		/**
+		 * Process captured missing.
+		 * @param {ManagedMissing} capturedMissing captured missing
+		 */
+		const processCapturedMissing = (capturedMissing) => {
+			if (capturedMissing.size === 0) {
+				return;
+			}
+			this._missingExistenceOptimization.optimize(snapshot, capturedMissing);
+			if (missingExistence === undefined) missingExistence = new Map();
+			const missingExistenceMap = missingExistence;
+			for (const path of capturedMissing) {
+				const cache = this._fileTimestamps.get(path);
+				if (cache !== undefined && !isExistenceOnly(cache)) {
+					if (cache !== "ignore") {
+						missingExistenceMap.set(path, Boolean(cache));
+					}
+				} else {
+					jobs++;
+					this.fileTimestampQueue.add(path, (err, entry) => {
+						if (err) {
+							if (this.logger) {
+								this.logger.debug(
+									`Error snapshotting missing timestamp of ${path}: ${err.stack}`
+								);
+							}
+							jobError();
+						} else {
+							missingExistenceMap.set(path, Boolean(entry));
+							jobDone();
+						}
+					});
+				}
+			}
+		};
+		if (missing) {
+			processCapturedMissing(captureNonManaged(missing, managedMissing));
+		}
+		this._managedItemInfoOptimization.optimize(snapshot, managedItems);
+		if (managedItems.size !== 0 && managedItemInfo === undefined) {
+			managedItemInfo = new Map();
+		}
+		const managedItemInfoMap = managedItemInfo;
+		for (const path of managedItems) {
+			const cache = this._managedItems.get(path);
+			if (cache !== undefined) {
+				if (!cache.startsWith("*")) {
+					managedFiles.add(join(this.fs, path, "package.json"));
+				} else if (cache === "*nested") {
+					managedMissing.add(join(this.fs, path, "package.json"));
+				}
+				/** @type {ManagedItemInfo} */ (managedItemInfoMap).set(path, cache);
+			} else {
+				jobs++;
+				this.managedItemQueue.add(path, (err, entry) => {
+					if (err) {
+						if (this.logger) {
+							this.logger.debug(
+								`Error snapshotting managed item ${path}: ${err.stack}`
+							);
+						}
+						jobError();
+					} else if (entry) {
+						if (!entry.startsWith("*")) {
+							managedFiles.add(join(this.fs, path, "package.json"));
+						} else if (cache === "*nested") {
+							managedMissing.add(join(this.fs, path, "package.json"));
+						}
+						/** @type {ManagedItemInfo} */ (managedItemInfoMap).set(
+							path,
+							entry
+						);
+						jobDone();
+					} else {
+						// Fallback to normal snapshotting
+						/**
+						 * Processes the provided set.
+						 * @param {Set} set set
+						 * @param {(set: Set) => void} fn fn
+						 */
+						const process = (set, fn) => {
+							if (set.size === 0) return;
+							/** @type {Set} */
+							const captured = new Set();
+							for (const file of set) {
+								if (file.startsWith(path)) captured.add(file);
+							}
+							if (captured.size > 0) fn(captured);
+						};
+						process(managedFiles, processCapturedFiles);
+						process(managedContexts, processCapturedDirectories);
+						process(managedMissing, processCapturedMissing);
+						jobDone();
+					}
+				});
+			}
+		}
+		jobDone();
+	}
+
+	/**
+	 * Merges the provided values into a single result.
+	 * @param {Snapshot} snapshot1 a snapshot
+	 * @param {Snapshot} snapshot2 a snapshot
+	 * @returns {Snapshot} merged snapshot
+	 */
+	mergeSnapshots(snapshot1, snapshot2) {
+		const snapshot = new Snapshot();
+		if (snapshot1.hasStartTime() && snapshot2.hasStartTime()) {
+			snapshot.setStartTime(
+				Math.min(
+					/** @type {NonNullable} */
+					(snapshot1.startTime),
+					/** @type {NonNullable} */
+					(snapshot2.startTime)
+				)
+			);
+		} else if (snapshot2.hasStartTime()) {
+			snapshot.startTime = snapshot2.startTime;
+		} else if (snapshot1.hasStartTime()) {
+			snapshot.startTime = snapshot1.startTime;
+		}
+		if (snapshot1.hasFileTimestamps() || snapshot2.hasFileTimestamps()) {
+			snapshot.setFileTimestamps(
+				mergeMaps(snapshot1.fileTimestamps, snapshot2.fileTimestamps)
+			);
+		}
+		if (snapshot1.hasFileHashes() || snapshot2.hasFileHashes()) {
+			snapshot.setFileHashes(
+				mergeMaps(snapshot1.fileHashes, snapshot2.fileHashes)
+			);
+		}
+		if (snapshot1.hasFileTshs() || snapshot2.hasFileTshs()) {
+			snapshot.setFileTshs(mergeMaps(snapshot1.fileTshs, snapshot2.fileTshs));
+		}
+		if (snapshot1.hasContextTimestamps() || snapshot2.hasContextTimestamps()) {
+			snapshot.setContextTimestamps(
+				mergeMaps(snapshot1.contextTimestamps, snapshot2.contextTimestamps)
+			);
+		}
+		if (snapshot1.hasContextHashes() || snapshot2.hasContextHashes()) {
+			snapshot.setContextHashes(
+				mergeMaps(snapshot1.contextHashes, snapshot2.contextHashes)
+			);
+		}
+		if (snapshot1.hasContextTshs() || snapshot2.hasContextTshs()) {
+			snapshot.setContextTshs(
+				mergeMaps(snapshot1.contextTshs, snapshot2.contextTshs)
+			);
+		}
+		if (snapshot1.hasMissingExistence() || snapshot2.hasMissingExistence()) {
+			snapshot.setMissingExistence(
+				mergeMaps(snapshot1.missingExistence, snapshot2.missingExistence)
+			);
+		}
+		if (snapshot1.hasManagedItemInfo() || snapshot2.hasManagedItemInfo()) {
+			snapshot.setManagedItemInfo(
+				mergeMaps(snapshot1.managedItemInfo, snapshot2.managedItemInfo)
+			);
+		}
+		if (snapshot1.hasManagedFiles() || snapshot2.hasManagedFiles()) {
+			snapshot.setManagedFiles(
+				mergeSets(snapshot1.managedFiles, snapshot2.managedFiles)
+			);
+		}
+		if (snapshot1.hasManagedContexts() || snapshot2.hasManagedContexts()) {
+			snapshot.setManagedContexts(
+				mergeSets(snapshot1.managedContexts, snapshot2.managedContexts)
+			);
+		}
+		if (snapshot1.hasManagedMissing() || snapshot2.hasManagedMissing()) {
+			snapshot.setManagedMissing(
+				mergeSets(snapshot1.managedMissing, snapshot2.managedMissing)
+			);
+		}
+		if (snapshot1.hasChildren() || snapshot2.hasChildren()) {
+			snapshot.setChildren(mergeSets(snapshot1.children, snapshot2.children));
+		}
+		if (
+			this._snapshotCache.get(snapshot1) === true &&
+			this._snapshotCache.get(snapshot2) === true
+		) {
+			this._snapshotCache.set(snapshot, true);
+		}
+		return snapshot;
+	}
+
+	/**
+	 * Checks snapshot valid.
+	 * @param {Snapshot} snapshot the snapshot made
+	 * @param {CheckSnapshotValidCallback} callback callback function
+	 * @returns {void}
+	 */
+	checkSnapshotValid(snapshot, callback) {
+		const cachedResult = this._snapshotCache.get(snapshot);
+		if (cachedResult !== undefined) {
+			this._statTestedSnapshotsCached++;
+			if (typeof cachedResult === "boolean") {
+				callback(null, cachedResult);
+			} else {
+				cachedResult.push(callback);
+			}
+			return;
+		}
+		this._statTestedSnapshotsNotCached++;
+		this._checkSnapshotValidNoCache(snapshot, callback);
+	}
+
+	/**
+	 * Check snapshot valid no cache.
+	 * @private
+	 * @param {Snapshot} snapshot the snapshot made
+	 * @param {CheckSnapshotValidCallback} callback callback function
+	 * @returns {void}
+	 */
+	_checkSnapshotValidNoCache(snapshot, callback) {
+		/** @type {number | undefined} */
+		let startTime;
+		if (snapshot.hasStartTime()) {
+			startTime = snapshot.startTime;
+		}
+		let jobs = 1;
+		const jobDone = () => {
+			if (--jobs === 0) {
+				this._snapshotCache.set(snapshot, true);
+				callback(null, true);
+			}
+		};
+		const invalid = () => {
+			if (jobs > 0) {
+				// large negative number instead of NaN or something else to keep jobs to stay a SMI (v8)
+				jobs = -100000000;
+				this._snapshotCache.set(snapshot, false);
+				callback(null, false);
+			}
+		};
+		/**
+		 * Invalid with error.
+		 * @param {string} path path
+		 * @param {WebpackError} err err
+		 */
+		const invalidWithError = (path, err) => {
+			if (this._remainingLogs > 0) {
+				this._log(path, "error occurred: %s", err);
+			}
+			invalid();
+		};
+		/**
+		 * Checks true, if ok.
+		 * @param {string} path file path
+		 * @param {string | null} current current hash
+		 * @param {string | null} snap snapshot hash
+		 * @returns {boolean} true, if ok
+		 */
+		const checkHash = (path, current, snap) => {
+			if (current !== snap) {
+				// If hash differ it's invalid
+				if (this._remainingLogs > 0) {
+					this._log(path, "hashes differ (%s != %s)", current, snap);
+				}
+				return false;
+			}
+			return true;
+		};
+		/**
+		 * Checks true, if ok.
+		 * @param {string} path file path
+		 * @param {boolean} current current entry
+		 * @param {boolean} snap entry from snapshot
+		 * @returns {boolean} true, if ok
+		 */
+		const checkExistence = (path, current, snap) => {
+			if (!current !== !snap) {
+				// If existence of item differs
+				// it's invalid
+				if (this._remainingLogs > 0) {
+					this._log(
+						path,
+						current ? "it didn't exist before" : "it does no longer exist"
+					);
+				}
+				return false;
+			}
+			return true;
+		};
+		/**
+		 * Checks true, if ok.
+		 * @param {string} path file path
+		 * @param {FileSystemInfoEntry | null} c current entry
+		 * @param {FileSystemInfoEntry | null} s entry from snapshot
+		 * @param {boolean} log log reason
+		 * @returns {boolean} true, if ok
+		 */
+		const checkFile = (path, c, s, log = true) => {
+			if (c === s) return true;
+			if (!checkExistence(path, Boolean(c), Boolean(s))) return false;
+			if (c) {
+				// For existing items only
+				if (typeof startTime === "number" && c.safeTime > startTime) {
+					// If a change happened after starting reading the item
+					// this may no longer be valid
+					if (log && this._remainingLogs > 0) {
+						this._log(
+							path,
+							"it may have changed (%d) after the start time of the snapshot (%d)",
+							c.safeTime,
+							startTime
+						);
+					}
+					return false;
+				}
+				const snap = /** @type {FileSystemInfoEntry} */ (s);
+				if (snap.timestamp !== undefined && c.timestamp !== snap.timestamp) {
+					// If we have a timestamp (it was a file or symlink) and it differs from current timestamp
+					// it's invalid
+					if (log && this._remainingLogs > 0) {
+						this._log(
+							path,
+							"timestamps differ (%d != %d)",
+							c.timestamp,
+							snap.timestamp
+						);
+					}
+					return false;
+				}
+			}
+			return true;
+		};
+		/**
+		 * Checks true, if ok.
+		 * @param {string} path file path
+		 * @param {ResolvedContextFileSystemInfoEntry | null} c current entry
+		 * @param {ResolvedContextFileSystemInfoEntry | null} s entry from snapshot
+		 * @param {boolean} log log reason
+		 * @returns {boolean} true, if ok
+		 */
+		const checkContext = (path, c, s, log = true) => {
+			if (c === s) return true;
+			if (!checkExistence(path, Boolean(c), Boolean(s))) return false;
+			if (c) {
+				// For existing items only
+				if (typeof startTime === "number" && c.safeTime > startTime) {
+					// If a change happened after starting reading the item
+					// this may no longer be valid
+					if (log && this._remainingLogs > 0) {
+						this._log(
+							path,
+							"it may have changed (%d) after the start time of the snapshot (%d)",
+							c.safeTime,
+							startTime
+						);
+					}
+					return false;
+				}
+				const snap = /** @type {ResolvedContextFileSystemInfoEntry} */ (s);
+				if (
+					snap.timestampHash !== undefined &&
+					c.timestampHash !== snap.timestampHash
+				) {
+					// If we have a timestampHash (it was a directory) and it differs from current timestampHash
+					// it's invalid
+					if (log && this._remainingLogs > 0) {
+						this._log(
+							path,
+							"timestamps hashes differ (%s != %s)",
+							c.timestampHash,
+							snap.timestampHash
+						);
+					}
+					return false;
+				}
+			}
+			return true;
+		};
+		if (snapshot.hasChildren()) {
+			/**
+			 * Processes the provided err.
+			 * @param {(WebpackError | null)=} err err
+			 * @param {boolean=} result result
+			 * @returns {void}
+			 */
+			const childCallback = (err, result) => {
+				if (err || !result) return invalid();
+				jobDone();
+			};
+			for (const child of /** @type {Children} */ (snapshot.children)) {
+				const cache = this._snapshotCache.get(child);
+				if (cache !== undefined) {
+					this._statTestedChildrenCached++;
+					/* istanbul ignore else */
+					if (typeof cache === "boolean") {
+						if (cache === false) {
+							invalid();
+							return;
+						}
+					} else {
+						jobs++;
+						cache.push(childCallback);
+					}
+				} else {
+					this._statTestedChildrenNotCached++;
+					jobs++;
+					this._checkSnapshotValidNoCache(child, childCallback);
+				}
+			}
+		}
+		if (snapshot.hasFileTimestamps()) {
+			const fileTimestamps =
+				/** @type {FileTimestamps} */
+				(snapshot.fileTimestamps);
+			this._statTestedEntries += fileTimestamps.size;
+			for (const [path, ts] of fileTimestamps) {
+				const cache = this._fileTimestamps.get(path);
+				if (cache !== undefined && !isExistenceOnly(cache)) {
+					if (
+						cache !== "ignore" &&
+						!checkFile(
+							path,
+							/** @type {FileSystemInfoEntry | null} */ (cache),
+							ts
+						)
+					) {
+						invalid();
+						return;
+					}
+				} else {
+					jobs++;
+					this.fileTimestampQueue.add(path, (err, entry) => {
+						if (err) return invalidWithError(path, err);
+						if (
+							!checkFile(
+								path,
+								/** @type {FileSystemInfoEntry | null} */ (entry),
+								ts
+							)
+						) {
+							invalid();
+						} else {
+							jobDone();
+						}
+					});
+				}
+			}
+		}
+		/**
+		 * Process file hash snapshot.
+		 * @param {string} path file path
+		 * @param {string | null} hash hash
+		 */
+		const processFileHashSnapshot = (path, hash) => {
+			const cache = this._fileHashes.get(path);
+			if (cache !== undefined) {
+				if (cache !== "ignore" && !checkHash(path, cache, hash)) {
+					invalid();
+				}
+			} else {
+				jobs++;
+				this.fileHashQueue.add(path, (err, entry) => {
+					if (err) return invalidWithError(path, err);
+					if (!checkHash(path, /** @type {string} */ (entry), hash)) {
+						invalid();
+					} else {
+						jobDone();
+					}
+				});
+			}
+		};
+		if (snapshot.hasFileHashes()) {
+			const fileHashes = /** @type {FileHashes} */ (snapshot.fileHashes);
+			this._statTestedEntries += fileHashes.size;
+			for (const [path, hash] of fileHashes) {
+				processFileHashSnapshot(path, hash);
+			}
+		}
+		if (snapshot.hasFileTshs()) {
+			const fileTshs = /** @type {FileTshs} */ (snapshot.fileTshs);
+			this._statTestedEntries += fileTshs.size;
+			for (const [path, tsh] of fileTshs) {
+				if (typeof tsh === "string") {
+					processFileHashSnapshot(path, tsh);
+				} else {
+					const cache = this._fileTimestamps.get(path);
+					if (cache !== undefined && !isExistenceOnly(cache)) {
+						if (
+							cache === "ignore" ||
+							!checkFile(
+								path,
+								/** @type {FileSystemInfoEntry | null} */ (cache),
+								tsh,
+								false
+							)
+						) {
+							processFileHashSnapshot(path, tsh && tsh.hash);
+						}
+					} else {
+						jobs++;
+						this.fileTimestampQueue.add(path, (err, entry) => {
+							if (err) return invalidWithError(path, err);
+							if (
+								!checkFile(
+									path,
+									/** @type {FileSystemInfoEntry | null} */
+									(entry),
+									tsh,
+									false
+								)
+							) {
+								processFileHashSnapshot(path, tsh && tsh.hash);
+							}
+							jobDone();
+						});
+					}
+				}
+			}
+		}
+		if (snapshot.hasContextTimestamps()) {
+			const contextTimestamps =
+				/** @type {ContextTimestamps} */
+				(snapshot.contextTimestamps);
+			this._statTestedEntries += contextTimestamps.size;
+			for (const [path, ts] of contextTimestamps) {
+				const cache = this._contextTimestamps.get(path);
+				if (cache === "ignore") continue;
+				// Treat existence-only entries (`{}` from watchpack) as a cache
+				// miss — they carry no time info, so we cannot compare them to
+				// the snapshot.
+				/** @type {ContextFileSystemInfoEntry | null | undefined} */
+				const usableCache =
+					cache === undefined || isExistenceOnly(cache)
+						? undefined
+						: /** @type {ContextFileSystemInfoEntry | null} */ (cache);
+				const cacheLacksHash =
+					contextCacheEntryLacksTimestampHash(usableCache) &&
+					ts !== null &&
+					ts.timestampHash !== undefined;
+				/** @type {undefined | null | ResolvedContextFileSystemInfoEntry} */
+				let resolved;
+				if (
+					usableCache !== undefined &&
+					!cacheLacksHash &&
+					(resolved = getResolvedTimestamp(usableCache)) !== undefined
+				) {
+					if (!checkContext(path, resolved, ts)) {
+						invalid();
+						return;
+					}
+				} else {
+					jobs++;
+					/**
+					 * Processes the provided err.
+					 * @param {(WebpackError | null)=} err error
+					 * @param {ResolvedContextTimestamp=} entry entry
+					 * @returns {void}
+					 */
+					const callback = (err, entry) => {
+						if (err) return invalidWithError(path, err);
+						if (
+							!checkContext(
+								path,
+								/** @type {ResolvedContextFileSystemInfoEntry | null} */
+								(entry),
+								ts
+							)
+						) {
+							invalid();
+						} else {
+							jobDone();
+						}
+					};
+					if (cacheLacksHash) {
+						this._readFreshContextTimestamp(path, callback);
+					} else if (usableCache !== undefined && usableCache !== null) {
+						this._resolveContextTimestamp(usableCache, callback);
+					} else {
+						this.getContextTimestamp(path, callback);
+					}
+				}
+			}
+		}
+		/**
+		 * Process context hash snapshot.
+		 * @param {string} path path
+		 * @param {string | null} hash hash
+		 */
+		const processContextHashSnapshot = (path, hash) => {
+			const cache = this._contextHashes.get(path);
+			/** @type {undefined | null | string} */
+			let resolved;
+			if (
+				cache !== undefined &&
+				(resolved = getResolvedHash(cache)) !== undefined
+			) {
+				if (!checkHash(path, resolved, hash)) {
+					invalid();
+				}
+			} else {
+				jobs++;
+				/**
+				 * Processes the provided err.
+				 * @param {(WebpackError | null)=} err err
+				 * @param {string=} entry entry
+				 * @returns {void}
+				 */
+				const callback = (err, entry) => {
+					if (err) return invalidWithError(path, err);
+					if (!checkHash(path, /** @type {string} */ (entry), hash)) {
+						invalid();
+					} else {
+						jobDone();
+					}
+				};
+				if (cache !== undefined) {
+					this._resolveContextHash(cache, callback);
+				} else {
+					this.getContextHash(path, callback);
+				}
+			}
+		};
+		if (snapshot.hasContextHashes()) {
+			const contextHashes =
+				/** @type {ContextHashes} */
+				(snapshot.contextHashes);
+			this._statTestedEntries += contextHashes.size;
+			for (const [path, hash] of contextHashes) {
+				processContextHashSnapshot(path, hash);
+			}
+		}
+		if (snapshot.hasContextTshs()) {
+			const contextTshs = /** @type {ContextTshs} */ (snapshot.contextTshs);
+			this._statTestedEntries += contextTshs.size;
+			for (const [path, tsh] of contextTshs) {
+				if (typeof tsh === "string") {
+					processContextHashSnapshot(path, tsh);
+				} else {
+					const cache = this._contextTimestamps.get(path);
+					if (cache === "ignore") continue;
+					// See the matching block in `hasContextTimestamps` above.
+					/** @type {ContextFileSystemInfoEntry | null | undefined} */
+					const usableCache =
+						cache === undefined || isExistenceOnly(cache)
+							? undefined
+							: /** @type {ContextFileSystemInfoEntry | null} */ (cache);
+					const cacheLacksHash =
+						contextCacheEntryLacksTimestampHash(usableCache) &&
+						tsh !== null &&
+						tsh.timestampHash !== undefined;
+					/** @type {undefined | null | ResolvedContextFileSystemInfoEntry} */
+					let resolved;
+					if (
+						usableCache !== undefined &&
+						!cacheLacksHash &&
+						(resolved = getResolvedTimestamp(usableCache)) !== undefined
+					) {
+						if (!checkContext(path, resolved, tsh, false)) {
+							processContextHashSnapshot(path, tsh && tsh.hash);
+						}
+					} else {
+						jobs++;
+						/**
+						 * Processes the provided err.
+						 * @param {(WebpackError | null)=} err error
+						 * @param {ResolvedContextTimestamp=} entry entry
+						 * @returns {void}
+						 */
+						const callback = (err, entry) => {
+							if (err) return invalidWithError(path, err);
+							if (
+								!checkContext(
+									path,
+									/** @type {ResolvedContextFileSystemInfoEntry | null} */
+									(entry),
+									tsh,
+									false
+								)
+							) {
+								processContextHashSnapshot(path, tsh && tsh.hash);
+							}
+							jobDone();
+						};
+						if (cacheLacksHash) {
+							this._readFreshContextTimestamp(path, callback);
+						} else if (usableCache !== undefined && usableCache !== null) {
+							this._resolveContextTimestamp(usableCache, callback);
+						} else {
+							this.getContextTimestamp(path, callback);
+						}
+					}
+				}
+			}
+		}
+		if (snapshot.hasMissingExistence()) {
+			const missingExistence =
+				/** @type {MissingExistence} */
+				(snapshot.missingExistence);
+			this._statTestedEntries += missingExistence.size;
+			for (const [path, existence] of missingExistence) {
+				const cache = this._fileTimestamps.get(path);
+				if (cache !== undefined && !isExistenceOnly(cache)) {
+					if (
+						cache !== "ignore" &&
+						!checkExistence(path, Boolean(cache), Boolean(existence))
+					) {
+						invalid();
+						return;
+					}
+				} else {
+					jobs++;
+					this.fileTimestampQueue.add(path, (err, entry) => {
+						if (err) return invalidWithError(path, err);
+						if (!checkExistence(path, Boolean(entry), Boolean(existence))) {
+							invalid();
+						} else {
+							jobDone();
+						}
+					});
+				}
+			}
+		}
+		if (snapshot.hasManagedItemInfo()) {
+			const managedItemInfo =
+				/** @type {ManagedItemInfo} */
+				(snapshot.managedItemInfo);
+			this._statTestedEntries += managedItemInfo.size;
+			for (const [path, info] of managedItemInfo) {
+				const cache = this._managedItems.get(path);
+				if (cache !== undefined) {
+					if (!checkHash(path, cache, info)) {
+						invalid();
+						return;
+					}
+				} else {
+					jobs++;
+					this.managedItemQueue.add(path, (err, entry) => {
+						if (err) return invalidWithError(path, err);
+						if (!checkHash(path, /** @type {string} */ (entry), info)) {
+							invalid();
+						} else {
+							jobDone();
+						}
+					});
+				}
+			}
+		}
+		jobDone();
+
+		// if there was an async action
+		// try to join multiple concurrent request for this snapshot
+		if (jobs > 0) {
+			const callbacks = [callback];
+			callback = (err, result) => {
+				for (const callback of callbacks) callback(err, result);
+			};
+			this._snapshotCache.set(snapshot, callbacks);
+		}
+	}
+
+	/**
+	 * @private
+	 * @type {Processor}
+	 */
+	_readFileTimestamp(path, callback) {
+		this.fs.stat(path, (err, _stat) => {
+			if (err) {
+				if (err.code === "ENOENT") {
+					this._fileTimestamps.set(path, null);
+					this._cachedDeprecatedFileTimestamps = undefined;
+					return callback(null, null);
+				}
+				return callback(/** @type {WebpackError} */ (err));
+			}
+			const stat = /** @type {IStats} */ (_stat);
+			/** @type {FileSystemInfoEntry} */
+			let ts;
+			if (stat.isDirectory()) {
+				ts = {
+					safeTime: 0,
+					timestamp: undefined
+				};
+			} else {
+				const mtime = Number(stat.mtime);
+
+				if (mtime) applyMtime(mtime);
+
+				ts = {
+					safeTime: mtime ? mtime + FS_ACCURACY : Infinity,
+					timestamp: mtime
+				};
+			}
+
+			this._fileTimestamps.set(path, ts);
+			this._cachedDeprecatedFileTimestamps = undefined;
+
+			callback(null, ts);
+		});
+	}
+
+	/**
+	 * @private
+	 * @type {Processor}
+	 */
+	_readFileHash(path, callback) {
+		this.fs.readFile(path, (err, content) => {
+			if (err) {
+				if (err.code === "EISDIR") {
+					this._fileHashes.set(path, "directory");
+					return callback(null, "directory");
+				}
+				if (err.code === "ENOENT") {
+					this._fileHashes.set(path, null);
+					return callback(null, null);
+				}
+				if (err.code === "ERR_FS_FILE_TOO_LARGE") {
+					/** @type {Logger} */
+					(this.logger).warn(`Ignoring ${path} for hashing as it's very large`);
+					this._fileHashes.set(path, "too large");
+					return callback(null, "too large");
+				}
+				return callback(/** @type {WebpackError} */ (err));
+			}
+
+			const hash = createHash(this._hashFunction);
+
+			hash.update(/** @type {string | Buffer} */ (content));
+
+			const digest = hash.digest("hex");
+
+			this._fileHashes.set(path, digest);
+
+			callback(null, digest);
+		});
+	}
+
+	/**
+	 * Get file timestamp and hash.
+	 * @private
+	 * @param {string} path path
+	 * @param {(err: WebpackError | null, timestampAndHash?: TimestampAndHash | string) => void} callback callback
+	 */
+	_getFileTimestampAndHash(path, callback) {
+		/**
+		 * Continue with hash.
+		 * @param {string} hash hash
+		 * @returns {void}
+		 */
+		const continueWithHash = (hash) => {
+			const cache = this._fileTimestamps.get(path);
+			if (cache !== undefined) {
+				if (cache !== "ignore") {
+					/** @type {TimestampAndHash} */
+					const result = {
+						.../** @type {FileSystemInfoEntry} */ (cache),
+						hash
+					};
+					this._fileTshs.set(path, result);
+					return callback(null, result);
+				}
+				this._fileTshs.set(path, hash);
+				return callback(null, hash);
+			}
+			this.fileTimestampQueue.add(path, (err, entry) => {
+				if (err) {
+					return callback(err);
+				}
+				/** @type {TimestampAndHash} */
+				const result = {
+					.../** @type {FileSystemInfoEntry} */ (entry),
+					hash
+				};
+				this._fileTshs.set(path, result);
+				return callback(null, result);
+			});
+		};
+
+		const cache = this._fileHashes.get(path);
+		if (cache !== undefined) {
+			continueWithHash(/** @type {string} */ (cache));
+		} else {
+			this.fileHashQueue.add(path, (err, entry) => {
+				if (err) {
+					return callback(err);
+				}
+				continueWithHash(/** @type {string} */ (entry));
+			});
+		}
+	}
+
+	/**
+	 * Processes the provided object.
+	 * @private
+	 * @template T
+	 * @template ItemType
+	 * @param {object} options options
+	 * @param {string} options.path path
+	 * @param {(value: string) => ItemType} options.fromImmutablePath called when context item is an immutable path
+	 * @param {(value: string) => ItemType} options.fromManagedItem called when context item is a managed path
+	 * @param {(value: string, result: string, callback: (err?: WebpackError | null, itemType?: ItemType) => void) => void} options.fromSymlink called when context item is a symlink
+	 * @param {(value: string, stats: IStats, callback: (err?: WebpackError | null, itemType?: ItemType | null) => void) => void} options.fromFile called when context item is a file
+	 * @param {(value: string, stats: IStats, callback: (err?: WebpackError | null, itemType?: ItemType) => void) => void} options.fromDirectory called when context item is a directory
+	 * @param {(arr: string[], arr1: ItemType[]) => T} options.reduce called from all context items
+	 * @param {(err?: Error | null, result?: T | null) => void} callback callback
+	 */
+	_readContext(
+		{
+			path,
+			fromImmutablePath,
+			fromManagedItem,
+			fromSymlink,
+			fromFile,
+			fromDirectory,
+			reduce
+		},
+		callback
+	) {
+		this.fs.readdir(path, (err, _files) => {
+			if (err) {
+				if (err.code === "ENOENT") {
+					return callback(null, null);
+				}
+				return callback(err);
+			}
+			const files = /** @type {string[]} */ (_files)
+				.map((file) => file.normalize("NFC"))
+				.filter((file) => !/^\./.test(file))
+				.sort();
+			asyncLib.map(
+				files,
+				(file, callback) => {
+					const child = join(this.fs, path, file);
+					for (const immutablePath of this.immutablePathsRegExps) {
+						if (immutablePath.test(path)) {
+							// ignore any immutable path for timestamping
+							return callback(null, fromImmutablePath(path));
+						}
+					}
+					for (const immutablePath of this.immutablePathsWithSlash) {
+						if (path.startsWith(immutablePath)) {
+							// ignore any immutable path for timestamping
+							return callback(null, fromImmutablePath(path));
+						}
+					}
+					for (const managedPath of this.managedPathsRegExps) {
+						const match = managedPath.exec(path);
+						if (match) {
+							const managedItem = getManagedItem(match[1], path);
+							if (managedItem) {
+								// construct timestampHash from managed info
+								return this.managedItemQueue.add(managedItem, (err, info) => {
+									if (err) return callback(err);
+									return callback(
+										null,
+										fromManagedItem(/** @type {string} */ (info))
+									);
+								});
+							}
+						}
+					}
+					for (const managedPath of this.managedPathsWithSlash) {
+						if (path.startsWith(managedPath)) {
+							const managedItem = getManagedItem(managedPath, child);
+							if (managedItem) {
+								// construct timestampHash from managed info
+								return this.managedItemQueue.add(managedItem, (err, info) => {
+									if (err) return callback(err);
+									return callback(
+										null,
+										fromManagedItem(/** @type {string} */ (info))
+									);
+								});
+							}
+						}
+					}
+
+					lstatReadlinkAbsolute(this.fs, child, (err, _stat) => {
+						if (err) return callback(err);
+
+						const stat = /** @type {IStats | string} */ (_stat);
+
+						if (typeof stat === "string") {
+							return fromSymlink(child, stat, callback);
+						}
+
+						if (stat.isFile()) {
+							return fromFile(child, stat, callback);
+						}
+						if (stat.isDirectory()) {
+							return fromDirectory(child, stat, callback);
+						}
+						callback(null, null);
+					});
+				},
+				(err, results) => {
+					if (err) return callback(err);
+					const result = reduce(files, /** @type {ItemType[]} */ (results));
+					callback(null, result);
+				}
+			);
+		});
+	}
+
+	/**
+	 * @private
+	 * @type {Processor}
+	 */
+	_readContextTimestamp(path, callback) {
+		this._readContext(
+			{
+				path,
+				fromImmutablePath: () =>
+					/** @type {ContextFileSystemInfoEntry | FileSystemInfoEntry | "ignore" | null} */
+					(null),
+				fromManagedItem: (info) => ({
+					safeTime: 0,
+					timestampHash: info
+				}),
+				fromSymlink: (file, target, callback) => {
+					callback(
+						null,
+						/** @type {ContextFileSystemInfoEntry} */
+						({
+							timestampHash: target,
+							symlinks: new Set([target])
+						})
+					);
+				},
+				fromFile: (file, stat, callback) => {
+					// Prefer the cached value over our new stat to report consistent results
+					const cache = this._fileTimestamps.get(file);
+					if (cache !== undefined && !isExistenceOnly(cache)) {
+						return callback(
+							null,
+							cache === "ignore"
+								? null
+								: /** @type {FileSystemInfoEntry | null} */ (cache)
+						);
+					}
+
+					const mtime = Number(stat.mtime);
+
+					if (mtime) applyMtime(mtime);
+
+					/** @type {FileSystemInfoEntry} */
+					const ts = {
+						safeTime: mtime ? mtime + FS_ACCURACY : Infinity,
+						timestamp: mtime
+					};
+
+					this._fileTimestamps.set(file, ts);
+					this._cachedDeprecatedFileTimestamps = undefined;
+					callback(null, ts);
+				},
+				fromDirectory: (directory, stat, callback) => {
+					this.contextTimestampQueue.increaseParallelism();
+					this._getUnresolvedContextTimestamp(directory, (err, tsEntry) => {
+						this.contextTimestampQueue.decreaseParallelism();
+						callback(err, tsEntry);
+					});
+				},
+				reduce: (files, tsEntries) => {
+					/** @type {undefined | Symlinks} */
+					let symlinks;
+
+					const hash = createHash(this._hashFunction);
+
+					for (const file of files) hash.update(file);
+					let safeTime = 0;
+					for (const _e of tsEntries) {
+						if (!_e) {
+							hash.update("n");
+							continue;
+						}
+						const entry =
+							/** @type {FileSystemInfoEntry | ContextFileSystemInfoEntry} */
+							(_e);
+						if (/** @type {FileSystemInfoEntry} */ (entry).timestamp) {
+							hash.update("f");
+							hash.update(
+								`${/** @type {FileSystemInfoEntry} */ (entry).timestamp}`
+							);
+						} else if (
+							/** @type {ContextFileSystemInfoEntry} */ (entry).timestampHash
+						) {
+							hash.update("d");
+							hash.update(
+								`${/** @type {ContextFileSystemInfoEntry} */ (entry).timestampHash}`
+							);
+						}
+						if (
+							/** @type {ContextFileSystemInfoEntry} */
+							(entry).symlinks !== undefined
+						) {
+							if (symlinks === undefined) symlinks = new Set();
+							addAll(
+								/** @type {ContextFileSystemInfoEntry} */ (entry).symlinks,
+								symlinks
+							);
+						}
+						if (entry.safeTime) {
+							safeTime = Math.max(safeTime, entry.safeTime);
+						}
+					}
+
+					const digest = hash.digest("hex");
+					/** @type {ContextFileSystemInfoEntry} */
+					const result = {
+						safeTime,
+						timestampHash: digest
+					};
+					if (symlinks) result.symlinks = symlinks;
+					return result;
+				}
+			},
+			(err, result) => {
+				if (err) return callback(/** @type {WebpackError} */ (err));
+				this._contextTimestamps.set(path, result);
+				this._cachedDeprecatedContextTimestamps = undefined;
+
+				callback(null, result);
+			}
+		);
+	}
+
+	/**
+	 * Resolve context timestamp.
+	 * @private
+	 * @param {ContextFileSystemInfoEntry} entry entry
+	 * @param {(err?: WebpackError | null, resolvedContextTimestamp?: ResolvedContextTimestamp) => void} callback callback
+	 * @returns {void}
+	 */
+	_resolveContextTimestamp(entry, callback) {
+		/** @type {string[]} */
+		const hashes = [];
+		let safeTime = 0;
+		// Skip already-visited symlink targets so cyclic pnpm/peer-variant graphs terminate (#21084).
+		const seen = new Set(entry.symlinks);
+		processAsyncTree(
+			/** @type {NonNullable} */ (entry.symlinks),
+			10,
+			(target, push, callback) => {
+				this._getUnresolvedContextTimestamp(target, (err, entry) => {
+					if (err) return callback(err);
+					if (entry && entry !== "ignore") {
+						hashes.push(/** @type {string} */ (entry.timestampHash));
+						if (entry.safeTime) {
+							safeTime = Math.max(safeTime, entry.safeTime);
+						}
+						if (entry.symlinks !== undefined) {
+							for (const target of entry.symlinks) {
+								if (!seen.has(target)) {
+									seen.add(target);
+									push(target);
+								}
+							}
+						}
+					}
+					callback();
+				});
+			},
+			(err) => {
+				if (err) return callback(/** @type {WebpackError} */ (err));
+				const hash = createHash(this._hashFunction);
+				hash.update(/** @type {string} */ (entry.timestampHash));
+				if (entry.safeTime) {
+					safeTime = Math.max(safeTime, entry.safeTime);
+				}
+				hashes.sort();
+				for (const h of hashes) {
+					hash.update(h);
+				}
+				callback(
+					null,
+					(entry.resolved = {
+						safeTime,
+						timestampHash: hash.digest("hex")
+					})
+				);
+			}
+		);
+	}
+
+	/**
+	 * @private
+	 * @type {Processor}
+	 */
+	_readContextHash(path, callback) {
+		this._readContext(
+			{
+				path,
+				fromImmutablePath: () => /** @type {ContextHash | ""} */ (""),
+				fromManagedItem: (info) => info || "",
+				fromSymlink: (file, target, callback) => {
+					callback(
+						null,
+						/** @type {ContextHash} */
+						({
+							hash: target,
+							symlinks: new Set([target])
+						})
+					);
+				},
+				fromFile: (file, stat, callback) =>
+					this.getFileHash(file, (err, hash) => {
+						callback(err, hash || "");
+					}),
+				fromDirectory: (directory, stat, callback) => {
+					this.contextHashQueue.increaseParallelism();
+					this._getUnresolvedContextHash(directory, (err, hash) => {
+						this.contextHashQueue.decreaseParallelism();
+						callback(err, hash || "");
+					});
+				},
+				/**
+				 * Returns reduced hash.
+				 * @param {string[]} files files
+				 * @param {(string | ContextHash | null)[]} fileHashes hashes
+				 * @returns {ContextHash} reduced hash
+				 */
+				reduce: (files, fileHashes) => {
+					/** @type {undefined | Symlinks} */
+					let symlinks;
+					const hash = createHash(this._hashFunction);
+
+					for (const file of files) hash.update(file);
+					for (const entry of fileHashes) {
+						// null for unsupported entry kinds (FIFO, socket); skip like the tsh reduce
+						if (!entry) {
+							continue;
+						} else if (typeof entry === "string") {
+							hash.update(entry);
+						} else {
+							if (entry.hash) hash.update(entry.hash);
+							if (entry.symlinks) {
+								if (symlinks === undefined) symlinks = new Set();
+								addAll(entry.symlinks, symlinks);
+							}
+						}
+					}
+
+					/** @type {ContextHash} */
+					const result = {
+						hash: hash.digest("hex")
+					};
+					if (symlinks) result.symlinks = symlinks;
+					return result;
+				}
+			},
+			(err, _result) => {
+				if (err) return callback(/** @type {WebpackError} */ (err));
+				const result = /** @type {ContextHash} */ (_result);
+				this._contextHashes.set(path, result);
+				return callback(null, result);
+			}
+		);
+	}
+
+	/**
+	 * Resolve context hash.
+	 * @private
+	 * @param {ContextHash} entry context hash
+	 * @param {(err: WebpackError | null, contextHash?: string) => void} callback callback
+	 * @returns {void}
+	 */
+	_resolveContextHash(entry, callback) {
+		/** @type {string[]} */
+		const hashes = [];
+		// Skip already-visited symlink targets so cyclic pnpm/peer-variant graphs terminate (#21084).
+		const seen = new Set(entry.symlinks);
+		processAsyncTree(
+			/** @type {NonNullable} */ (entry.symlinks),
+			10,
+			(target, push, callback) => {
+				this._getUnresolvedContextHash(target, (err, hash) => {
+					if (err) return callback(err);
+					if (hash) {
+						hashes.push(hash.hash);
+						if (hash.symlinks !== undefined) {
+							for (const target of hash.symlinks) {
+								if (!seen.has(target)) {
+									seen.add(target);
+									push(target);
+								}
+							}
+						}
+					}
+					callback();
+				});
+			},
+			(err) => {
+				if (err) return callback(/** @type {WebpackError} */ (err));
+				const hash = createHash(this._hashFunction);
+				hash.update(entry.hash);
+				hashes.sort();
+				for (const h of hashes) {
+					hash.update(h);
+				}
+				callback(null, (entry.resolved = hash.digest("hex")));
+			}
+		);
+	}
+
+	/**
+	 * @private
+	 * @type {Processor}
+	 */
+	_readContextTimestampAndHash(path, callback) {
+		/**
+		 * Processes the provided timestamp.
+		 * @param {ContextTimestamp} timestamp timestamp
+		 * @param {ContextHash | null} hash hash
+		 */
+		const finalize = (timestamp, hash) => {
+			// Merging two `null` reads yields `{}`, which reads as "exists, no hash" (#21636)
+			const result =
+				/** @type {ContextTimestampAndHash} */
+				(
+					hash === null || timestamp === "ignore"
+						? hash
+						: { ...timestamp, ...hash }
+				);
+			this._contextTshs.set(path, result);
+			callback(null, result);
+		};
+		const cachedHash = this._contextHashes.get(path);
+		const cachedTimestamp = this._contextTimestamps.get(path);
+		if (cachedHash !== undefined) {
+			if (cachedTimestamp !== undefined) {
+				finalize(cachedTimestamp, cachedHash);
+			} else {
+				this.contextTimestampQueue.add(path, (err, entry) => {
+					if (err) return callback(err);
+					finalize(
+						/** @type {ContextFileSystemInfoEntry} */
+						(entry),
+						cachedHash
+					);
+				});
+			}
+		} else if (cachedTimestamp !== undefined) {
+			this.contextHashQueue.add(path, (err, entry) => {
+				if (err) return callback(err);
+				finalize(cachedTimestamp, /** @type {ContextHash} */ (entry));
+			});
+		} else {
+			this._readContext(
+				{
+					path,
+					fromImmutablePath: () =>
+						/** @type {ContextTimestampAndHash | Omit | string | null} */ (
+							null
+						),
+					fromManagedItem: (info) => ({
+						safeTime: 0,
+						timestampHash: info,
+						hash: info || ""
+					}),
+					fromSymlink: (file, target, callback) => {
+						callback(null, {
+							timestampHash: target,
+							hash: target,
+							symlinks: new Set([target])
+						});
+					},
+					fromFile: (file, stat, callback) => {
+						this._getFileTimestampAndHash(file, callback);
+					},
+					fromDirectory: (directory, stat, callback) => {
+						this.contextTshQueue.increaseParallelism();
+						this.contextTshQueue.add(directory, (err, result) => {
+							this.contextTshQueue.decreaseParallelism();
+							callback(err, result);
+						});
+					},
+					/**
+					 * Returns tsh.
+					 * @param {string[]} files files
+					 * @param {(Partial & Partial | string | null)[]} results results
+					 * @returns {ContextTimestampAndHash} tsh
+					 */
+					reduce: (files, results) => {
+						/** @type {undefined | Symlinks} */
+						let symlinks;
+
+						const tsHash = createHash(this._hashFunction);
+						const hash = createHash(this._hashFunction);
+
+						for (const file of files) {
+							tsHash.update(file);
+							hash.update(file);
+						}
+						let safeTime = 0;
+						for (const entry of results) {
+							if (!entry) {
+								tsHash.update("n");
+								continue;
+							}
+							if (typeof entry === "string") {
+								tsHash.update("n");
+								hash.update(entry);
+								continue;
+							}
+							if (entry.timestamp) {
+								tsHash.update("f");
+								tsHash.update(`${entry.timestamp}`);
+							} else if (entry.timestampHash) {
+								tsHash.update("d");
+								tsHash.update(`${entry.timestampHash}`);
+							}
+							if (entry.symlinks !== undefined) {
+								if (symlinks === undefined) symlinks = new Set();
+								addAll(entry.symlinks, symlinks);
+							}
+							if (entry.safeTime) {
+								safeTime = Math.max(safeTime, entry.safeTime);
+							}
+							hash.update(/** @type {string} */ (entry.hash));
+						}
+
+						/** @type {ContextTimestampAndHash} */
+						const result = {
+							safeTime,
+							timestampHash: tsHash.digest("hex"),
+							hash: hash.digest("hex")
+						};
+						if (symlinks) result.symlinks = symlinks;
+						return result;
+					}
+				},
+				(err, _result) => {
+					if (err) return callback(/** @type {WebpackError} */ (err));
+					const result = /** @type {ContextTimestampAndHash} */ (_result);
+					this._contextTshs.set(path, result);
+					return callback(null, result);
+				}
+			);
+		}
+	}
+
+	/**
+	 * Resolve context tsh.
+	 * @private
+	 * @param {ContextTimestampAndHash} entry entry
+	 * @param {ProcessorCallback} callback callback
+	 * @returns {void}
+	 */
+	_resolveContextTsh(entry, callback) {
+		/** @type {string[]} */
+		const hashes = [];
+		/** @type {string[]} */
+		const tsHashes = [];
+		let safeTime = 0;
+		// Skip already-visited symlink targets so cyclic pnpm/peer-variant graphs terminate (#21084).
+		const seen = new Set(entry.symlinks);
+		processAsyncTree(
+			/** @type {NonNullable} */ (entry.symlinks),
+			10,
+			(target, push, callback) => {
+				this._getUnresolvedContextTsh(target, (err, entry) => {
+					if (err) return callback(err);
+					if (entry) {
+						hashes.push(entry.hash);
+						if (entry.timestampHash) tsHashes.push(entry.timestampHash);
+						if (entry.safeTime) {
+							safeTime = Math.max(safeTime, entry.safeTime);
+						}
+						if (entry.symlinks !== undefined) {
+							for (const target of entry.symlinks) {
+								if (!seen.has(target)) {
+									seen.add(target);
+									push(target);
+								}
+							}
+						}
+					}
+					callback();
+				});
+			},
+			(err) => {
+				if (err) return callback(/** @type {WebpackError} */ (err));
+				const hash = createHash(this._hashFunction);
+				const tsHash = createHash(this._hashFunction);
+				hash.update(entry.hash);
+				if (entry.timestampHash) tsHash.update(entry.timestampHash);
+				if (entry.safeTime) {
+					safeTime = Math.max(safeTime, entry.safeTime);
+				}
+				hashes.sort();
+				for (const h of hashes) {
+					hash.update(h);
+				}
+				tsHashes.sort();
+				for (const h of tsHashes) {
+					tsHash.update(h);
+				}
+				callback(
+					null,
+					(entry.resolved = {
+						safeTime,
+						timestampHash: tsHash.digest("hex"),
+						hash: hash.digest("hex")
+					})
+				);
+			}
+		);
+	}
+
+	/**
+	 * @private
+	 * @type {Processor>}
+	 */
+	_getManagedItemDirectoryInfo(path, callback) {
+		this.fs.readdir(path, (err, elements) => {
+			if (err) {
+				if (err.code === "ENOENT" || err.code === "ENOTDIR") {
+					return callback(null, EMPTY_SET);
+				}
+				return callback(/** @type {WebpackError} */ (err));
+			}
+			const set = new Set(
+				/** @type {string[]} */
+				(elements).map((element) => join(this.fs, path, element))
+			);
+			callback(null, set);
+		});
+	}
+
+	/**
+	 * @private
+	 * @type {Processor}
+	 */
+	_getManagedItemInfo(path, callback) {
+		const dir = dirname(this.fs, path);
+		this.managedItemDirectoryQueue.add(dir, (err, elements) => {
+			if (err) {
+				return callback(err);
+			}
+			if (!(/** @type {Set} */ (elements).has(path))) {
+				// file or directory doesn't exist
+				this._managedItems.set(path, "*missing");
+				return callback(null, "*missing");
+			}
+			// something exists
+			// it may be a file or directory
+			if (
+				path.endsWith("node_modules") &&
+				(path.endsWith("/node_modules") || path.endsWith("\\node_modules"))
+			) {
+				// we are only interested in existence of this special directory
+				this._managedItems.set(path, "*node_modules");
+				return callback(null, "*node_modules");
+			}
+
+			// we assume it's a directory, as files shouldn't occur in managed paths
+			const packageJsonPath = join(this.fs, path, "package.json");
+			this.fs.readFile(packageJsonPath, (err, content) => {
+				if (err) {
+					if (err.code === "ENOENT" || err.code === "ENOTDIR") {
+						// no package.json or path is not a directory
+						this.fs.readdir(path, (err, elements) => {
+							if (
+								!err &&
+								/** @type {string[]} */ (elements).length === 1 &&
+								/** @type {string[]} */ (elements)[0] === "node_modules"
+							) {
+								// This is only a grouping folder e.g. used by yarn
+								// we are only interested in existence of this special directory
+								this._managedItems.set(path, "*nested");
+								return callback(null, "*nested");
+							}
+							/** @type {Logger} */
+							(this.logger).warn(
+								`Managed item ${path} isn't a directory or doesn't contain a package.json (see snapshot.managedPaths option)`
+							);
+							return callback();
+						});
+						return;
+					}
+					return callback(/** @type {WebpackError} */ (err));
+				}
+				/** @type {JsonObject} */
+				let data;
+				try {
+					data = JSON.parse(/** @type {Buffer} */ (content).toString("utf8"));
+				} catch (parseErr) {
+					return callback(/** @type {WebpackError} */ (parseErr));
+				}
+				if (!data.name) {
+					/** @type {Logger} */
+					(this.logger).warn(
+						`${packageJsonPath} doesn't contain a "name" property (see snapshot.managedPaths option)`
+					);
+					return callback();
+				}
+				const info = `${data.name || ""}@${data.version || ""}`;
+				this._managedItems.set(path, info);
+				callback(null, info);
+			});
+		});
+	}
+
+	getDeprecatedFileTimestamps() {
+		if (this._cachedDeprecatedFileTimestamps !== undefined) {
+			return this._cachedDeprecatedFileTimestamps;
+		}
+		/** @type {Map} */
+		const map = new Map();
+		for (const [path, info] of this._fileTimestamps) {
+			if (info) {
+				const safeTime =
+					typeof info === "object"
+						? /** @type {Partial} */ (info).safeTime
+						: undefined;
+				map.set(path, safeTime === undefined ? null : safeTime);
+			}
+		}
+		return (this._cachedDeprecatedFileTimestamps = map);
+	}
+
+	getDeprecatedContextTimestamps() {
+		if (this._cachedDeprecatedContextTimestamps !== undefined) {
+			return this._cachedDeprecatedContextTimestamps;
+		}
+		/** @type {Map} */
+		const map = new Map();
+		for (const [path, info] of this._contextTimestamps) {
+			if (info) {
+				const safeTime =
+					typeof info === "object"
+						? /** @type {Partial} */ (info).safeTime
+						: undefined;
+				map.set(path, safeTime === undefined ? null : safeTime);
+			}
+		}
+		return (this._cachedDeprecatedContextTimestamps = map);
+	}
+}
+
+module.exports = FileSystemInfo;
+module.exports.Snapshot = Snapshot;
diff --git a/lib/FlagAllModulesAsUsedPlugin.js b/lib/FlagAllModulesAsUsedPlugin.js
new file mode 100644
index 00000000000..4a7dcd765d4
--- /dev/null
+++ b/lib/FlagAllModulesAsUsedPlugin.js
@@ -0,0 +1,52 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const { getEntryRuntime, mergeRuntimeOwned } = require("./util/runtime");
+
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./Module").FactoryMeta} FactoryMeta */
+/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */
+
+const PLUGIN_NAME = "FlagAllModulesAsUsedPlugin";
+class FlagAllModulesAsUsedPlugin {
+	/**
+	 * Creates an instance of FlagAllModulesAsUsedPlugin.
+	 * @param {string} explanation explanation
+	 */
+	constructor(explanation) {
+		/** @type {string} */
+		this.explanation = explanation;
+	}
+
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
+	apply(compiler) {
+		compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
+			const moduleGraph = compilation.moduleGraph;
+			compilation.hooks.optimizeDependencies.tap(PLUGIN_NAME, (modules) => {
+				/** @type {RuntimeSpec} */
+				let runtime;
+				for (const [name, { options }] of compilation.entries) {
+					runtime = mergeRuntimeOwned(
+						runtime,
+						getEntryRuntime(compilation, name, options)
+					);
+				}
+				for (const module of modules) {
+					const exportsInfo = moduleGraph.getExportsInfo(module);
+					exportsInfo.setUsedInUnknownWay(runtime);
+					moduleGraph.addExtraReason(module, this.explanation);
+				}
+			});
+		});
+	}
+}
+
+module.exports = FlagAllModulesAsUsedPlugin;
diff --git a/lib/FlagDependencyExportsPlugin.js b/lib/FlagDependencyExportsPlugin.js
index 0aafb0b4aa2..d0d86c92035 100644
--- a/lib/FlagDependencyExportsPlugin.js
+++ b/lib/FlagDependencyExportsPlugin.js
@@ -2,144 +2,564 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
+const asyncLib = require("neo-async");
 const Queue = require("./util/Queue");
+const { compareModulesByIdentifier } = require("./util/comparators");
+const createHash = require("./util/createHash");
 
-const addToSet = (a, b) => {
-	let changed = false;
-	for (const item of b) {
-		if (!a.has(item)) {
-			a.add(item);
-			changed = true;
-		}
-	}
-	return changed;
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./DependenciesBlock")} DependenciesBlock */
+/** @typedef {import("./Dependency")} Dependency */
+/** @typedef {import("./Dependency").ExportSpec} ExportSpec */
+/** @typedef {import("./Dependency").ExportsSpec} ExportsSpec */
+/** @typedef {import("./ExportsInfo")} ExportsInfo */
+/** @typedef {import("./ExportsInfo").ExportInfoName} ExportInfoName */
+/** @typedef {import("./ExportsInfo").RestoreProvidedData} RestoreProvidedData */
+/** @typedef {import("../declarations/WebpackOptions").HashFunction} HashFunction */
+/** @typedef {import("./LazyBarrel")} LazyBarrelController */
+/** @typedef {import("./Module")} Module */
+/** @typedef {import("./Module").BuildInfo} BuildInfo */
+
+const PLUGIN_NAME = "FlagDependencyExportsPlugin";
+const PLUGIN_LOGGER_NAME = `webpack.${PLUGIN_NAME}`;
+
+/** @typedef {{ etag: string, data: RestoreProvidedData }} MemCacheEntry */
+
+/**
+ * @param {Module} module the module
+ * @param {LazyBarrelController} lazyBarrelController the compilation's lazy barrel controller
+ * @param {HashFunction} hashFunction hash function for long keys
+ * @returns {string} the hash
+ */
+const getLazyRequestsHash = (module, lazyBarrelController, hashFunction) => {
+	const lazyKeys = lazyBarrelController.getLazyRequests(module);
+	if (!lazyKeys) return "";
+	const joined = [...lazyKeys].join("|");
+	if (joined.length < 100) return joined;
+	const hash = createHash(hashFunction);
+	hash.update(joined);
+	return /** @type {string} */ (hash.digest("hex"));
 };
 
 class FlagDependencyExportsPlugin {
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
 	apply(compiler) {
-		compiler.hooks.compilation.tap(
-			"FlagDependencyExportsPlugin",
-			compilation => {
-				compilation.hooks.finishModules.tap(
-					"FlagDependencyExportsPlugin",
-					modules => {
-						const dependencies = new Map();
-
-						const queue = new Queue();
-
-						let module;
-						let moduleWithExports;
-						let moduleProvidedExports;
-
-						const processDependenciesBlock = depBlock => {
-							for (const dep of depBlock.dependencies) {
-								if (processDependency(dep)) return true;
-							}
-							for (const variable of depBlock.variables) {
-								for (const dep of variable.dependencies) {
-									if (processDependency(dep)) return true;
-								}
-							}
-							for (const block of depBlock.blocks) {
-								if (processDependenciesBlock(block)) return true;
-							}
-							return false;
-						};
-
-						const processDependency = dep => {
-							const exportDesc = dep.getExports && dep.getExports();
-							if (!exportDesc) return;
-							moduleWithExports = true;
-							const exports = exportDesc.exports;
-							// break early if it's only in the worst state
-							if (module.buildMeta.providedExports === true) {
-								return true;
+		compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
+			const moduleGraph = compilation.moduleGraph;
+			const cache = compilation.getCache(PLUGIN_NAME);
+			const lazyBarrelController = compilation._lazyBarrelController;
+			const hashFunction = compilation.outputOptions.hashFunction;
+			compilation.hooks.finishModules.tapAsync(
+				PLUGIN_NAME,
+				(modules, callback) => {
+					const logger = compilation.getLogger(PLUGIN_LOGGER_NAME);
+					let statRestoredFromMemCache = 0;
+					let statRestoredFromCache = 0;
+					let statNoExports = 0;
+					let statFlaggedUncached = 0;
+					let statNotCached = 0;
+					let statQueueItemsProcessed = 0;
+
+					const { moduleMemCaches } = compilation;
+
+					/** @type {Queue} */
+					const queue = new Queue();
+					/** @type {Set} */
+					const modulesToAnalyse = new Set();
+
+					// Step 1: Try to restore cached provided export info from cache
+					logger.time("restore cached provided exports");
+					asyncLib.each(
+						/** @type {import("neo-async").IterableCollection} */ (
+							/** @type {unknown} */ (modules)
+						),
+						(module, callback) => {
+							const exportsInfo = moduleGraph.getExportsInfo(module);
+							// If the module doesn't have an exportsType, it's a module
+							// without declared exports.
+							if (
+								(!module.buildMeta || !module.buildMeta.exportsType) &&
+								exportsInfo.otherExportsInfo.provided !== null
+							) {
+								// It's a module without declared exports
+								statNoExports++;
+								exportsInfo.setHasProvideInfo();
+								exportsInfo.setUnknownExportsProvided();
+								return callback();
 							}
-							// break if it should move to the worst state
-							if (exports === true) {
-								module.buildMeta.providedExports = true;
-								notifyDependencies();
-								return true;
+							// If the module has no hash, it's uncacheable
+							if (
+								typeof (/** @type {BuildInfo} */ (module.buildInfo).hash) !==
+								"string"
+							) {
+								statFlaggedUncached++;
+								// Determined below, in a stable order
+								modulesToAnalyse.add(module);
+								exportsInfo.setHasProvideInfo();
+								return callback();
 							}
-							// merge in new exports
-							if (Array.isArray(exports)) {
-								if (addToSet(moduleProvidedExports, exports)) {
-									notifyDependencies();
-								}
+							const memCache = moduleMemCaches && moduleMemCaches.get(module);
+							/** @type {MemCacheEntry | undefined} */
+							const memCacheEntry = memCache && memCache.get(this);
+							const hash = getLazyRequestsHash(
+								module,
+								lazyBarrelController,
+								hashFunction
+							);
+							// validated by the still-lazy request keys: reference-change
+							// detection cannot invalidate the mem cache when the un-lazied
+							// connections are unsafe-cached (they are skipped when comparing
+							// references)
+							if (memCacheEntry !== undefined && memCacheEntry.etag === hash) {
+								statRestoredFromMemCache++;
+								exportsInfo.restoreProvided(memCacheEntry.data);
+								return callback();
 							}
-							// store dependencies
-							const exportDeps = exportDesc.dependencies;
-							if (exportDeps) {
-								for (const exportDependency of exportDeps) {
-									// add dependency for this module
-									const set = dependencies.get(exportDependency);
-									if (set === undefined) {
-										dependencies.set(exportDependency, new Set([module]));
+							const buildHash = /** @type {string} */ (
+								/** @type {BuildInfo} */ (module.buildInfo).hash
+							);
+							cache.get(
+								module.identifier(),
+								hash ? `${buildHash}|${hash}` : buildHash,
+								(err, result) => {
+									if (err) return callback(err);
+
+									if (result !== undefined) {
+										statRestoredFromCache++;
+										exportsInfo.restoreProvided(result);
 									} else {
-										set.add(module);
+										statNotCached++;
+										// Without cached info determine the exports below
+										modulesToAnalyse.add(module);
+										exportsInfo.setHasProvideInfo();
 									}
+									callback();
 								}
-							}
-							return false;
-						};
-
-						const notifyDependencies = () => {
-							const deps = dependencies.get(module);
-							if (deps !== undefined) {
-								for (const dep of deps) {
-									queue.enqueue(dep);
+							);
+						},
+						(err) => {
+							logger.timeEnd("restore cached provided exports");
+							if (err) return callback(err);
+
+							// Seed the queue with targets before the modules that read
+							// them. Enqueueing at the two branches above ordered the work by
+							// how fast each cache lookup answered — one is synchronous, the
+							// other a `cache.get` callback — so the persistent cache decided
+							// it. A module analysed before the module it re-exports from
+							// reads exports that are not determined yet and over-estimates,
+							// and this loop can add exports info but never retract it, so
+							// the over-estimate survives the pass that corrects it. The
+							// roots are sorted so the walk is stable, and post-order visits
+							// each module after everything it points at.
+							/** @type {Set} */
+							const visited = new Set();
+							/** @type {Module[]} */
+							const stack = [];
+							/** @type {Set} */
+							const expanded = new Set();
+							for (const root of [...modulesToAnalyse].sort(
+								compareModulesByIdentifier
+							)) {
+								if (visited.has(root)) continue;
+								stack.push(root);
+								while (stack.length > 0) {
+									const current = stack[stack.length - 1];
+									if (expanded.has(current)) {
+										stack.pop();
+										if (!visited.has(current)) {
+											visited.add(current);
+											if (modulesToAnalyse.has(current)) {
+												queue.enqueue(current);
+											}
+										}
+										continue;
+									}
+									expanded.add(current);
+									for (const connection of moduleGraph.getOutgoingConnections(
+										current
+									)) {
+										const target = connection.module;
+										// A module restored from cache is already final, so
+										// nothing has to be ordered before it and the walk stops
+										// there — on an incremental build that is most of them.
+										if (
+											target !== undefined &&
+											target !== null &&
+											modulesToAnalyse.has(target) &&
+											!expanded.has(target)
+										) {
+											stack.push(target);
+										}
+									}
 								}
 							}
-						};
 
-						// Start with all modules without provided exports
-						for (const module of modules) {
-							if (!module.buildMeta.providedExports) {
-								queue.enqueue(module);
-							}
-						}
+							/** @type {Set} */
+							const modulesToStore = new Set();
+
+							/** @type {Map>} */
+							const dependencies = new Map();
+
+							/** @type {Module} */
+							let module;
+
+							/** @type {ExportsInfo} */
+							let exportsInfo;
+
+							/** @type {Map} */
+							const exportsSpecsFromDependencies = new Map();
+
+							let cacheable = true;
+							let changed = false;
+
+							/**
+							 * Process dependencies block.
+							 * @param {DependenciesBlock} depBlock the dependencies block
+							 * @returns {void}
+							 */
+							const processDependenciesBlock = (depBlock) => {
+								for (const dep of depBlock.dependencies) {
+									processDependency(dep);
+								}
+								for (const block of depBlock.blocks) {
+									processDependenciesBlock(block);
+								}
+							};
+
+							/**
+							 * Process dependency.
+							 * @param {Dependency} dep the dependency
+							 * @returns {void}
+							 */
+							const processDependency = (dep) => {
+								const exportDesc = dep.getExports(moduleGraph);
+								if (!exportDesc) return;
+								exportsSpecsFromDependencies.set(dep, exportDesc);
+							};
+
+							/**
+							 * Process exports spec.
+							 * @param {Dependency} dep dependency
+							 * @param {ExportsSpec} exportDesc info
+							 * @returns {void}
+							 */
+							const processExportsSpec = (dep, exportDesc) => {
+								const exports = exportDesc.exports;
+								const globalCanMangle = exportDesc.canMangle;
+								const globalFrom = exportDesc.from;
+								const globalPriority = exportDesc.priority;
+								const globalTerminalBinding =
+									exportDesc.terminalBinding || false;
+								const globalPure = exportDesc.isPure || false;
+								const exportDeps = exportDesc.dependencies;
+								if (exportDesc.hideExports) {
+									for (const name of exportDesc.hideExports) {
+										const exportInfo = exportsInfo.getExportInfo(name);
+										exportInfo.unsetTarget(dep);
+									}
+								}
+								if (exports === true) {
+									// unknown exports
+									if (
+										exportsInfo.setUnknownExportsProvided(
+											globalCanMangle,
+											exportDesc.excludeExports,
+											globalFrom && dep,
+											globalFrom,
+											globalPriority
+										)
+									) {
+										changed = true;
+									}
+								} else if (Array.isArray(exports)) {
+									/**
+									 * merge in new exports
+									 * @param {ExportsInfo} exportsInfo own exports info
+									 * @param {(ExportSpec | string)[]} exports list of exports
+									 */
+									const mergeExports = (exportsInfo, exports) => {
+										for (const exportNameOrSpec of exports) {
+											/** @type {ExportInfoName} */
+											let name;
+											let canMangle = globalCanMangle;
+											let terminalBinding = globalTerminalBinding;
+											let pure = globalPure;
+											/** @type {ExportSpec["exports"]} */
+											let exports;
+											let from = globalFrom;
+											/** @type {ExportSpec["export"]} */
+											let fromExport;
+											let priority = globalPriority;
+											let hidden = false;
+											/** @type {ExportSpec["inlined"]} */
+											let inlined;
+											if (typeof exportNameOrSpec === "string") {
+												name = exportNameOrSpec;
+											} else {
+												name = exportNameOrSpec.name;
+												if (exportNameOrSpec.canMangle !== undefined) {
+													canMangle = exportNameOrSpec.canMangle;
+												}
+												if (exportNameOrSpec.export !== undefined) {
+													fromExport = exportNameOrSpec.export;
+												}
+												if (exportNameOrSpec.exports !== undefined) {
+													exports = exportNameOrSpec.exports;
+												}
+												if (exportNameOrSpec.from !== undefined) {
+													from = exportNameOrSpec.from;
+												}
+												if (exportNameOrSpec.priority !== undefined) {
+													priority = exportNameOrSpec.priority;
+												}
+												if (exportNameOrSpec.terminalBinding !== undefined) {
+													terminalBinding = exportNameOrSpec.terminalBinding;
+												}
+												if (exportNameOrSpec.isPure !== undefined) {
+													pure = exportNameOrSpec.isPure;
+												}
+												if (exportNameOrSpec.hidden !== undefined) {
+													hidden = exportNameOrSpec.hidden;
+												}
+												if (exportNameOrSpec.inlined !== undefined) {
+													inlined = exportNameOrSpec.inlined;
+												}
+											}
+											const exportInfo = exportsInfo.getExportInfo(name);
 
-						while (queue.length > 0) {
-							module = queue.dequeue();
-
-							if (module.buildMeta.providedExports !== true) {
-								moduleWithExports =
-									module.buildMeta && module.buildMeta.exportsType;
-								moduleProvidedExports = Array.isArray(
-									module.buildMeta.providedExports
-								)
-									? new Set(module.buildMeta.providedExports)
-									: new Set();
+											if (
+												exportInfo.provided === false ||
+												exportInfo.provided === null
+											) {
+												exportInfo.provided = true;
+												changed = true;
+											}
+
+											if (
+												exportInfo.canMangleProvide !== false &&
+												canMangle === false
+											) {
+												exportInfo.canMangleProvide = false;
+												changed = true;
+											}
+
+											if (
+												inlined !== undefined &&
+												exportInfo.canInlineProvide === undefined
+											) {
+												exportInfo.canInlineProvide = inlined;
+												changed = true;
+											}
+
+											if (terminalBinding && !exportInfo.terminalBinding) {
+												exportInfo.terminalBinding = true;
+												changed = true;
+											}
+
+											if (pure && exportInfo.pureProvide !== true) {
+												exportInfo.pureProvide = true;
+												changed = true;
+											}
+
+											if (exports) {
+												const nestedExportsInfo =
+													exportInfo.createNestedExportsInfo();
+												mergeExports(
+													/** @type {ExportsInfo} */ (nestedExportsInfo),
+													exports
+												);
+											}
+
+											if (
+												from &&
+												(hidden
+													? exportInfo.unsetTarget(dep)
+													: exportInfo.setTarget(
+															dep,
+															from,
+															fromExport === undefined ? [name] : fromExport,
+															priority
+														))
+											) {
+												changed = true;
+											}
+
+											// Recalculate target exportsInfo
+											const target = exportInfo.getTarget(moduleGraph);
+											/** @type {undefined | ExportsInfo} */
+											let targetExportsInfo;
+											if (target) {
+												const targetModuleExportsInfo =
+													moduleGraph.getExportsInfo(target.module);
+												targetExportsInfo =
+													targetModuleExportsInfo.getNestedExportsInfo(
+														target.export
+													);
+												// add dependency for this module
+												const set = dependencies.get(target.module);
+												if (set === undefined) {
+													dependencies.set(target.module, new Set([module]));
+												} else {
+													set.add(module);
+												}
+											}
+
+											if (exportInfo.exportsInfoOwned) {
+												if (
+													/** @type {ExportsInfo} */
+													(exportInfo.exportsInfo).setRedirectNamedTo(
+														targetExportsInfo
+													)
+												) {
+													changed = true;
+												}
+											} else if (exportInfo.exportsInfo !== targetExportsInfo) {
+												exportInfo.exportsInfo = targetExportsInfo;
+												changed = true;
+											}
+										}
+									};
+									mergeExports(exportsInfo, exports);
+								}
+								// store dependencies
+								if (exportDeps) {
+									cacheable = false;
+									for (const exportDependency of exportDeps) {
+										// add dependency for this module
+										const set = dependencies.get(exportDependency);
+										if (set === undefined) {
+											dependencies.set(exportDependency, new Set([module]));
+										} else {
+											set.add(module);
+										}
+									}
+								}
+							};
+
+							const notifyDependencies = () => {
+								const deps = dependencies.get(module);
+								if (deps !== undefined) {
+									for (const dep of deps) {
+										queue.enqueue(dep);
+									}
+								}
+							};
+
+							logger.time("figure out provided exports");
+							while (queue.length > 0) {
+								module = /** @type {Module} */ (queue.dequeue());
+
+								statQueueItemsProcessed++;
+
+								exportsInfo = moduleGraph.getExportsInfo(module);
+
+								cacheable = true;
+								changed = false;
+
+								exportsSpecsFromDependencies.clear();
+								moduleGraph.freeze();
 								processDependenciesBlock(module);
-								if (!moduleWithExports) {
-									module.buildMeta.providedExports = true;
+								moduleGraph.unfreeze();
+								for (const [dep, exportsSpec] of exportsSpecsFromDependencies) {
+									processExportsSpec(dep, exportsSpec);
+								}
+
+								if (cacheable) {
+									modulesToStore.add(module);
+								}
+
+								if (changed) {
 									notifyDependencies();
-								} else if (module.buildMeta.providedExports !== true) {
-									module.buildMeta.providedExports = Array.from(
-										moduleProvidedExports
-									);
 								}
 							}
+							logger.timeEnd("figure out provided exports");
+
+							logger.log(
+								`${Math.round(
+									(100 * (statFlaggedUncached + statNotCached)) /
+										(statRestoredFromMemCache +
+											statRestoredFromCache +
+											statNotCached +
+											statFlaggedUncached +
+											statNoExports)
+								)}% of exports of modules have been determined (${statNoExports} no declared exports, ${statNotCached} not cached, ${statFlaggedUncached} flagged uncacheable, ${statRestoredFromCache} from cache, ${statRestoredFromMemCache} from mem cache, ${
+									statQueueItemsProcessed - statNotCached - statFlaggedUncached
+								} additional calculations due to dependencies)`
+							);
+
+							logger.time("store provided exports into cache");
+							asyncLib.each(
+								modulesToStore,
+								(module, callback) => {
+									if (
+										typeof (
+											/** @type {BuildInfo} */
+											(module.buildInfo).hash
+										) !== "string"
+									) {
+										// not cacheable
+										return callback();
+									}
+									const cachedData = moduleGraph
+										.getExportsInfo(module)
+										.getRestoreProvidedData();
+									const memCache =
+										moduleMemCaches && moduleMemCaches.get(module);
+									const hash = getLazyRequestsHash(
+										module,
+										lazyBarrelController,
+										hashFunction
+									);
+									if (memCache) {
+										/** @type {MemCacheEntry} */
+										const entry = {
+											etag: hash,
+											data: cachedData
+										};
+										memCache.set(this, entry);
+									}
+									const buildHash = /** @type {string} */ (
+										/** @type {BuildInfo} */ (module.buildInfo).hash
+									);
+									cache.store(
+										module.identifier(),
+										hash ? `${buildHash}|${hash}` : buildHash,
+										cachedData,
+										callback
+									);
+								},
+								(err) => {
+									logger.timeEnd("store provided exports into cache");
+									callback(err);
+								}
+							);
 						}
-					}
-				);
-				const providedExportsCache = new WeakMap();
-				compilation.hooks.rebuildModule.tap(
-					"FlagDependencyExportsPlugin",
-					module => {
-						providedExportsCache.set(module, module.buildMeta.providedExports);
-					}
+					);
+				}
+			);
+
+			/** @type {WeakMap} */
+			const providedExportsCache = new WeakMap();
+			compilation.hooks.rebuildModule.tap(PLUGIN_NAME, (module) => {
+				providedExportsCache.set(
+					module,
+					moduleGraph.getExportsInfo(module).getRestoreProvidedData()
 				);
-				compilation.hooks.finishRebuildingModule.tap(
-					"FlagDependencyExportsPlugin",
-					module => {
-						module.buildMeta.providedExports = providedExportsCache.get(module);
-					}
+			});
+			compilation.hooks.finishRebuildingModule.tap(PLUGIN_NAME, (module) => {
+				moduleGraph.getExportsInfo(module).restoreProvided(
+					/** @type {RestoreProvidedData} */
+					(providedExportsCache.get(module))
 				);
-			}
-		);
+			});
+		});
 	}
 }
 
diff --git a/lib/FlagDependencyUsagePlugin.js b/lib/FlagDependencyUsagePlugin.js
index e464c1cd6ac..931bb6777dd 100644
--- a/lib/FlagDependencyUsagePlugin.js
+++ b/lib/FlagDependencyUsagePlugin.js
@@ -2,110 +2,440 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
-const addToSet = (a, b) => {
-	for (const item of b) {
-		if (!a.includes(item)) a.push(item);
-	}
-	return a;
-};
+const Dependency = require("./Dependency");
+const { UsageState } = require("./ExportsInfo");
+const ModuleGraphConnection = require("./ModuleGraphConnection");
+const { STAGE_DEFAULT } = require("./OptimizationStages");
+const ArrayQueue = require("./util/ArrayQueue");
+const TupleQueue = require("./util/TupleQueue");
+const TupleSet = require("./util/TupleSet");
+const { getEntryRuntime, mergeRuntimeOwned } = require("./util/runtime");
+
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./DependenciesBlock")} DependenciesBlock */
+/** @typedef {import("./Dependency").ReferencedExport} ReferencedExport */
+/** @typedef {import("./Dependency").ReferencedExports} ReferencedExports */
+/** @typedef {import("./ExportsInfo")} ExportsInfo */
+/** @typedef {import("./Module")} Module */
+/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */
+
+const {
+	NO_EXPORTS_REFERENCED,
+	EXPORTS_OBJECT_REFERENCED,
+	EXPORTS_OBJECT_REFERENCED_MANGLEABLE
+} = Dependency;
+
+const PLUGIN_NAME = "FlagDependencyUsagePlugin";
+const PLUGIN_LOGGER_NAME = `webpack.${PLUGIN_NAME}`;
 
-const isSubset = (biggerSet, subset) => {
-	if (biggerSet === true) return true;
-	if (subset === true) return false;
-	return subset.every(item => biggerSet.indexOf(item) >= 0);
-};
+// Hoisted stateless predicates for setUsedConditionally on the innermost
+// used-export loop, so they aren't re-allocated per export.
+/**
+ * @param {import("./ExportsInfo").UsageStateType} used usage state
+ * @returns {boolean} whether unused
+ */
+const IS_UNUSED = (used) => used === UsageState.Unused;
+/**
+ * @param {import("./ExportsInfo").UsageStateType} used usage state
+ * @returns {boolean} whether not used
+ */
+const IS_NOT_USED = (used) => used !== UsageState.Used;
 
 class FlagDependencyUsagePlugin {
+	/**
+	 * Creates an instance of FlagDependencyUsagePlugin.
+	 * @param {boolean} global do a global analysis instead of per runtime
+	 * @param {boolean=} mangleEscapingNamespaces keep exports mangleable when a module's namespace object escapes
+	 */
+	constructor(global, mangleEscapingNamespaces = false) {
+		/** @type {boolean} */
+		this.global = global;
+		/** @type {boolean} */
+		this.mangleEscapingNamespaces = mangleEscapingNamespaces;
+	}
+
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
 	apply(compiler) {
-		compiler.hooks.compilation.tap("FlagDependencyUsagePlugin", compilation => {
+		compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
+			const moduleGraph = compilation.moduleGraph;
 			compilation.hooks.optimizeDependencies.tap(
-				"FlagDependencyUsagePlugin",
-				modules => {
-					const processModule = (module, usedExports) => {
-						module.used = true;
-						if (module.usedExports === true) return;
-						if (usedExports === true) {
-							module.usedExports = true;
-						} else if (Array.isArray(usedExports)) {
-							const old = module.usedExports ? module.usedExports.length : -1;
-							module.usedExports = addToSet(
-								module.usedExports || [],
-								usedExports
-							);
-							if (module.usedExports.length === old) {
-								return;
+				{ name: PLUGIN_NAME, stage: STAGE_DEFAULT },
+				(modules) => {
+					if (compilation.moduleMemCaches) {
+						throw new Error(
+							"optimization.usedExports can't be used with cacheUnaffected as export usage is a global effect"
+						);
+					}
+
+					const logger = compilation.getLogger(PLUGIN_LOGGER_NAME);
+					/** @type {Map} */
+					const exportInfoToModuleMap = new Map();
+
+					/** @type {TupleQueue} */
+					const queue = new TupleQueue();
+
+					/** @type {TupleSet | undefined} */
+					let walkedSideEffectFreeModules;
+
+					/**
+					 * Process referenced module.
+					 * @param {Module} module module to process
+					 * @param {ReferencedExports} usedExports list of used exports
+					 * @param {RuntimeSpec} runtime part of which runtime
+					 * @returns {void}
+					 */
+					const processReferencedModule = (module, usedExports, runtime) => {
+						const exportsInfo = moduleGraph.getExportsInfo(module);
+						if (usedExports === EXPORTS_OBJECT_REFERENCED_MANGLEABLE) {
+							// The whole namespace object escapes via a reference that codegen
+							// can materialize as a decoupled namespace object. When all exports
+							// are statically known we keep them mangleable instead of marking
+							// them used-in-unknown-way.
+							if (
+								this.mangleEscapingNamespaces &&
+								module.buildMeta &&
+								module.buildMeta.exportsType === "namespace" &&
+								exportsInfo.otherExportsInfo.provided === false
+							) {
+								let changed = exportsInfo.setAllKnownExportsUsed(runtime);
+								// The whole namespace object is observed as a value, so the
+								// module must stay a real ES module namespace at runtime
+								// (keep __esModule / the namespace object, i.e. `r()`).
+								if (
+									exportsInfo
+										.getExportInfo("__esModule")
+										.setUsed(UsageState.Used, runtime)
+								) {
+									changed = true;
+								}
+								// Exports must keep a real binding (not be inlined) so member
+								// access on the namespace has ES namespace semantics, e.g.
+								// `delete ns.x` hits a non-configurable property and throws.
+								for (const exportInfo of exportsInfo.ownedExports) {
+									exportInfo.canInlineUse = false;
+								}
+								if (changed) {
+									queue.enqueue(module, runtime);
+								}
+							} else if (exportsInfo.setUsedInUnknownWay(runtime)) {
+								queue.enqueue(module, runtime);
 							}
-						} else if (Array.isArray(module.usedExports)) {
 							return;
-						} else {
-							module.usedExports = false;
 						}
-
-						// for a module without side effects we stop tracking usage here when no export is used
-						// This module won't be evaluated in this case
-						if (module.factoryMeta.sideEffectFree) {
-							if (module.usedExports === false) return;
-							if (
-								Array.isArray(module.usedExports) &&
-								module.usedExports.length === 0
-							)
+						if (usedExports.length > 0) {
+							if (!module.buildMeta || !module.buildMeta.exportsType) {
+								if (exportsInfo.setUsedWithoutInfo(runtime)) {
+									queue.enqueue(module, runtime);
+								}
 								return;
-						}
+							}
+							for (const usedExportInfo of usedExports) {
+								/** @type {string[]} */
+								let usedExport;
+								let canMangle = true;
+								let canInline = true;
+								if (Array.isArray(usedExportInfo)) {
+									usedExport = usedExportInfo;
+								} else {
+									usedExport = usedExportInfo.name;
+									canMangle = usedExportInfo.canMangle !== false;
+									canInline = usedExportInfo.canInline !== false;
+								}
+								if (usedExport.length === 0) {
+									if (exportsInfo.setUsedInUnknownWay(runtime)) {
+										queue.enqueue(module, runtime);
+									}
+								} else {
+									let currentExportsInfo = exportsInfo;
+									for (let i = 0; i < usedExport.length; i++) {
+										const exportInfo = currentExportsInfo.getExportInfo(
+											usedExport[i]
+										);
+										if (canMangle === false) {
+											exportInfo.canMangleUse = false;
+										}
 
-						queue.push([module, module.usedExports]);
+										if (exportInfo.canInlineUse === undefined) {
+											exportInfo.canInlineUse = canInline;
+										} else if (!canInline) {
+											exportInfo.canInlineUse = false;
+										}
+										const lastOne = i === usedExport.length - 1;
+										if (!lastOne) {
+											const nestedInfo = exportInfo.getNestedExportsInfo();
+											if (nestedInfo) {
+												if (
+													exportInfo.setUsedConditionally(
+														IS_UNUSED,
+														UsageState.OnlyPropertiesUsed,
+														runtime
+													)
+												) {
+													const currentModule =
+														currentExportsInfo === exportsInfo
+															? module
+															: exportInfoToModuleMap.get(currentExportsInfo);
+													if (currentModule) {
+														queue.enqueue(currentModule, runtime);
+													}
+												}
+												currentExportsInfo = nestedInfo;
+												continue;
+											}
+										}
+										if (
+											exportInfo.setUsedConditionally(
+												IS_NOT_USED,
+												UsageState.Used,
+												runtime
+											)
+										) {
+											const currentModule =
+												currentExportsInfo === exportsInfo
+													? module
+													: exportInfoToModuleMap.get(currentExportsInfo);
+											if (currentModule) {
+												queue.enqueue(currentModule, runtime);
+											}
+										}
+										break;
+									}
+								}
+							}
+						} else if (
+							module.factoryMeta !== undefined &&
+							module.factoryMeta.sideEffectFree
+						) {
+							// Nothing is used and the module has no side effects, so it is
+							// not used for them either. An active connection still evaluates
+							// it, so keep walking it to count the references it makes itself.
+							if (walkedSideEffectFreeModules === undefined) {
+								walkedSideEffectFreeModules = new TupleSet();
+							}
+							if (!walkedSideEffectFreeModules.has(module, runtime)) {
+								walkedSideEffectFreeModules.add(module, runtime);
+								queue.enqueue(module, runtime);
+							}
+						} else if (exportsInfo.setUsedForSideEffectsOnly(runtime)) {
+							queue.enqueue(module, runtime);
+						}
 					};
 
-					const processDependenciesBlock = (depBlock, usedExports) => {
-						for (const dep of depBlock.dependencies) {
-							processDependency(dep);
-						}
-						for (const variable of depBlock.variables) {
-							for (const dep of variable.dependencies) {
-								processDependency(dep);
+					/**
+					 * Processes the provided module.
+					 * @param {DependenciesBlock} module the module
+					 * @param {RuntimeSpec} runtime part of which runtime
+					 * @returns {void}
+					 */
+					const processModule = (module, runtime) => {
+						/** @typedef {Map} ExportMaps */
+						/** @type {Map} */
+						const map = new Map();
+						// Modules whose whole namespace object escapes in a mangleable way.
+						// Tracked separately so specific member references are still merged
+						// (and marked used) instead of being dropped by the escape marker.
+						// Lazily allocated — usually empty.
+						/** @type {Set | undefined} */
+						let mangleableEscapeModules;
+
+						/** @type {ArrayQueue} */
+						const queue = new ArrayQueue();
+						queue.enqueue(module);
+						for (;;) {
+							const block = queue.dequeue();
+							if (block === undefined) break;
+							for (const b of block.blocks) {
+								if (b.groupOptions && b.groupOptions.entryOptions) {
+									processModule(
+										b,
+										this.global
+											? undefined
+											: b.groupOptions.entryOptions.runtime || undefined
+									);
+								} else {
+									queue.enqueue(b);
+								}
+							}
+							for (const dep of block.dependencies) {
+								const connection = moduleGraph.getConnection(dep);
+								if (!connection || !connection.module) {
+									continue;
+								}
+								const activeState = connection.getActiveState(runtime);
+								if (activeState === false) continue;
+								const { module } = connection;
+								if (activeState === ModuleGraphConnection.TRANSITIVE_ONLY) {
+									processModule(module, runtime);
+									continue;
+								}
+								const oldReferencedExports = map.get(module);
+								if (oldReferencedExports === EXPORTS_OBJECT_REFERENCED) {
+									continue;
+								}
+								const referencedExports =
+									compilation.getDependencyReferencedExports(dep, runtime);
+								// The non-mangleable whole-object reference is the most
+								// conservative result and always wins.
+								if (referencedExports === EXPORTS_OBJECT_REFERENCED) {
+									map.set(module, EXPORTS_OBJECT_REFERENCED);
+									if (mangleableEscapeModules) {
+										mangleableEscapeModules.delete(module);
+									}
+									continue;
+								}
+								// A mangleable whole-object escape keeps the module's exports
+								// mangleable (applied after the merge). Unlike the conservative
+								// marker it must not drop specific member references: those still
+								// need their own (possibly non-existent) export marked used so
+								// they render as a qualified access, not a bare `undefined`.
+								if (
+									referencedExports === EXPORTS_OBJECT_REFERENCED_MANGLEABLE
+								) {
+									if (mangleableEscapeModules === undefined) {
+										mangleableEscapeModules = new Set();
+									}
+									mangleableEscapeModules.add(module);
+									continue;
+								}
+								if (
+									oldReferencedExports === undefined ||
+									oldReferencedExports === NO_EXPORTS_REFERENCED
+								) {
+									map.set(module, referencedExports);
+								} else if (
+									oldReferencedExports !== undefined &&
+									referencedExports === NO_EXPORTS_REFERENCED
+								) {
+									continue;
+								} else {
+									/** @type {undefined | ExportMaps} */
+									let exportsMap;
+									if (Array.isArray(oldReferencedExports)) {
+										exportsMap = new Map();
+										for (const item of oldReferencedExports) {
+											if (Array.isArray(item)) {
+												exportsMap.set(item.join("\n"), item);
+											} else {
+												exportsMap.set(item.name.join("\n"), item);
+											}
+										}
+										map.set(module, exportsMap);
+									} else {
+										exportsMap = oldReferencedExports;
+									}
+									for (const item of referencedExports) {
+										if (Array.isArray(item)) {
+											const key = item.join("\n");
+											const oldItem = exportsMap.get(key);
+											if (oldItem === undefined) {
+												exportsMap.set(key, item);
+											}
+											// if oldItem is already an array we have to do nothing
+											// if oldItem is an ReferencedExport object, we don't have to do anything
+											// as canMangle defaults to true for arrays
+										} else {
+											const key = item.name.join("\n");
+											const oldItem = exportsMap.get(key);
+											if (oldItem === undefined || Array.isArray(oldItem)) {
+												exportsMap.set(key, item);
+											} else {
+												exportsMap.set(key, {
+													name: item.name,
+													canMangle: item.canMangle && oldItem.canMangle,
+													canInline: item.canInline && oldItem.canInline
+												});
+											}
+										}
+									}
+								}
 							}
 						}
-						for (const block of depBlock.blocks) {
-							queue.push([block, usedExports]);
-						}
-					};
 
-					const processDependency = dep => {
-						// TODO remove dep.getReference existance check in webpack 5
-						const reference = dep.getReference && dep.getReference();
-						if (!reference) return;
-						const module = reference.module;
-						const importedNames = reference.importedNames;
-						const oldUsed = module.used;
-						const oldUsedExports = module.usedExports;
-						if (
-							!oldUsed ||
-							(importedNames &&
-								(!oldUsedExports || !isSubset(oldUsedExports, importedNames)))
-						) {
-							processModule(module, importedNames);
+						for (const [module, referencedExports] of map) {
+							if (Array.isArray(referencedExports)) {
+								processReferencedModule(module, referencedExports, runtime);
+							} else {
+								processReferencedModule(
+									module,
+									[...referencedExports.values()],
+									runtime
+								);
+							}
+						}
+						if (mangleableEscapeModules) {
+							for (const module of mangleableEscapeModules) {
+								processReferencedModule(
+									module,
+									EXPORTS_OBJECT_REFERENCED_MANGLEABLE,
+									runtime
+								);
+							}
 						}
 					};
 
+					logger.time("initialize exports usage");
 					for (const module of modules) {
-						module.used = false;
+						const exportsInfo = moduleGraph.getExportsInfo(module);
+						exportInfoToModuleMap.set(exportsInfo, module);
+						exportsInfo.setHasUseInfo();
 					}
+					logger.timeEnd("initialize exports usage");
+
+					logger.time("trace exports usage in graph");
 
-					const queue = [];
-					for (const preparedEntrypoint of compilation._preparedEntrypoints) {
-						if (preparedEntrypoint.module) {
-							processModule(preparedEntrypoint.module, true);
+					/**
+					 * Process entry dependency.
+					 * @param {Dependency} dep dependency
+					 * @param {RuntimeSpec} runtime runtime
+					 */
+					const processEntryDependency = (dep, runtime) => {
+						const module = moduleGraph.getModule(dep);
+						if (module) {
+							processReferencedModule(module, NO_EXPORTS_REFERENCED, runtime);
+						}
+					};
+					/** @type {RuntimeSpec} */
+					let globalRuntime;
+					for (const [
+						entryName,
+						{ dependencies: deps, includeDependencies: includeDeps, options }
+					] of compilation.entries) {
+						const runtime = this.global
+							? undefined
+							: getEntryRuntime(compilation, entryName, options);
+						for (const dep of deps) {
+							processEntryDependency(dep, runtime);
 						}
+						for (const dep of includeDeps) {
+							processEntryDependency(dep, runtime);
+						}
+						globalRuntime = mergeRuntimeOwned(globalRuntime, runtime);
+					}
+					for (const dep of compilation.globalEntry.dependencies) {
+						processEntryDependency(dep, globalRuntime);
+					}
+					for (const dep of compilation.globalEntry.includeDependencies) {
+						processEntryDependency(dep, globalRuntime);
 					}
 
 					while (queue.length) {
-						const queueItem = queue.pop();
-						processDependenciesBlock(queueItem[0], queueItem[1]);
+						const [module, runtime] = /** @type {[Module, RuntimeSpec]} */ (
+							queue.dequeue()
+						);
+						processModule(module, runtime);
 					}
+					logger.timeEnd("trace exports usage in graph");
 				}
 			);
 		});
 	}
 }
+
 module.exports = FlagDependencyUsagePlugin;
diff --git a/lib/FlagEntryExportAsUsedPlugin.js b/lib/FlagEntryExportAsUsedPlugin.js
new file mode 100644
index 00000000000..18342e7f3a4
--- /dev/null
+++ b/lib/FlagEntryExportAsUsedPlugin.js
@@ -0,0 +1,59 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const { getEntryRuntime } = require("./util/runtime");
+
+/** @typedef {import("./Compiler")} Compiler */
+
+const PLUGIN_NAME = "FlagEntryExportAsUsedPlugin";
+
+class FlagEntryExportAsUsedPlugin {
+	/**
+	 * Creates an instance of FlagEntryExportAsUsedPlugin.
+	 * @param {boolean} nsObjectUsed true, if the ns object is used
+	 * @param {string} explanation explanation for the reason
+	 */
+	constructor(nsObjectUsed, explanation) {
+		/** @type {boolean} */
+		this.nsObjectUsed = nsObjectUsed;
+		/** @type {string} */
+		this.explanation = explanation;
+	}
+
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
+	apply(compiler) {
+		compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
+			const moduleGraph = compilation.moduleGraph;
+			compilation.hooks.seal.tap(PLUGIN_NAME, () => {
+				for (const [
+					entryName,
+					{ dependencies: deps, options }
+				] of compilation.entries) {
+					const runtime = getEntryRuntime(compilation, entryName, options);
+					for (const dep of deps) {
+						const module = moduleGraph.getModule(dep);
+						if (module) {
+							const exportsInfo = moduleGraph.getExportsInfo(module);
+							if (this.nsObjectUsed) {
+								exportsInfo.setUsedInUnknownWay(runtime);
+							} else {
+								exportsInfo.setAllKnownExportsUsed(runtime);
+							}
+							moduleGraph.addExtraReason(module, this.explanation);
+						}
+					}
+				}
+			});
+		});
+	}
+}
+
+module.exports = FlagEntryExportAsUsedPlugin;
diff --git a/lib/FlagInitialModulesAsUsedPlugin.js b/lib/FlagInitialModulesAsUsedPlugin.js
deleted file mode 100644
index 7272ddb3a35..00000000000
--- a/lib/FlagInitialModulesAsUsedPlugin.js
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-class FlagInitialModulesAsUsedPlugin {
-	constructor(explanation) {
-		this.explanation = explanation;
-	}
-
-	apply(compiler) {
-		compiler.hooks.compilation.tap(
-			"FlagInitialModulesAsUsedPlugin",
-			compilation => {
-				compilation.hooks.afterOptimizeChunks.tap(
-					"FlagInitialModulesAsUsedPlugin",
-					chunks => {
-						for (const chunk of chunks) {
-							if (!chunk.isOnlyInitial()) {
-								return;
-							}
-							for (const module of chunk.modulesIterable) {
-								module.used = true;
-								module.usedExports = true;
-								module.addReason(null, null, this.explanation);
-							}
-						}
-					}
-				);
-			}
-		);
-	}
-}
-
-module.exports = FlagInitialModulesAsUsedPlugin;
diff --git a/lib/FunctionModulePlugin.js b/lib/FunctionModulePlugin.js
deleted file mode 100644
index fc4b2707f0a..00000000000
--- a/lib/FunctionModulePlugin.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const FunctionModuleTemplatePlugin = require("./FunctionModuleTemplatePlugin");
-
-class FunctionModulePlugin {
-	apply(compiler) {
-		compiler.hooks.compilation.tap("FunctionModulePlugin", compilation => {
-			new FunctionModuleTemplatePlugin().apply(
-				compilation.moduleTemplates.javascript
-			);
-		});
-	}
-}
-
-module.exports = FunctionModulePlugin;
diff --git a/lib/FunctionModuleTemplatePlugin.js b/lib/FunctionModuleTemplatePlugin.js
deleted file mode 100644
index cf2e1b0eae4..00000000000
--- a/lib/FunctionModuleTemplatePlugin.js
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const { ConcatSource } = require("webpack-sources");
-const Template = require("./Template");
-
-class FunctionModuleTemplatePlugin {
-	apply(moduleTemplate) {
-		moduleTemplate.hooks.render.tap(
-			"FunctionModuleTemplatePlugin",
-			(moduleSource, module) => {
-				const source = new ConcatSource();
-				const args = [module.moduleArgument];
-				// TODO remove HACK checking type for javascript
-				if (module.type && module.type.startsWith("javascript")) {
-					args.push(module.exportsArgument);
-					if (module.hasDependencies(d => d.requireWebpackRequire !== false)) {
-						args.push("__webpack_require__");
-					}
-				} else if (module.type && module.type.startsWith("json")) {
-					// no additional arguments needed
-				} else {
-					args.push(module.exportsArgument, "__webpack_require__");
-				}
-				source.add("/***/ (function(" + args.join(", ") + ") {\n\n");
-				if (module.buildInfo.strict) source.add('"use strict";\n');
-				source.add(moduleSource);
-				source.add("\n\n/***/ })");
-				return source;
-			}
-		);
-
-		moduleTemplate.hooks.package.tap(
-			"FunctionModuleTemplatePlugin",
-			(moduleSource, module) => {
-				if (moduleTemplate.runtimeTemplate.outputOptions.pathinfo) {
-					const source = new ConcatSource();
-					const req = module.readableIdentifier(
-						moduleTemplate.runtimeTemplate.requestShortener
-					);
-					source.add("/*!****" + req.replace(/./g, "*") + "****!*\\\n");
-					source.add("  !*** " + req.replace(/\*\//g, "*_/") + " ***!\n");
-					source.add("  \\****" + req.replace(/./g, "*") + "****/\n");
-					if (
-						Array.isArray(module.buildMeta.providedExports) &&
-						module.buildMeta.providedExports.length === 0
-					) {
-						source.add(Template.toComment("no exports provided") + "\n");
-					} else if (Array.isArray(module.buildMeta.providedExports)) {
-						source.add(
-							Template.toComment(
-								"exports provided: " +
-									module.buildMeta.providedExports.join(", ")
-							) + "\n"
-						);
-					} else if (module.buildMeta.providedExports) {
-						source.add(Template.toComment("no static exports found") + "\n");
-					}
-					if (
-						Array.isArray(module.usedExports) &&
-						module.usedExports.length === 0
-					) {
-						source.add(Template.toComment("no exports used") + "\n");
-					} else if (Array.isArray(module.usedExports)) {
-						source.add(
-							Template.toComment(
-								"exports used: " + module.usedExports.join(", ")
-							) + "\n"
-						);
-					} else if (module.usedExports) {
-						source.add(Template.toComment("all exports used") + "\n");
-					}
-					if (module.optimizationBailout) {
-						for (const text of module.optimizationBailout) {
-							let code;
-							if (typeof text === "function") {
-								code = text(moduleTemplate.runtimeTemplate.requestShortener);
-							} else {
-								code = text;
-							}
-							source.add(Template.toComment(`${code}`) + "\n");
-						}
-					}
-					source.add(moduleSource);
-					return source;
-				}
-				return moduleSource;
-			}
-		);
-
-		moduleTemplate.hooks.hash.tap("FunctionModuleTemplatePlugin", hash => {
-			hash.update("FunctionModuleTemplatePlugin");
-			hash.update("2");
-		});
-	}
-}
-module.exports = FunctionModuleTemplatePlugin;
diff --git a/lib/Generator.js b/lib/Generator.js
index 441f22872de..310dcb4feb5 100644
--- a/lib/Generator.js
+++ b/lib/Generator.js
@@ -2,50 +2,207 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
-/** @typedef {import("./Module")} Module */
-/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */
+const { JAVASCRIPT_TYPE } = require("./ModuleSourceTypeConstants");
+
 /** @typedef {import("webpack-sources").Source} Source */
+/** @typedef {import("./ChunkGraph")} ChunkGraph */
+/** @typedef {import("./CodeGenerationResults")} CodeGenerationResults */
+/** @typedef {import("./ConcatenationScope")} ConcatenationScope */
+/** @typedef {import("./DependencyTemplates")} DependencyTemplates */
+/** @typedef {import("./Module").CodeGenerationResultData} CodeGenerationResultData */
+/** @typedef {import("./Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */
+/** @typedef {import("./Module").RuntimeRequirements} RuntimeRequirements */
+/** @typedef {import("./Module").SourceType} SourceType */
+/** @typedef {import("./Module").SourceTypes} SourceTypes */
+/** @typedef {import("./ModuleGraph")} ModuleGraph */
+/** @typedef {import("./NormalModule")} NormalModule */
+/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */
+/** @typedef {import("./util/Hash")} Hash */
+/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */
+
+/**
+ * Defines the generate context type used by this module.
+ * @typedef {object} GenerateContext
+ * @property {DependencyTemplates} dependencyTemplates mapping from dependencies to templates
+ * @property {RuntimeTemplate} runtimeTemplate the runtime template
+ * @property {ModuleGraph} moduleGraph the module graph
+ * @property {ChunkGraph} chunkGraph the chunk graph
+ * @property {RuntimeRequirements} runtimeRequirements the requirements for runtime
+ * @property {RuntimeSpec} runtime the runtime
+ * @property {ConcatenationScope=} concatenationScope when in concatenated module, information about other concatenated modules
+ * @property {CodeGenerationResults=} codeGenerationResults code generation results of other modules (need to have a codeGenerationDependency to use that)
+ * @property {SourceType} type which kind of code should be generated
+ * @property {() => CodeGenerationResultData=} getData get access to the code generation data
+ */
 
 /**
- *
+ * Defines the generate error fn callback.
+ * @callback GenerateErrorFn
+ * @param {Error} error the error
+ * @param {NormalModule} module module for which the code should be generated
+ * @param {GenerateContext} generateContext context for generate
+ * @returns {Source | null} generated code
  */
+
+/**
+ * Represents the generator runtime component.
+ * @typedef {object} UpdateHashContext
+ * @property {NormalModule} module the module
+ * @property {ChunkGraph} chunkGraph
+ * @property {RuntimeSpec} runtime
+ * @property {RuntimeTemplate=} runtimeTemplate
+ */
+
 class Generator {
+	/**
+	 * Returns generator by type.
+	 * @param {{ [key in SourceType]?: Generator }} map map of types
+	 * @returns {ByTypeGenerator} generator by type
+	 */
 	static byType(map) {
 		return new ByTypeGenerator(map);
 	}
 
+	/* istanbul ignore next */
+	/**
+	 * Returns the source types available for this module.
+	 * @abstract
+	 * @param {NormalModule} module fresh module
+	 * @returns {SourceTypes} available types (do not mutate)
+	 */
+	getTypes(module) {
+		const AbstractMethodError = require("./errors/AbstractMethodError");
+
+		throw new AbstractMethodError();
+	}
+
+	/**
+	 * @returns {boolean} whether getTypes() depends on the module's incoming connections
+	 */
+	getTypesDependOnIncomingConnections() {
+		return false;
+	}
+
+	/* istanbul ignore next */
+	/**
+	 * Returns the estimated size for the requested source type.
+	 * @abstract
+	 * @param {NormalModule} module the module
+	 * @param {SourceType=} type source type
+	 * @returns {number} estimate size of the module
+	 */
+	getSize(module, type) {
+		const AbstractMethodError = require("./errors/AbstractMethodError");
+
+		throw new AbstractMethodError();
+	}
+
+	/* istanbul ignore next */
 	/**
+	 * Generates generated code for this runtime module.
 	 * @abstract
-	 * @param {Module} module module for which the code should be generated
-	 * @param {Map} dependencyTemplates mapping from dependencies to templates
-	 * @param {RuntimeTemplate} runtimeTemplate the runtime template
-	 * @param {string} type which kind of code should be generated
-	 * @returns {Source} generated code
+	 * @param {NormalModule} module module for which the code should be generated
+	 * @param {GenerateContext} generateContext context for generate
+	 * @returns {Source | null} generated code
+	 */
+	generate(
+		module,
+		{ dependencyTemplates, runtimeTemplate, moduleGraph, type }
+	) {
+		const AbstractMethodError = require("./errors/AbstractMethodError");
+
+		throw new AbstractMethodError();
+	}
+
+	/**
+	 * Returns the reason this module cannot be concatenated, when one exists.
+	 * @param {NormalModule} module module for which the bailout reason should be determined
+	 * @param {ConcatenationBailoutReasonContext} context context
+	 * @returns {string | undefined} reason why this module can't be concatenated, undefined when it can be concatenated
+	 */
+	getConcatenationBailoutReason(module, context) {
+		return `Module Concatenation is not implemented for ${this.constructor.name}`;
+	}
+
+	/**
+	 * Updates the hash with the data contributed by this instance.
+	 * @param {Hash} hash hash that will be modified
+	 * @param {UpdateHashContext} updateHashContext context for updating hash
 	 */
-	generate(module, dependencyTemplates, runtimeTemplate, type) {
-		throw new Error("Generator.generate: must be overriden");
+	updateHash(hash, { module, runtime }) {
+		// no nothing
+	}
+}
+
+/**
+ * @this {ByTypeGenerator}
+ * @type {GenerateErrorFn}
+ */
+function generateError(error, module, generateContext) {
+	const type = generateContext.type;
+	const generator =
+		/** @type {Generator & { generateError?: GenerateErrorFn }} */
+		(this.map[type]);
+	if (!generator) {
+		throw new Error(`Generator.byType: no generator specified for ${type}`);
+	}
+	if (typeof generator.generateError === "undefined") {
+		return null;
 	}
+	return generator.generateError(error, module, generateContext);
 }
 
 class ByTypeGenerator extends Generator {
+	/**
+	 * Creates an instance of ByTypeGenerator.
+	 * @param {{ [key in SourceType]?: Generator }} map map of types
+	 */
 	constructor(map) {
 		super();
 		this.map = map;
+		/** @type {SourceTypes} */
+		this._types = /** @type {SourceTypes} */ (new Set(Object.keys(map)));
+		/** @type {GenerateErrorFn | undefined} */
+		this.generateError = generateError.bind(this);
+	}
+
+	/**
+	 * Returns the source types available for this module.
+	 * @param {NormalModule} module fresh module
+	 * @returns {SourceTypes} available types (do not mutate)
+	 */
+	getTypes(module) {
+		return this._types;
 	}
 
-	generate(module, dependencyTemplates, runtimeTemplate, type) {
+	/**
+	 * Returns the estimated size for the requested source type.
+	 * @param {NormalModule} module the module
+	 * @param {SourceType=} type source type
+	 * @returns {number} estimate size of the module
+	 */
+	getSize(module, type = JAVASCRIPT_TYPE) {
+		const t = type;
+		const generator = this.map[t];
+		return generator ? generator.getSize(module, t) : 0;
+	}
+
+	/**
+	 * Generates generated code for this runtime module.
+	 * @param {NormalModule} module module for which the code should be generated
+	 * @param {GenerateContext} generateContext context for generate
+	 * @returns {Source | null} generated code
+	 */
+	generate(module, generateContext) {
+		const type = generateContext.type;
 		const generator = this.map[type];
 		if (!generator) {
 			throw new Error(`Generator.byType: no generator specified for ${type}`);
 		}
-		return generator.generate(
-			module,
-			dependencyTemplates,
-			runtimeTemplate,
-			type
-		);
+		return generator.generate(module, generateContext);
 	}
 }
 
diff --git a/lib/GraphHelpers.js b/lib/GraphHelpers.js
deleted file mode 100644
index d668159705a..00000000000
--- a/lib/GraphHelpers.js
+++ /dev/null
@@ -1,64 +0,0 @@
-/** @typedef {import("./Chunk")} Chunk */
-/** @typedef {import("./ChunkGroup")} ChunkGroup */
-/** @typedef {import("./Module")} Module */
-/** @typedef {import("./DependenciesBlock")} DependenciesBlock */
-
-/**
- * @param {ChunkGroup} chunkGroup the ChunkGroup to connect
- * @param {Chunk} chunk chunk to tie to ChunkGroup
- * @returns {void}
- */
-const connectChunkGroupAndChunk = (chunkGroup, chunk) => {
-	if (chunkGroup.pushChunk(chunk)) {
-		chunk.addGroup(chunkGroup);
-	}
-};
-
-/**
- * @param {ChunkGroup} parent parent ChunkGroup to connect
- * @param {ChunkGroup} child child ChunkGroup to connect
- * @returns {void}
- */
-const connectChunkGroupParentAndChild = (parent, child) => {
-	if (parent.addChild(child)) {
-		child.addParent(parent);
-	}
-};
-
-/**
- * @param {Chunk} chunk Chunk to connect to Module
- * @param {Module} module Module to connect to Chunk
- * @returns {void}
- */
-const connectChunkAndModule = (chunk, module) => {
-	if (module.addChunk(chunk)) {
-		chunk.addModule(module);
-	}
-};
-
-/**
- * @param {Chunk} chunk Chunk being disconnected
- * @param {Module} module Module being disconnected
- * @returns {void}
- */
-const disconnectChunkAndModule = (chunk, module) => {
-	chunk.removeModule(module);
-	module.removeChunk(chunk);
-};
-
-/**
- * @param {DependenciesBlock} depBlock DepBlock being tied to ChunkGroup
- * @param {ChunkGroup} chunkGroup ChunkGroup being tied to DepBlock
- * @returns {void}
- */
-const connectDependenciesBlockAndChunkGroup = (depBlock, chunkGroup) => {
-	if (chunkGroup.addBlock(depBlock)) {
-		depBlock.chunkGroup = chunkGroup;
-	}
-};
-
-exports.connectChunkGroupAndChunk = connectChunkGroupAndChunk;
-exports.connectChunkGroupParentAndChild = connectChunkGroupParentAndChild;
-exports.connectChunkAndModule = connectChunkAndModule;
-exports.disconnectChunkAndModule = disconnectChunkAndModule;
-exports.connectDependenciesBlockAndChunkGroup = connectDependenciesBlockAndChunkGroup;
diff --git a/lib/HarmonyLinkingError.js b/lib/HarmonyLinkingError.js
deleted file mode 100644
index 78ce16dde45..00000000000
--- a/lib/HarmonyLinkingError.js
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-*/
-"use strict";
-
-const WebpackError = require("./WebpackError");
-
-module.exports = class HarmonyLinkingError extends WebpackError {
-	/** @param {string} message Error message */
-	constructor(message) {
-		super(message);
-		this.name = "HarmonyLinkingError";
-		this.hideStack = true;
-
-		Error.captureStackTrace(this, this.constructor);
-	}
-};
diff --git a/lib/HashedModuleIdsPlugin.js b/lib/HashedModuleIdsPlugin.js
deleted file mode 100644
index aeb9f1d9346..00000000000
--- a/lib/HashedModuleIdsPlugin.js
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-const createHash = require("./util/createHash");
-
-const validateOptions = require("schema-utils");
-const schema = require("../schemas/plugins/HashedModuleIdsPlugin.json");
-
-class HashedModuleIdsPlugin {
-	constructor(options) {
-		validateOptions(schema, options || {}, "Hashed Module Ids Plugin");
-
-		this.options = Object.assign(
-			{
-				context: null,
-				hashFunction: "md4",
-				hashDigest: "base64",
-				hashDigestLength: 4
-			},
-			options
-		);
-	}
-
-	apply(compiler) {
-		const options = this.options;
-		compiler.hooks.compilation.tap("HashedModuleIdsPlugin", compilation => {
-			const usedIds = new Set();
-			compilation.hooks.beforeModuleIds.tap(
-				"HashedModuleIdsPlugin",
-				modules => {
-					for (const module of modules) {
-						if (module.id === null && module.libIdent) {
-							const id = module.libIdent({
-								context: this.options.context || compiler.options.context
-							});
-							const hash = createHash(options.hashFunction);
-							hash.update(id);
-							const hashId = hash.digest(options.hashDigest);
-							let len = options.hashDigestLength;
-							while (usedIds.has(hashId.substr(0, len))) len++;
-							module.id = hashId.substr(0, len);
-							usedIds.add(module.id);
-						}
-					}
-				}
-			);
-		});
-	}
-}
-
-module.exports = HashedModuleIdsPlugin;
diff --git a/lib/HotModuleReplacement.runtime.js b/lib/HotModuleReplacement.runtime.js
deleted file mode 100644
index 4afddedc939..00000000000
--- a/lib/HotModuleReplacement.runtime.js
+++ /dev/null
@@ -1,642 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-/*global $hash$ $requestTimeout$ installedModules $require$ hotDownloadManifest hotDownloadUpdateChunk hotDisposeChunk modules */
-module.exports = function() {
-	var hotApplyOnUpdate = true;
-	var hotCurrentHash = $hash$; // eslint-disable-line no-unused-vars
-	var hotRequestTimeout = $requestTimeout$;
-	var hotCurrentModuleData = {};
-	var hotCurrentChildModule; // eslint-disable-line no-unused-vars
-	var hotCurrentParents = []; // eslint-disable-line no-unused-vars
-	var hotCurrentParentsTemp = []; // eslint-disable-line no-unused-vars
-
-	// eslint-disable-next-line no-unused-vars
-	function hotCreateRequire(moduleId) {
-		var me = installedModules[moduleId];
-		if (!me) return $require$;
-		var fn = function(request) {
-			if (me.hot.active) {
-				if (installedModules[request]) {
-					if (installedModules[request].parents.indexOf(moduleId) === -1) {
-						installedModules[request].parents.push(moduleId);
-					}
-				} else {
-					hotCurrentParents = [moduleId];
-					hotCurrentChildModule = request;
-				}
-				if (me.children.indexOf(request) === -1) {
-					me.children.push(request);
-				}
-			} else {
-				console.warn(
-					"[HMR] unexpected require(" +
-						request +
-						") from disposed module " +
-						moduleId
-				);
-				hotCurrentParents = [];
-			}
-			return $require$(request);
-		};
-		var ObjectFactory = function ObjectFactory(name) {
-			return {
-				configurable: true,
-				enumerable: true,
-				get: function() {
-					return $require$[name];
-				},
-				set: function(value) {
-					$require$[name] = value;
-				}
-			};
-		};
-		for (var name in $require$) {
-			if (
-				Object.prototype.hasOwnProperty.call($require$, name) &&
-				name !== "e" &&
-				name !== "t"
-			) {
-				Object.defineProperty(fn, name, ObjectFactory(name));
-			}
-		}
-		fn.e = function(chunkId) {
-			if (hotStatus === "ready") hotSetStatus("prepare");
-			hotChunksLoading++;
-			return $require$.e(chunkId).then(finishChunkLoading, function(err) {
-				finishChunkLoading();
-				throw err;
-			});
-
-			function finishChunkLoading() {
-				hotChunksLoading--;
-				if (hotStatus === "prepare") {
-					if (!hotWaitingFilesMap[chunkId]) {
-						hotEnsureUpdateChunk(chunkId);
-					}
-					if (hotChunksLoading === 0 && hotWaitingFiles === 0) {
-						hotUpdateDownloaded();
-					}
-				}
-			}
-		};
-		fn.t = function(value, mode) {
-			if (mode & 1) value = fn(value);
-			return $require$.t(value, mode & ~1);
-		};
-		return fn;
-	}
-
-	// eslint-disable-next-line no-unused-vars
-	function hotCreateModule(moduleId) {
-		var hot = {
-			// private stuff
-			_acceptedDependencies: {},
-			_declinedDependencies: {},
-			_selfAccepted: false,
-			_selfDeclined: false,
-			_disposeHandlers: [],
-			_main: hotCurrentChildModule !== moduleId,
-
-			// Module API
-			active: true,
-			accept: function(dep, callback) {
-				if (typeof dep === "undefined") hot._selfAccepted = true;
-				else if (typeof dep === "function") hot._selfAccepted = dep;
-				else if (typeof dep === "object")
-					for (var i = 0; i < dep.length; i++)
-						hot._acceptedDependencies[dep[i]] = callback || function() {};
-				else hot._acceptedDependencies[dep] = callback || function() {};
-			},
-			decline: function(dep) {
-				if (typeof dep === "undefined") hot._selfDeclined = true;
-				else if (typeof dep === "object")
-					for (var i = 0; i < dep.length; i++)
-						hot._declinedDependencies[dep[i]] = true;
-				else hot._declinedDependencies[dep] = true;
-			},
-			dispose: function(callback) {
-				hot._disposeHandlers.push(callback);
-			},
-			addDisposeHandler: function(callback) {
-				hot._disposeHandlers.push(callback);
-			},
-			removeDisposeHandler: function(callback) {
-				var idx = hot._disposeHandlers.indexOf(callback);
-				if (idx >= 0) hot._disposeHandlers.splice(idx, 1);
-			},
-
-			// Management API
-			check: hotCheck,
-			apply: hotApply,
-			status: function(l) {
-				if (!l) return hotStatus;
-				hotStatusHandlers.push(l);
-			},
-			addStatusHandler: function(l) {
-				hotStatusHandlers.push(l);
-			},
-			removeStatusHandler: function(l) {
-				var idx = hotStatusHandlers.indexOf(l);
-				if (idx >= 0) hotStatusHandlers.splice(idx, 1);
-			},
-
-			//inherit from previous dispose call
-			data: hotCurrentModuleData[moduleId]
-		};
-		hotCurrentChildModule = undefined;
-		return hot;
-	}
-
-	var hotStatusHandlers = [];
-	var hotStatus = "idle";
-
-	function hotSetStatus(newStatus) {
-		hotStatus = newStatus;
-		for (var i = 0; i < hotStatusHandlers.length; i++)
-			hotStatusHandlers[i].call(null, newStatus);
-	}
-
-	// while downloading
-	var hotWaitingFiles = 0;
-	var hotChunksLoading = 0;
-	var hotWaitingFilesMap = {};
-	var hotRequestedFilesMap = {};
-	var hotAvailableFilesMap = {};
-	var hotDeferred;
-
-	// The update info
-	var hotUpdate, hotUpdateNewHash;
-
-	function toModuleId(id) {
-		var isNumber = +id + "" === id;
-		return isNumber ? +id : id;
-	}
-
-	function hotCheck(apply) {
-		if (hotStatus !== "idle") {
-			throw new Error("check() is only allowed in idle status");
-		}
-		hotApplyOnUpdate = apply;
-		hotSetStatus("check");
-		return hotDownloadManifest(hotRequestTimeout).then(function(update) {
-			if (!update) {
-				hotSetStatus("idle");
-				return null;
-			}
-			hotRequestedFilesMap = {};
-			hotWaitingFilesMap = {};
-			hotAvailableFilesMap = update.c;
-			hotUpdateNewHash = update.h;
-
-			hotSetStatus("prepare");
-			var promise = new Promise(function(resolve, reject) {
-				hotDeferred = {
-					resolve: resolve,
-					reject: reject
-				};
-			});
-			hotUpdate = {};
-			/*foreachInstalledChunks*/
-			{
-				// eslint-disable-line no-lone-blocks
-				/*globals chunkId */
-				hotEnsureUpdateChunk(chunkId);
-			}
-			if (
-				hotStatus === "prepare" &&
-				hotChunksLoading === 0 &&
-				hotWaitingFiles === 0
-			) {
-				hotUpdateDownloaded();
-			}
-			return promise;
-		});
-	}
-
-	// eslint-disable-next-line no-unused-vars
-	function hotAddUpdateChunk(chunkId, moreModules) {
-		if (!hotAvailableFilesMap[chunkId] || !hotRequestedFilesMap[chunkId])
-			return;
-		hotRequestedFilesMap[chunkId] = false;
-		for (var moduleId in moreModules) {
-			if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
-				hotUpdate[moduleId] = moreModules[moduleId];
-			}
-		}
-		if (--hotWaitingFiles === 0 && hotChunksLoading === 0) {
-			hotUpdateDownloaded();
-		}
-	}
-
-	function hotEnsureUpdateChunk(chunkId) {
-		if (!hotAvailableFilesMap[chunkId]) {
-			hotWaitingFilesMap[chunkId] = true;
-		} else {
-			hotRequestedFilesMap[chunkId] = true;
-			hotWaitingFiles++;
-			hotDownloadUpdateChunk(chunkId);
-		}
-	}
-
-	function hotUpdateDownloaded() {
-		hotSetStatus("ready");
-		var deferred = hotDeferred;
-		hotDeferred = null;
-		if (!deferred) return;
-		if (hotApplyOnUpdate) {
-			// Wrap deferred object in Promise to mark it as a well-handled Promise to
-			// avoid triggering uncaught exception warning in Chrome.
-			// See https://bugs.chromium.org/p/chromium/issues/detail?id=465666
-			Promise.resolve()
-				.then(function() {
-					return hotApply(hotApplyOnUpdate);
-				})
-				.then(
-					function(result) {
-						deferred.resolve(result);
-					},
-					function(err) {
-						deferred.reject(err);
-					}
-				);
-		} else {
-			var outdatedModules = [];
-			for (var id in hotUpdate) {
-				if (Object.prototype.hasOwnProperty.call(hotUpdate, id)) {
-					outdatedModules.push(toModuleId(id));
-				}
-			}
-			deferred.resolve(outdatedModules);
-		}
-	}
-
-	function hotApply(options) {
-		if (hotStatus !== "ready")
-			throw new Error("apply() is only allowed in ready status");
-		options = options || {};
-
-		var cb;
-		var i;
-		var j;
-		var module;
-		var moduleId;
-
-		function getAffectedStuff(updateModuleId) {
-			var outdatedModules = [updateModuleId];
-			var outdatedDependencies = {};
-
-			var queue = outdatedModules.slice().map(function(id) {
-				return {
-					chain: [id],
-					id: id
-				};
-			});
-			while (queue.length > 0) {
-				var queueItem = queue.pop();
-				var moduleId = queueItem.id;
-				var chain = queueItem.chain;
-				module = installedModules[moduleId];
-				if (!module || module.hot._selfAccepted) continue;
-				if (module.hot._selfDeclined) {
-					return {
-						type: "self-declined",
-						chain: chain,
-						moduleId: moduleId
-					};
-				}
-				if (module.hot._main) {
-					return {
-						type: "unaccepted",
-						chain: chain,
-						moduleId: moduleId
-					};
-				}
-				for (var i = 0; i < module.parents.length; i++) {
-					var parentId = module.parents[i];
-					var parent = installedModules[parentId];
-					if (!parent) continue;
-					if (parent.hot._declinedDependencies[moduleId]) {
-						return {
-							type: "declined",
-							chain: chain.concat([parentId]),
-							moduleId: moduleId,
-							parentId: parentId
-						};
-					}
-					if (outdatedModules.indexOf(parentId) !== -1) continue;
-					if (parent.hot._acceptedDependencies[moduleId]) {
-						if (!outdatedDependencies[parentId])
-							outdatedDependencies[parentId] = [];
-						addAllToSet(outdatedDependencies[parentId], [moduleId]);
-						continue;
-					}
-					delete outdatedDependencies[parentId];
-					outdatedModules.push(parentId);
-					queue.push({
-						chain: chain.concat([parentId]),
-						id: parentId
-					});
-				}
-			}
-
-			return {
-				type: "accepted",
-				moduleId: updateModuleId,
-				outdatedModules: outdatedModules,
-				outdatedDependencies: outdatedDependencies
-			};
-		}
-
-		function addAllToSet(a, b) {
-			for (var i = 0; i < b.length; i++) {
-				var item = b[i];
-				if (a.indexOf(item) === -1) a.push(item);
-			}
-		}
-
-		// at begin all updates modules are outdated
-		// the "outdated" status can propagate to parents if they don't accept the children
-		var outdatedDependencies = {};
-		var outdatedModules = [];
-		var appliedUpdate = {};
-
-		var warnUnexpectedRequire = function warnUnexpectedRequire() {
-			console.warn(
-				"[HMR] unexpected require(" + result.moduleId + ") to disposed module"
-			);
-		};
-
-		for (var id in hotUpdate) {
-			if (Object.prototype.hasOwnProperty.call(hotUpdate, id)) {
-				moduleId = toModuleId(id);
-				/** @type {TODO} */
-				var result;
-				if (hotUpdate[id]) {
-					result = getAffectedStuff(moduleId);
-				} else {
-					result = {
-						type: "disposed",
-						moduleId: id
-					};
-				}
-				/** @type {Error|false} */
-				var abortError = false;
-				var doApply = false;
-				var doDispose = false;
-				var chainInfo = "";
-				if (result.chain) {
-					chainInfo = "\nUpdate propagation: " + result.chain.join(" -> ");
-				}
-				switch (result.type) {
-					case "self-declined":
-						if (options.onDeclined) options.onDeclined(result);
-						if (!options.ignoreDeclined)
-							abortError = new Error(
-								"Aborted because of self decline: " +
-									result.moduleId +
-									chainInfo
-							);
-						break;
-					case "declined":
-						if (options.onDeclined) options.onDeclined(result);
-						if (!options.ignoreDeclined)
-							abortError = new Error(
-								"Aborted because of declined dependency: " +
-									result.moduleId +
-									" in " +
-									result.parentId +
-									chainInfo
-							);
-						break;
-					case "unaccepted":
-						if (options.onUnaccepted) options.onUnaccepted(result);
-						if (!options.ignoreUnaccepted)
-							abortError = new Error(
-								"Aborted because " + moduleId + " is not accepted" + chainInfo
-							);
-						break;
-					case "accepted":
-						if (options.onAccepted) options.onAccepted(result);
-						doApply = true;
-						break;
-					case "disposed":
-						if (options.onDisposed) options.onDisposed(result);
-						doDispose = true;
-						break;
-					default:
-						throw new Error("Unexception type " + result.type);
-				}
-				if (abortError) {
-					hotSetStatus("abort");
-					return Promise.reject(abortError);
-				}
-				if (doApply) {
-					appliedUpdate[moduleId] = hotUpdate[moduleId];
-					addAllToSet(outdatedModules, result.outdatedModules);
-					for (moduleId in result.outdatedDependencies) {
-						if (
-							Object.prototype.hasOwnProperty.call(
-								result.outdatedDependencies,
-								moduleId
-							)
-						) {
-							if (!outdatedDependencies[moduleId])
-								outdatedDependencies[moduleId] = [];
-							addAllToSet(
-								outdatedDependencies[moduleId],
-								result.outdatedDependencies[moduleId]
-							);
-						}
-					}
-				}
-				if (doDispose) {
-					addAllToSet(outdatedModules, [result.moduleId]);
-					appliedUpdate[moduleId] = warnUnexpectedRequire;
-				}
-			}
-		}
-
-		// Store self accepted outdated modules to require them later by the module system
-		var outdatedSelfAcceptedModules = [];
-		for (i = 0; i < outdatedModules.length; i++) {
-			moduleId = outdatedModules[i];
-			if (
-				installedModules[moduleId] &&
-				installedModules[moduleId].hot._selfAccepted
-			)
-				outdatedSelfAcceptedModules.push({
-					module: moduleId,
-					errorHandler: installedModules[moduleId].hot._selfAccepted
-				});
-		}
-
-		// Now in "dispose" phase
-		hotSetStatus("dispose");
-		Object.keys(hotAvailableFilesMap).forEach(function(chunkId) {
-			if (hotAvailableFilesMap[chunkId] === false) {
-				hotDisposeChunk(chunkId);
-			}
-		});
-
-		var idx;
-		var queue = outdatedModules.slice();
-		while (queue.length > 0) {
-			moduleId = queue.pop();
-			module = installedModules[moduleId];
-			if (!module) continue;
-
-			var data = {};
-
-			// Call dispose handlers
-			var disposeHandlers = module.hot._disposeHandlers;
-			for (j = 0; j < disposeHandlers.length; j++) {
-				cb = disposeHandlers[j];
-				cb(data);
-			}
-			hotCurrentModuleData[moduleId] = data;
-
-			// disable module (this disables requires from this module)
-			module.hot.active = false;
-
-			// remove module from cache
-			delete installedModules[moduleId];
-
-			// when disposing there is no need to call dispose handler
-			delete outdatedDependencies[moduleId];
-
-			// remove "parents" references from all children
-			for (j = 0; j < module.children.length; j++) {
-				var child = installedModules[module.children[j]];
-				if (!child) continue;
-				idx = child.parents.indexOf(moduleId);
-				if (idx >= 0) {
-					child.parents.splice(idx, 1);
-				}
-			}
-		}
-
-		// remove outdated dependency from module children
-		var dependency;
-		var moduleOutdatedDependencies;
-		for (moduleId in outdatedDependencies) {
-			if (
-				Object.prototype.hasOwnProperty.call(outdatedDependencies, moduleId)
-			) {
-				module = installedModules[moduleId];
-				if (module) {
-					moduleOutdatedDependencies = outdatedDependencies[moduleId];
-					for (j = 0; j < moduleOutdatedDependencies.length; j++) {
-						dependency = moduleOutdatedDependencies[j];
-						idx = module.children.indexOf(dependency);
-						if (idx >= 0) module.children.splice(idx, 1);
-					}
-				}
-			}
-		}
-
-		// Not in "apply" phase
-		hotSetStatus("apply");
-
-		hotCurrentHash = hotUpdateNewHash;
-
-		// insert new code
-		for (moduleId in appliedUpdate) {
-			if (Object.prototype.hasOwnProperty.call(appliedUpdate, moduleId)) {
-				modules[moduleId] = appliedUpdate[moduleId];
-			}
-		}
-
-		// call accept handlers
-		var error = null;
-		for (moduleId in outdatedDependencies) {
-			if (
-				Object.prototype.hasOwnProperty.call(outdatedDependencies, moduleId)
-			) {
-				module = installedModules[moduleId];
-				if (module) {
-					moduleOutdatedDependencies = outdatedDependencies[moduleId];
-					var callbacks = [];
-					for (i = 0; i < moduleOutdatedDependencies.length; i++) {
-						dependency = moduleOutdatedDependencies[i];
-						cb = module.hot._acceptedDependencies[dependency];
-						if (cb) {
-							if (callbacks.indexOf(cb) !== -1) continue;
-							callbacks.push(cb);
-						}
-					}
-					for (i = 0; i < callbacks.length; i++) {
-						cb = callbacks[i];
-						try {
-							cb(moduleOutdatedDependencies);
-						} catch (err) {
-							if (options.onErrored) {
-								options.onErrored({
-									type: "accept-errored",
-									moduleId: moduleId,
-									dependencyId: moduleOutdatedDependencies[i],
-									error: err
-								});
-							}
-							if (!options.ignoreErrored) {
-								if (!error) error = err;
-							}
-						}
-					}
-				}
-			}
-		}
-
-		// Load self accepted modules
-		for (i = 0; i < outdatedSelfAcceptedModules.length; i++) {
-			var item = outdatedSelfAcceptedModules[i];
-			moduleId = item.module;
-			hotCurrentParents = [moduleId];
-			try {
-				$require$(moduleId);
-			} catch (err) {
-				if (typeof item.errorHandler === "function") {
-					try {
-						item.errorHandler(err);
-					} catch (err2) {
-						if (options.onErrored) {
-							options.onErrored({
-								type: "self-accept-error-handler-errored",
-								moduleId: moduleId,
-								error: err2,
-								originalError: err
-							});
-						}
-						if (!options.ignoreErrored) {
-							if (!error) error = err2;
-						}
-						if (!error) error = err;
-					}
-				} else {
-					if (options.onErrored) {
-						options.onErrored({
-							type: "self-accept-errored",
-							moduleId: moduleId,
-							error: err
-						});
-					}
-					if (!options.ignoreErrored) {
-						if (!error) error = err;
-					}
-				}
-			}
-		}
-
-		// handle errors in accept handlers and self accepted module load
-		if (error) {
-			hotSetStatus("fail");
-			return Promise.reject(error);
-		}
-
-		hotSetStatus("idle");
-		return new Promise(function(resolve) {
-			resolve(outdatedModules);
-		});
-	}
-};
diff --git a/lib/HotModuleReplacementPlugin.js b/lib/HotModuleReplacementPlugin.js
index 4b0edac7241..78c603f2e43 100644
--- a/lib/HotModuleReplacementPlugin.js
+++ b/lib/HotModuleReplacementPlugin.js
@@ -2,51 +2,308 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
-const { SyncBailHook } = require("tapable");
 const { RawSource } = require("webpack-sources");
-const Template = require("./Template");
+const ChunkGraph = require("./ChunkGraph");
+const Compilation = require("./Compilation");
+const HotUpdateChunk = require("./HotUpdateChunk");
+const {
+	JAVASCRIPT_MODULE_TYPE_AUTO,
+	JAVASCRIPT_MODULE_TYPE_DYNAMIC,
+	JAVASCRIPT_MODULE_TYPE_ESM,
+	WEBPACK_MODULE_TYPE_RUNTIME
+} = require("./ModuleTypeConstants");
+const NormalModule = require("./NormalModule");
+const RuntimeGlobals = require("./RuntimeGlobals");
+const { chunkHasCss } = require("./css/CssModulesPlugin");
+const ConstDependency = require("./dependencies/ConstDependency");
+const ImportMetaHotAcceptDependency = require("./dependencies/ImportMetaHotAcceptDependency");
+const ImportMetaHotDeclineDependency = require("./dependencies/ImportMetaHotDeclineDependency");
+const { isImportMetaFieldEnabled } = require("./dependencies/ImportMetaPlugin");
 const ModuleHotAcceptDependency = require("./dependencies/ModuleHotAcceptDependency");
 const ModuleHotDeclineDependency = require("./dependencies/ModuleHotDeclineDependency");
-const ConstDependency = require("./dependencies/ConstDependency");
-const NullFactory = require("./NullFactory");
-const ParserHelpers = require("./ParserHelpers");
-
-module.exports = class HotModuleReplacementPlugin {
-	constructor(options) {
-		this.options = options || {};
-		this.multiStep = this.options.multiStep;
-		this.fullBuildTimeout = this.options.fullBuildTimeout || 200;
-		this.requestTimeout = this.options.requestTimeout || 10000;
+const WebpackError = require("./errors/WebpackError");
+const HotModuleReplacementRuntimeModule = require("./hmr/HotModuleReplacementRuntimeModule");
+const getParserHooks = require("./hmr/parserHooks");
+const {
+	evaluateToIdentifier
+} = require("./javascript/JavascriptParserHelpers");
+const ConcatenatedModule = require("./optimize/ConcatenatedModule");
+const { find, isSubset } = require("./util/SetHelpers");
+const TupleSet = require("./util/TupleSet");
+const { compareModulesById } = require("./util/comparators");
+const {
+	forEachRuntime,
+	getRuntimeKey,
+	intersectRuntime,
+	keyToRuntime,
+	mergeRuntimeOwned,
+	subtractRuntime
+} = require("./util/runtime");
+
+/** @typedef {import("estree").CallExpression} CallExpression */
+/** @typedef {import("estree").Expression} Expression */
+/** @typedef {import("./Chunk")} Chunk */
+/** @typedef {import("./Chunk").ChunkId} ChunkId */
+/** @typedef {import("./ChunkGraph").ModuleId} ModuleId */
+/** @typedef {import("./Compilation").AssetInfo} AssetInfo */
+/** @typedef {import("./Compilation").Records} Records */
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./CodeGenerationResults")} CodeGenerationResults */
+/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */
+/** @typedef {import("./Module")} Module */
+/** @typedef {import("./Module").BuildInfo} BuildInfo */
+/** @typedef {import("./css/CssModule").CssModuleBuildMeta} CssModuleBuildMeta */
+/** @typedef {import("./hmr/parserHooks").HMRJavascriptParserHooks} HMRJavascriptParserHooks */
+/** @typedef {import("./RuntimeModule")} RuntimeModule */
+/** @typedef {import("./javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */
+/** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */
+/** @typedef {import("./javascript/JavascriptParserHelpers").Range} Range */
+/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */
+
+/** @typedef {string[]} Requests */
+
+/** @typedef {number} HotIndex */
+/** @typedef {Record} FullHashChunkModuleHashes */
+/** @typedef {Record} ChunkModuleHashes */
+/** @typedef {Record} ChunkHashes */
+/** @typedef {Record} ChunkRuntime */
+/** @typedef {Record} ChunkModuleIds */
+
+/** @typedef {Set} ChunkIds */
+/** @typedef {Set} ModuleSet */
+
+/** @typedef {{ updatedChunkIds: ChunkIds, removedChunkIds: ChunkIds, removedModules: ModuleSet, forceLoadChunkIds: ChunkIds, filename: string, assetInfo: AssetInfo }} HotUpdateMainContentByRuntimeItem */
+/** @typedef {Map} HotUpdateMainContentByRuntime */
+
+const PLUGIN_NAME = "HotModuleReplacementPlugin";
+
+class HotModuleReplacementPlugin {
+	/**
+	 * Returns the attached hooks.
+	 * @param {JavascriptParser} parser the parser
+	 * @returns {HMRJavascriptParserHooks} the attached hooks
+	 */
+	static getParserHooks(parser) {
+		return getParserHooks(parser);
 	}
 
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
 	apply(compiler) {
-		const multiStep = this.multiStep;
-		const fullBuildTimeout = this.fullBuildTimeout;
-		const requestTimeout = this.requestTimeout;
-		const hotUpdateChunkFilename =
-			compiler.options.output.hotUpdateChunkFilename;
-		const hotUpdateMainFilename = compiler.options.output.hotUpdateMainFilename;
-		compiler.hooks.additionalPass.tapAsync(
-			"HotModuleReplacementPlugin",
-			callback => {
-				if (multiStep) return setTimeout(callback, fullBuildTimeout);
-				return callback();
+		const { _backCompat: backCompat } = compiler;
+		if (compiler.options.output.strictModuleErrorHandling === undefined) {
+			compiler.options.output.strictModuleErrorHandling = true;
+		}
+		const runtimeRequirements = [RuntimeGlobals.module];
+
+		/**
+		 * Creates an accept handler.
+		 * @param {JavascriptParser} parser the parser
+		 * @param {typeof ModuleHotAcceptDependency} ParamDependency dependency
+		 * @returns {(expr: CallExpression) => boolean | undefined} callback
+		 */
+		const createAcceptHandler = (parser, ParamDependency) => {
+			// through the static method: replacing it stays a way to intercept
+			const { hotAcceptCallback, hotAcceptWithoutCallback } =
+				HotModuleReplacementPlugin.getParserHooks(parser);
+
+			return (expr) => {
+				const module = parser.state.module;
+				const dep = new ConstDependency(
+					`${module.moduleArgument}.hot.accept`,
+					/** @type {Range} */ (expr.callee.range),
+					runtimeRequirements
+				);
+				dep.loc = parser.getLocation(expr);
+				module.addPresentationalDependency(dep);
+				/** @type {BuildInfo} */
+				(module.buildInfo).moduleConcatenationBailout =
+					"Hot Module Replacement";
+
+				if (expr.arguments.length >= 1) {
+					const arg = parser.evaluateExpression(expr.arguments[0]);
+					/** @type {BasicEvaluatedExpression[]} */
+					let params = [];
+					if (arg.isString()) {
+						params = [arg];
+					} else if (arg.isArray()) {
+						params =
+							/** @type {BasicEvaluatedExpression[]} */
+							(arg.items).filter((param) => param.isString());
+					}
+					/** @type {Requests} */
+					const requests = [];
+					if (params.length > 0) {
+						for (const [idx, param] of params.entries()) {
+							const request = /** @type {string} */ (param.string);
+							const dep = new ParamDependency(
+								request,
+								/** @type {Range} */ (param.range)
+							);
+							dep.optional = true;
+							dep.setLocWithIndex(parser.getLocation(expr), idx);
+							module.addDependency(dep);
+							requests.push(request);
+						}
+						if (expr.arguments.length > 1) {
+							hotAcceptCallback.call(expr.arguments[1], requests);
+							for (let i = 1; i < expr.arguments.length; i++) {
+								parser.walkExpression(expr.arguments[i]);
+							}
+							return true;
+						}
+						hotAcceptWithoutCallback.call(expr, requests);
+						return true;
+					}
+				}
+				parser.walkExpressions(expr.arguments);
+				return true;
+			};
+		};
+
+		/**
+		 * Creates a decline handler.
+		 * @param {JavascriptParser} parser the parser
+		 * @param {typeof ModuleHotDeclineDependency} ParamDependency dependency
+		 * @returns {(expr: CallExpression) => boolean | undefined} callback
+		 */
+		const createDeclineHandler = (parser, ParamDependency) => (expr) => {
+			const module = parser.state.module;
+			const dep = new ConstDependency(
+				`${module.moduleArgument}.hot.decline`,
+				/** @type {Range} */ (expr.callee.range),
+				runtimeRequirements
+			);
+			dep.loc = parser.getLocation(expr);
+			module.addPresentationalDependency(dep);
+			/** @type {BuildInfo} */
+			(module.buildInfo).moduleConcatenationBailout = "Hot Module Replacement";
+			if (expr.arguments.length === 1) {
+				const arg = parser.evaluateExpression(expr.arguments[0]);
+				/** @type {BasicEvaluatedExpression[]} */
+				let params = [];
+				if (arg.isString()) {
+					params = [arg];
+				} else if (arg.isArray()) {
+					params =
+						/** @type {BasicEvaluatedExpression[]} */
+						(arg.items).filter((param) => param.isString());
+				}
+				for (const [idx, param] of params.entries()) {
+					const dep = new ParamDependency(
+						/** @type {string} */ (param.string),
+						/** @type {Range} */ (param.range)
+					);
+					dep.optional = true;
+					dep.setLocWithIndex(parser.getLocation(expr), idx);
+					module.addDependency(dep);
+				}
 			}
-		);
-		compiler.hooks.compilation.tap(
-			"HotModuleReplacementPlugin",
-			(compilation, { normalModuleFactory }) => {
-				const hotUpdateChunkTemplate = compilation.hotUpdateChunkTemplate;
-				if (!hotUpdateChunkTemplate) return;
+			return true;
+		};
 
-				compilation.dependencyFactories.set(ConstDependency, new NullFactory());
-				compilation.dependencyTemplates.set(
-					ConstDependency,
-					new ConstDependency.Template()
+		/**
+		 * Creates a hmr expression handler.
+		 * @param {JavascriptParser} parser the parser
+		 * @returns {(expr: Expression) => boolean | undefined} callback
+		 */
+		const createHMRExpressionHandler = (parser) => (expr) => {
+			const module = parser.state.module;
+			const dep = new ConstDependency(
+				`${module.moduleArgument}.hot`,
+				/** @type {Range} */ (expr.range),
+				runtimeRequirements
+			);
+			dep.loc = parser.getLocation(expr);
+			module.addPresentationalDependency(dep);
+			/** @type {BuildInfo} */
+			(module.buildInfo).moduleConcatenationBailout = "Hot Module Replacement";
+			return true;
+		};
+
+		/**
+		 * Processes the provided parser.
+		 * @param {JavascriptParser} parser the parser
+		 * @returns {void}
+		 */
+		const applyModuleHot = (parser) => {
+			parser.hooks.evaluateIdentifier.for("module.hot").tap(
+				{
+					name: PLUGIN_NAME,
+					before: "NodeStuffPlugin"
+				},
+				(expr) =>
+					evaluateToIdentifier(
+						"module.hot",
+						"module",
+						() => ["hot"],
+						true
+					)(expr)
+			);
+			parser.hooks.call
+				.for("module.hot.accept")
+				.tap(
+					PLUGIN_NAME,
+					createAcceptHandler(parser, ModuleHotAcceptDependency)
+				);
+			parser.hooks.call
+				.for("module.hot.decline")
+				.tap(
+					PLUGIN_NAME,
+					createDeclineHandler(parser, ModuleHotDeclineDependency)
+				);
+			parser.hooks.expression
+				.for("module.hot")
+				.tap(PLUGIN_NAME, createHMRExpressionHandler(parser));
+		};
+
+		/**
+		 * Apply import meta hot.
+		 * @param {JavascriptParser} parser the parser
+		 * @returns {void}
+		 */
+		const applyImportMetaHot = (parser) => {
+			parser.hooks.evaluateIdentifier
+				.for("import.meta.webpackHot")
+				.tap(PLUGIN_NAME, (expr) =>
+					evaluateToIdentifier(
+						"import.meta.webpackHot",
+						"import.meta",
+						() => ["webpackHot"],
+						true
+					)(expr)
 				);
+			parser.hooks.call
+				.for("import.meta.webpackHot.accept")
+				.tap(
+					PLUGIN_NAME,
+					createAcceptHandler(parser, ImportMetaHotAcceptDependency)
+				);
+			parser.hooks.call
+				.for("import.meta.webpackHot.decline")
+				.tap(
+					PLUGIN_NAME,
+					createDeclineHandler(parser, ImportMetaHotDeclineDependency)
+				);
+			parser.hooks.expression
+				.for("import.meta.webpackHot")
+				.tap(PLUGIN_NAME, createHMRExpressionHandler(parser));
+		};
+
+		compiler.hooks.compilation.tap(
+			PLUGIN_NAME,
+			(compilation, { normalModuleFactory }) => {
+				// This applies the HMR plugin only to the targeted compiler
+				// It should not affect child compilations
+				if (compilation.compiler !== compiler) return;
 
+				// #region module.hot.* API
 				compilation.dependencyFactories.set(
 					ModuleHotAcceptDependency,
 					normalModuleFactory
@@ -55,7 +312,6 @@ module.exports = class HotModuleReplacementPlugin {
 					ModuleHotAcceptDependency,
 					new ModuleHotAcceptDependency.Template()
 				);
-
 				compilation.dependencyFactories.set(
 					ModuleHotDeclineDependency,
 					normalModuleFactory
@@ -64,350 +320,655 @@ module.exports = class HotModuleReplacementPlugin {
 					ModuleHotDeclineDependency,
 					new ModuleHotDeclineDependency.Template()
 				);
+				// #endregion
 
-				compilation.hooks.record.tap(
-					"HotModuleReplacementPlugin",
-					(compilation, records) => {
-						if (records.hash === compilation.hash) return;
-						records.hash = compilation.hash;
-						records.moduleHashs = {};
-						for (const module of compilation.modules) {
-							const identifier = module.identifier();
-							records.moduleHashs[identifier] = module.hash;
-						}
-						records.chunkHashs = {};
-						for (const chunk of compilation.chunks) {
-							records.chunkHashs[chunk.id] = chunk.hash;
-						}
-						records.chunkModuleIds = {};
-						for (const chunk of compilation.chunks) {
-							records.chunkModuleIds[chunk.id] = Array.from(
-								chunk.modulesIterable,
-								m => m.id
-							);
-						}
-					}
+				// #region import.meta.webpackHot.* API
+				compilation.dependencyFactories.set(
+					ImportMetaHotAcceptDependency,
+					normalModuleFactory
+				);
+				compilation.dependencyTemplates.set(
+					ImportMetaHotAcceptDependency,
+					new ImportMetaHotAcceptDependency.Template()
 				);
-				let initialPass = false;
-				let recompilation = false;
-				compilation.hooks.afterHash.tap("HotModuleReplacementPlugin", () => {
-					let records = compilation.records;
-					if (!records) {
-						initialPass = true;
-						return;
+				compilation.dependencyFactories.set(
+					ImportMetaHotDeclineDependency,
+					normalModuleFactory
+				);
+				compilation.dependencyTemplates.set(
+					ImportMetaHotDeclineDependency,
+					new ImportMetaHotDeclineDependency.Template()
+				);
+				// #endregion
+
+				/** @type {HotIndex} */
+				let hotIndex = 0;
+				/** @type {FullHashChunkModuleHashes} */
+				const fullHashChunkModuleHashes = {};
+				/** @type {ChunkModuleHashes} */
+				const chunkModuleHashes = {};
+
+				compilation.hooks.record.tap(PLUGIN_NAME, (compilation, records) => {
+					if (records.hash === compilation.hash) return;
+					const chunkGraph = compilation.chunkGraph;
+					records.hash = compilation.hash;
+					records.hotIndex = hotIndex;
+					records.fullHashChunkModuleHashes = fullHashChunkModuleHashes;
+					records.chunkModuleHashes = chunkModuleHashes;
+					records.chunkHashes = {};
+					records.chunkRuntime = {};
+					for (const chunk of compilation.chunks) {
+						const chunkId = /** @type {ChunkId} */ (chunk.id);
+						records.chunkHashes[chunkId] = /** @type {string} */ (chunk.hash);
+						records.chunkRuntime[chunkId] = getRuntimeKey(chunk.runtime);
 					}
-					if (!records.hash) initialPass = true;
-					const preHash = records.preHash || "x";
-					const prepreHash = records.prepreHash || "x";
-					if (preHash === compilation.hash) {
-						recompilation = true;
-						compilation.modifyHash(prepreHash);
-						return;
+					records.chunkModuleIds = {};
+					for (const chunk of compilation.chunks) {
+						const chunkId = /** @type {ChunkId} */ (chunk.id);
+
+						/** @type {ModuleId[]} */
+						const moduleIds = [];
+						for (const m of chunkGraph.getOrderedChunkModulesIterable(
+							chunk,
+							compareModulesById(chunkGraph)
+						)) {
+							moduleIds.push(
+								/** @type {ModuleId} */ (chunkGraph.getModuleId(m))
+							);
+							if (m instanceof ConcatenatedModule && m.modules) {
+								for (const innerModule of m.modules) {
+									if (
+										innerModule.buildMeta &&
+										/** @type {CssModuleBuildMeta} */ (innerModule.buildMeta)
+											.needIdInConcatenation
+									) {
+										const innerId = chunkGraph.getModuleId(innerModule);
+										if (innerId !== null) {
+											moduleIds.push(innerId);
+										}
+									}
+								}
+							}
+						}
+						records.chunkModuleIds[chunkId] = moduleIds;
 					}
-					records.prepreHash = records.hash || "x";
-					records.preHash = compilation.hash;
-					compilation.modifyHash(records.prepreHash);
 				});
-				compilation.hooks.shouldGenerateChunkAssets.tap(
-					"HotModuleReplacementPlugin",
-					() => {
-						if (multiStep && !recompilation && !initialPass) return false;
-					}
-				);
-				compilation.hooks.needAdditionalPass.tap(
-					"HotModuleReplacementPlugin",
-					() => {
-						if (multiStep && !recompilation && !initialPass) return true;
+				/** @type {TupleSet} */
+				const updatedModules = new TupleSet();
+				/** @type {TupleSet} */
+				const fullHashModules = new TupleSet();
+				/** @type {TupleSet} */
+				const nonCodeGeneratedModules = new TupleSet();
+				compilation.hooks.fullHash.tap(PLUGIN_NAME, (hash) => {
+					const chunkGraph = compilation.chunkGraph;
+					const records = /** @type {Records} */ (compilation.records);
+					for (const chunk of compilation.chunks) {
+						/**
+						 * Returns module hash.
+						 * @param {Module} module module
+						 * @returns {string} module hash
+						 */
+						const getModuleHash = (module) => {
+							const codeGenerationResults =
+								/** @type {CodeGenerationResults} */
+								(compilation.codeGenerationResults);
+							if (codeGenerationResults.has(module, chunk.runtime)) {
+								return codeGenerationResults.getHash(module, chunk.runtime);
+							}
+							nonCodeGeneratedModules.add(module, chunk.runtime);
+							return chunkGraph.getModuleHash(module, chunk.runtime);
+						};
+						const fullHashModulesInThisChunk =
+							chunkGraph.getChunkFullHashModulesSet(chunk);
+						if (fullHashModulesInThisChunk !== undefined) {
+							for (const module of fullHashModulesInThisChunk) {
+								fullHashModules.add(module, chunk);
+							}
+						}
+						const modules = chunkGraph.getChunkModulesIterable(chunk);
+						if (modules !== undefined) {
+							if (records.chunkModuleHashes) {
+								if (fullHashModulesInThisChunk !== undefined) {
+									for (const module of modules) {
+										const key = `${chunk.id}|${module.identifier()}`;
+										const hash = getModuleHash(module);
+										if (
+											fullHashModulesInThisChunk.has(
+												/** @type {RuntimeModule} */
+												(module)
+											)
+										) {
+											if (
+												/** @type {FullHashChunkModuleHashes} */
+												(records.fullHashChunkModuleHashes)[key] !== hash
+											) {
+												updatedModules.add(module, chunk);
+											}
+											fullHashChunkModuleHashes[key] = hash;
+										} else {
+											if (records.chunkModuleHashes[key] !== hash) {
+												updatedModules.add(module, chunk);
+											}
+											chunkModuleHashes[key] = hash;
+										}
+									}
+								} else {
+									for (const module of modules) {
+										const key = `${chunk.id}|${module.identifier()}`;
+										const hash = getModuleHash(module);
+										if (records.chunkModuleHashes[key] !== hash) {
+											updatedModules.add(module, chunk);
+										}
+										chunkModuleHashes[key] = hash;
+									}
+								}
+							} else if (fullHashModulesInThisChunk !== undefined) {
+								for (const module of modules) {
+									const key = `${chunk.id}|${module.identifier()}`;
+									const hash = getModuleHash(module);
+									if (
+										fullHashModulesInThisChunk.has(
+											/** @type {RuntimeModule} */ (module)
+										)
+									) {
+										fullHashChunkModuleHashes[key] = hash;
+									} else {
+										chunkModuleHashes[key] = hash;
+									}
+								}
+							} else {
+								for (const module of modules) {
+									const key = `${chunk.id}|${module.identifier()}`;
+									const hash = getModuleHash(module);
+									chunkModuleHashes[key] = hash;
+								}
+							}
+						}
 					}
-				);
-				compilation.hooks.additionalChunkAssets.tap(
-					"HotModuleReplacementPlugin",
+
+					hotIndex = records.hotIndex || 0;
+					if (updatedModules.size > 0) hotIndex++;
+
+					hash.update(`${hotIndex}`);
+				});
+				compilation.hooks.processAssets.tap(
+					{
+						name: PLUGIN_NAME,
+						stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL
+					},
 					() => {
-						const records = compilation.records;
+						const chunkGraph = compilation.chunkGraph;
+						const records = /** @type {Records} */ (compilation.records);
 						if (records.hash === compilation.hash) return;
 						if (
-							!records.moduleHashs ||
-							!records.chunkHashs ||
+							!records.chunkModuleHashes ||
+							!records.chunkHashes ||
 							!records.chunkModuleIds
-						)
+						) {
 							return;
+						}
+						const codeGenerationResults =
+							/** @type {CodeGenerationResults} */
+							(compilation.codeGenerationResults);
+						for (const [module, chunk] of fullHashModules) {
+							const key = `${chunk.id}|${module.identifier()}`;
+							const hash = nonCodeGeneratedModules.has(module, chunk.runtime)
+								? chunkGraph.getModuleHash(module, chunk.runtime)
+								: codeGenerationResults.getHash(module, chunk.runtime);
+							if (records.chunkModuleHashes[key] !== hash) {
+								updatedModules.add(module, chunk);
+							}
+							chunkModuleHashes[key] = hash;
+						}
+
+						/** @type {HotUpdateMainContentByRuntime} */
+						const hotUpdateMainContentByRuntime = new Map();
+						/** @type {RuntimeSpec} */
+						let allOldRuntime;
+						const chunkRuntime =
+							/** @type {ChunkRuntime} */
+							(records.chunkRuntime);
+						for (const key of Object.keys(chunkRuntime)) {
+							const runtime = keyToRuntime(chunkRuntime[key]);
+							allOldRuntime = mergeRuntimeOwned(allOldRuntime, runtime);
+						}
+						forEachRuntime(allOldRuntime, (runtime) => {
+							const { path: filename, info: assetInfo } =
+								compilation.getPathWithInfo(
+									compilation.outputOptions.hotUpdateMainFilename,
+									{
+										hash: records.hash,
+										runtime
+									}
+								);
+							hotUpdateMainContentByRuntime.set(
+								/** @type {string} */ (runtime),
+								{
+									/** @type {ChunkIds} */
+									updatedChunkIds: new Set(),
+									/** @type {ChunkIds} */
+									removedChunkIds: new Set(),
+									/** @type {ModuleSet} */
+									removedModules: new Set(),
+									/** @type {ChunkIds} */
+									forceLoadChunkIds: new Set(),
+									filename,
+									assetInfo
+								}
+							);
+						});
+						if (hotUpdateMainContentByRuntime.size === 0) return;
+
+						// Create a list of all active modules to verify which modules are removed completely
+						/** @type {Map} */
+						const allModules = new Map();
 						for (const module of compilation.modules) {
-							const identifier = module.identifier();
-							let hash = module.hash;
-							module.hotUpdate = records.moduleHashs[identifier] !== hash;
+							const id =
+								/** @type {ModuleId} */
+								(chunkGraph.getModuleId(module));
+							allModules.set(id, module);
 						}
-						const hotUpdateMainContent = {
-							h: compilation.hash,
-							c: {}
-						};
-						for (const key of Object.keys(records.chunkHashs)) {
-							const chunkId = isNaN(+key) ? key : +key;
-							const currentChunk = compilation.chunks.find(
-								chunk => chunk.id === chunkId
+
+						// List of completely removed modules
+						/** @type {Set} */
+						const completelyRemovedModules = new Set();
+
+						for (const key of Object.keys(records.chunkHashes)) {
+							const oldRuntime = keyToRuntime(
+								/** @type {ChunkRuntime} */
+								(records.chunkRuntime)[key]
+							);
+							/** @type {Module[]} */
+							const remainingModules = [];
+							// Check which modules are removed
+							for (const id of records.chunkModuleIds[key]) {
+								const module = allModules.get(id);
+								if (module === undefined) {
+									completelyRemovedModules.add(id);
+								} else {
+									remainingModules.push(module);
+								}
+							}
+
+							/** @type {ChunkId | null} */
+							let chunkId;
+							/** @type {undefined | Module[]} */
+							let newModules;
+							/** @type {undefined | RuntimeModule[]} */
+							let newRuntimeModules;
+							/** @type {undefined | RuntimeModule[]} */
+							let newFullHashModules;
+							/** @type {undefined | RuntimeModule[]} */
+							let newDependentHashModules;
+							/** @type {RuntimeSpec} */
+							let newRuntime;
+							/** @type {RuntimeSpec} */
+							let removedFromRuntime;
+							const currentChunk = find(
+								compilation.chunks,
+								(chunk) => `${chunk.id}` === key
 							);
 							if (currentChunk) {
-								const newModules = currentChunk
-									.getModules()
-									.filter(module => module.hotUpdate);
-								const allModules = new Set();
-								for (const module of currentChunk.modulesIterable) {
-									allModules.add(module.id);
+								chunkId = currentChunk.id;
+								newRuntime = intersectRuntime(
+									currentChunk.runtime,
+									allOldRuntime
+								);
+								if (newRuntime === undefined) continue;
+								newModules = chunkGraph
+									.getChunkModules(currentChunk)
+									.filter((module) => updatedModules.has(module, currentChunk));
+								newRuntimeModules = [
+									...chunkGraph.getChunkRuntimeModulesIterable(currentChunk)
+								].filter((module) => updatedModules.has(module, currentChunk));
+								const fullHashModules =
+									chunkGraph.getChunkFullHashModulesIterable(currentChunk);
+								newFullHashModules =
+									fullHashModules &&
+									[...fullHashModules].filter((module) =>
+										updatedModules.has(module, currentChunk)
+									);
+								const dependentHashModules =
+									chunkGraph.getChunkDependentHashModulesIterable(currentChunk);
+								newDependentHashModules =
+									dependentHashModules &&
+									[...dependentHashModules].filter((module) =>
+										updatedModules.has(module, currentChunk)
+									);
+								removedFromRuntime = subtractRuntime(oldRuntime, newRuntime);
+							} else {
+								// chunk has completely removed
+								chunkId = `${Number(key)}` === key ? Number(key) : key;
+								removedFromRuntime = oldRuntime;
+								newRuntime = oldRuntime;
+							}
+							if (removedFromRuntime) {
+								// chunk was removed from some runtimes
+								forEachRuntime(removedFromRuntime, (runtime) => {
+									const item =
+										/** @type {HotUpdateMainContentByRuntimeItem} */
+										(
+											hotUpdateMainContentByRuntime.get(
+												/** @type {string} */ (runtime)
+											)
+										);
+									item.removedChunkIds.add(/** @type {ChunkId} */ (chunkId));
+								});
+								// dispose modules from the chunk in these runtimes
+								// where they are no longer in this runtime
+								for (const module of remainingModules) {
+									const moduleKey = `${key}|${module.identifier()}`;
+									const oldHash = records.chunkModuleHashes[moduleKey];
+									const runtimes = chunkGraph.getModuleRuntimes(module);
+									if (oldRuntime === newRuntime && runtimes.has(newRuntime)) {
+										// Module is still in the same runtime combination
+										const hash = nonCodeGeneratedModules.has(module, newRuntime)
+											? chunkGraph.getModuleHash(module, newRuntime)
+											: codeGenerationResults.getHash(module, newRuntime);
+										if (hash !== oldHash) {
+											if (module.type === WEBPACK_MODULE_TYPE_RUNTIME) {
+												newRuntimeModules = newRuntimeModules || [];
+												newRuntimeModules.push(
+													/** @type {RuntimeModule} */ (module)
+												);
+											} else {
+												newModules = newModules || [];
+												newModules.push(module);
+											}
+										}
+									} else {
+										// module is no longer in this runtime combination
+										forEachRuntime(removedFromRuntime, (runtime) => {
+											const item =
+												/** @type {HotUpdateMainContentByRuntimeItem} */ (
+													hotUpdateMainContentByRuntime.get(
+														/** @type {string} */ (runtime)
+													)
+												);
+											// Module still in this runtime via another chunk: force-load one so
+											// it keeps an installed owner instead of being disposed (lost updates).
+											const stillInRuntime = [...runtimes].some(
+												(moduleRuntime) =>
+													typeof moduleRuntime === "string"
+														? moduleRuntime === runtime
+														: moduleRuntime !== undefined &&
+															moduleRuntime.has(/** @type {string} */ (runtime))
+											);
+											if (stillInRuntime) {
+												for (const moduleChunk of chunkGraph.getModuleChunksIterable(
+													module
+												)) {
+													const chunkRuntime = moduleChunk.runtime;
+													const inRuntime =
+														typeof chunkRuntime === "string"
+															? chunkRuntime === runtime
+															: chunkRuntime !== undefined &&
+																chunkRuntime.has(
+																	/** @type {string} */ (runtime)
+																);
+													if (inRuntime) {
+														item.forceLoadChunkIds.add(
+															/** @type {ChunkId} */ (moduleChunk.id)
+														);
+														break;
+													}
+												}
+												return;
+											}
+											item.removedModules.add(module);
+										});
+									}
+								}
+							}
+							if (
+								(newModules && newModules.length > 0) ||
+								(newRuntimeModules && newRuntimeModules.length > 0)
+							) {
+								const hotUpdateChunk = new HotUpdateChunk();
+								if (backCompat) {
+									ChunkGraph.setChunkGraphForChunk(hotUpdateChunk, chunkGraph);
+								}
+								hotUpdateChunk.id = chunkId;
+								hotUpdateChunk.runtime = currentChunk
+									? currentChunk.runtime
+									: newRuntime;
+								if (currentChunk) {
+									for (const group of currentChunk.groupsIterable) {
+										hotUpdateChunk.addGroup(group);
+									}
 								}
-								const removedModules = records.chunkModuleIds[chunkId].filter(
-									id => !allModules.has(id)
+								chunkGraph.attachModules(hotUpdateChunk, newModules || []);
+								chunkGraph.attachRuntimeModules(
+									hotUpdateChunk,
+									newRuntimeModules || []
 								);
-								if (newModules.length > 0 || removedModules.length > 0) {
-									const source = hotUpdateChunkTemplate.render(
-										chunkId,
-										newModules,
-										removedModules,
-										compilation.hash,
-										compilation.moduleTemplates.javascript,
-										compilation.dependencyTemplates
+								if (newFullHashModules) {
+									chunkGraph.attachFullHashModules(
+										hotUpdateChunk,
+										newFullHashModules
 									);
-									const filename = compilation.getPath(hotUpdateChunkFilename, {
-										hash: records.hash,
-										chunk: currentChunk
-									});
-									compilation.additionalChunkAssets.push(filename);
-									compilation.assets[filename] = source;
-									hotUpdateMainContent.c[chunkId] = true;
-									currentChunk.files.push(filename);
-									compilation.hooks.chunkAsset.call(
-										"HotModuleReplacementPlugin",
-										currentChunk,
-										filename
+								}
+								if (newDependentHashModules) {
+									chunkGraph.attachDependentHashModules(
+										hotUpdateChunk,
+										newDependentHashModules
 									);
 								}
-							} else {
-								hotUpdateMainContent.c[chunkId] = false;
+								const renderManifest = compilation.getRenderManifest({
+									chunk: hotUpdateChunk,
+									hash: /** @type {string} */ (records.hash),
+									fullHash: /** @type {string} */ (records.hash),
+									outputOptions: compilation.outputOptions,
+									moduleTemplates: compilation.moduleTemplates,
+									dependencyTemplates: compilation.dependencyTemplates,
+									codeGenerationResults: /** @type {CodeGenerationResults} */ (
+										compilation.codeGenerationResults
+									),
+									runtimeTemplate: compilation.runtimeTemplate,
+									moduleGraph: compilation.moduleGraph,
+									chunkGraph
+								});
+								for (const entry of renderManifest) {
+									/** @type {string} */
+									let filename;
+									/** @type {AssetInfo} */
+									let assetInfo;
+									if ("filename" in entry) {
+										filename = entry.filename;
+										assetInfo = entry.info;
+									} else {
+										({ path: filename, info: assetInfo } =
+											compilation.getPathWithInfo(
+												entry.filenameTemplate,
+												entry.pathOptions
+											));
+									}
+									const source = entry.render();
+									compilation.additionalChunkAssets.push(filename);
+									compilation.emitAsset(filename, source, {
+										hotModuleReplacement: true,
+										...assetInfo
+									});
+									if (currentChunk) {
+										currentChunk.files.add(filename);
+										compilation.hooks.chunkAsset.call(currentChunk, filename);
+									}
+								}
+								forEachRuntime(newRuntime, (runtime) => {
+									const item =
+										/** @type {HotUpdateMainContentByRuntimeItem} */ (
+											hotUpdateMainContentByRuntime.get(
+												/** @type {string} */ (runtime)
+											)
+										);
+									item.updatedChunkIds.add(/** @type {ChunkId} */ (chunkId));
+								});
 							}
 						}
-						const source = new RawSource(JSON.stringify(hotUpdateMainContent));
-						const filename = compilation.getPath(hotUpdateMainFilename, {
-							hash: records.hash
-						});
-						compilation.assets[filename] = source;
-					}
-				);
-
-				const mainTemplate = compilation.mainTemplate;
-
-				mainTemplate.hooks.hash.tap("HotModuleReplacementPlugin", hash => {
-					hash.update("HotMainTemplateDecorator");
-				});
-
-				mainTemplate.hooks.moduleRequire.tap(
-					"HotModuleReplacementPlugin",
-					(_, chunk, hash, varModuleId) => {
-						return `hotCreateRequire(${varModuleId})`;
-					}
-				);
+						const completelyRemovedModulesArray = [...completelyRemovedModules];
+						/** @type {Map>} */
+						const hotUpdateMainContentByFilename = new Map();
+						for (const {
+							removedChunkIds,
+							removedModules,
+							updatedChunkIds,
+							forceLoadChunkIds,
+							filename,
+							assetInfo
+						} of hotUpdateMainContentByRuntime.values()) {
+							const old = hotUpdateMainContentByFilename.get(filename);
+							if (
+								old &&
+								(!isSubset(old.removedChunkIds, removedChunkIds) ||
+									!isSubset(old.removedModules, removedModules) ||
+									!isSubset(old.updatedChunkIds, updatedChunkIds) ||
+									!isSubset(old.forceLoadChunkIds, forceLoadChunkIds))
+							) {
+								compilation.warnings.push(
+									new WebpackError(`HotModuleReplacementPlugin
+The configured output.hotUpdateMainFilename doesn't lead to unique filenames per runtime and HMR update differs between runtimes.
+This might lead to incorrect runtime behavior of the applied update.
+To fix this, make sure to include [runtime] in the output.hotUpdateMainFilename option, or use the default config.`)
+								);
+								for (const chunkId of removedChunkIds) {
+									old.removedChunkIds.add(chunkId);
+								}
+								for (const chunkId of removedModules) {
+									old.removedModules.add(chunkId);
+								}
+								for (const chunkId of updatedChunkIds) {
+									old.updatedChunkIds.add(chunkId);
+								}
+								for (const chunkId of forceLoadChunkIds) {
+									old.forceLoadChunkIds.add(chunkId);
+								}
+								continue;
+							}
+							hotUpdateMainContentByFilename.set(filename, {
+								removedChunkIds,
+								removedModules,
+								updatedChunkIds,
+								forceLoadChunkIds,
+								assetInfo
+							});
+						}
+						for (const [
+							filename,
+							{
+								removedChunkIds,
+								removedModules,
+								updatedChunkIds,
+								forceLoadChunkIds,
+								assetInfo
+							}
+						] of hotUpdateMainContentByFilename) {
+							/** @type {{ c: ChunkId[], r: ChunkId[], m: ModuleId[], f?: ChunkId[], css?: { r: ChunkId[] } }} */
+							const hotUpdateMainJson = {
+								c: [...updatedChunkIds],
+								r: [...removedChunkIds],
+								m:
+									removedModules.size === 0
+										? completelyRemovedModulesArray
+										: [
+												...completelyRemovedModulesArray,
+												...Array.from(
+													removedModules,
+													(m) =>
+														/** @type {ModuleId} */ (chunkGraph.getModuleId(m))
+												)
+											]
+							};
 
-				mainTemplate.hooks.requireExtensions.tap(
-					"HotModuleReplacementPlugin",
-					source => {
-						const buf = [source];
-						buf.push("");
-						buf.push("// __webpack_hash__");
-						buf.push(
-							mainTemplate.requireFn +
-								".h = function() { return hotCurrentHash; };"
-						);
-						return Template.asString(buf);
-					}
-				);
+							// Chunks the client must load so a module whose only loaded chunk
+							// left a runtime keeps an installed owner (see force-load branch).
+							if (forceLoadChunkIds.size > 0) {
+								hotUpdateMainJson.f = [...forceLoadChunkIds];
+							}
 
-				const needChunkLoadingCode = chunk => {
-					for (const chunkGroup of chunk.groupsIterable) {
-						if (chunkGroup.chunks.length > 1) return true;
-						if (chunkGroup.getNumberOfChildren() > 0) return true;
-					}
-					return false;
-				};
-
-				mainTemplate.hooks.bootstrap.tap(
-					"HotModuleReplacementPlugin",
-					(source, chunk, hash) => {
-						source = mainTemplate.hooks.hotBootstrap.call(source, chunk, hash);
-						return Template.asString([
-							source,
-							"",
-							hotInitCode
-								.replace(/\$require\$/g, mainTemplate.requireFn)
-								.replace(/\$hash\$/g, JSON.stringify(hash))
-								.replace(/\$requestTimeout\$/g, requestTimeout)
-								.replace(
-									/\/\*foreachInstalledChunks\*\//g,
-									needChunkLoadingCode(chunk)
-										? "for(var chunkId in installedChunks)"
-										: `var chunkId = ${JSON.stringify(chunk.id)};`
-								)
-						]);
-					}
-				);
+							// Build CSS removed chunks list (chunks in updatedChunkIds that no longer have CSS)
+							/** @type {ChunkId[]} */
+							const cssRemovedChunkIds = [];
+							if (compilation.options.experiments.css) {
+								for (const chunkId of updatedChunkIds) {
+									for (const /** @type {Chunk} */ chunk of compilation.chunks) {
+										if (chunk.id === chunkId) {
+											if (!chunkHasCss(chunk, chunkGraph)) {
+												cssRemovedChunkIds.push(chunkId);
+											}
+											break;
+										}
+									}
+								}
+							}
 
-				mainTemplate.hooks.globalHash.tap(
-					"HotModuleReplacementPlugin",
-					() => true
-				);
+							if (cssRemovedChunkIds.length > 0) {
+								hotUpdateMainJson.css = { r: cssRemovedChunkIds };
+							}
 
-				mainTemplate.hooks.currentHash.tap(
-					"HotModuleReplacementPlugin",
-					(_, length) => {
-						if (isFinite(length)) {
-							return `hotCurrentHash.substr(0, ${length})`;
-						} else {
-							return "hotCurrentHash";
+							const source = new RawSource(
+								(filename.endsWith(".json") ? "" : "export default ") +
+									JSON.stringify(hotUpdateMainJson)
+							);
+							compilation.emitAsset(filename, source, {
+								hotModuleReplacement: true,
+								...assetInfo
+							});
 						}
 					}
 				);
 
-				mainTemplate.hooks.moduleObj.tap(
-					"HotModuleReplacementPlugin",
-					(source, chunk, hash, varModuleId) => {
-						return Template.asString([
-							`${source},`,
-							`hot: hotCreateModule(${varModuleId}),`,
-							"parents: (hotCurrentParentsTemp = hotCurrentParents, hotCurrentParents = [], hotCurrentParentsTemp),",
-							"children: []"
-						]);
+				compilation.hooks.additionalTreeRuntimeRequirements.tap(
+					PLUGIN_NAME,
+					(chunk, runtimeRequirements) => {
+						runtimeRequirements.add(RuntimeGlobals.hmrDownloadManifest);
+						runtimeRequirements.add(RuntimeGlobals.hmrDownloadUpdateHandlers);
+						runtimeRequirements.add(RuntimeGlobals.interceptModuleExecution);
+						runtimeRequirements.add(RuntimeGlobals.moduleCache);
+						compilation.addRuntimeModule(
+							chunk,
+							new HotModuleReplacementRuntimeModule()
+						);
 					}
 				);
 
-				const handler = (parser, parserOptions) => {
-					parser.hooks.expression
-						.for("__webpack_hash__")
-						.tap(
-							"HotModuleReplacementPlugin",
-							ParserHelpers.toConstantDependencyWithWebpackRequire(
-								parser,
-								"__webpack_require__.h()"
-							)
-						);
-					parser.hooks.evaluateTypeof
-						.for("__webpack_hash__")
-						.tap(
-							"HotModuleReplacementPlugin",
-							ParserHelpers.evaluateToString("string")
-						);
-					parser.hooks.evaluateIdentifier.for("module.hot").tap(
-						{
-							name: "HotModuleReplacementPlugin",
-							before: "NodeStuffPlugin"
-						},
-						expr => {
-							return ParserHelpers.evaluateToIdentifier(
-								"module.hot",
-								!!parser.state.compilation.hotUpdateChunkTemplate
-							)(expr);
+				normalModuleFactory.hooks.parser
+					.for(JAVASCRIPT_MODULE_TYPE_AUTO)
+					.tap(PLUGIN_NAME, (parser, parserOptions) => {
+						applyModuleHot(parser);
+						if (
+							isImportMetaFieldEnabled(parserOptions.importMeta, "webpackHot")
+						) {
+							applyImportMetaHot(parser);
 						}
-					);
-					// TODO webpack 5: refactor this, no custom hooks
-					if (!parser.hooks.hotAcceptCallback) {
-						parser.hooks.hotAcceptCallback = new SyncBailHook([
-							"expression",
-							"requests"
-						]);
-					}
-					if (!parser.hooks.hotAcceptWithoutCallback) {
-						parser.hooks.hotAcceptWithoutCallback = new SyncBailHook([
-							"expression",
-							"requests"
-						]);
-					}
-					parser.hooks.call
-						.for("module.hot.accept")
-						.tap("HotModuleReplacementPlugin", expr => {
-							if (!parser.state.compilation.hotUpdateChunkTemplate) {
-								return false;
-							}
-							if (expr.arguments.length >= 1) {
-								const arg = parser.evaluateExpression(expr.arguments[0]);
-								let params = [];
-								let requests = [];
-								if (arg.isString()) {
-									params = [arg];
-								} else if (arg.isArray()) {
-									params = arg.items.filter(param => param.isString());
-								}
-								if (params.length > 0) {
-									params.forEach((param, idx) => {
-										const request = param.string;
-										const dep = new ModuleHotAcceptDependency(
-											request,
-											param.range
-										);
-										dep.optional = true;
-										dep.loc = Object.create(expr.loc);
-										dep.loc.index = idx;
-										parser.state.module.addDependency(dep);
-										requests.push(request);
-									});
-									if (expr.arguments.length > 1) {
-										parser.hooks.hotAcceptCallback.call(
-											expr.arguments[1],
-											requests
-										);
-										parser.walkExpression(expr.arguments[1]); // other args are ignored
-										return true;
-									} else {
-										parser.hooks.hotAcceptWithoutCallback.call(expr, requests);
-										return true;
-									}
-								}
-							}
-						});
-					parser.hooks.call
-						.for("module.hot.decline")
-						.tap("HotModuleReplacementPlugin", expr => {
-							if (!parser.state.compilation.hotUpdateChunkTemplate) {
-								return false;
-							}
-							if (expr.arguments.length === 1) {
-								const arg = parser.evaluateExpression(expr.arguments[0]);
-								let params = [];
-								if (arg.isString()) {
-									params = [arg];
-								} else if (arg.isArray()) {
-									params = arg.items.filter(param => param.isString());
-								}
-								params.forEach((param, idx) => {
-									const dep = new ModuleHotDeclineDependency(
-										param.string,
-										param.range
-									);
-									dep.optional = true;
-									dep.loc = Object.create(expr.loc);
-									dep.loc.index = idx;
-									parser.state.module.addDependency(dep);
-								});
-							}
-						});
-					parser.hooks.expression
-						.for("module.hot")
-						.tap("HotModuleReplacementPlugin", ParserHelpers.skipTraversal);
-				};
-
-				// TODO add HMR support for javascript/esm
+					});
 				normalModuleFactory.hooks.parser
-					.for("javascript/auto")
-					.tap("HotModuleReplacementPlugin", handler);
+					.for(JAVASCRIPT_MODULE_TYPE_DYNAMIC)
+					.tap(PLUGIN_NAME, (parser) => {
+						applyModuleHot(parser);
+					});
 				normalModuleFactory.hooks.parser
-					.for("javascript/dynamic")
-					.tap("HotModuleReplacementPlugin", handler);
+					.for(JAVASCRIPT_MODULE_TYPE_ESM)
+					.tap(PLUGIN_NAME, (parser, parserOptions) => {
+						if (
+							isImportMetaFieldEnabled(parserOptions.importMeta, "webpackHot")
+						) {
+							applyImportMetaHot(parser);
+						}
+					});
+				normalModuleFactory.hooks.module.tap(PLUGIN_NAME, (module) => {
+					if (module instanceof NormalModule) module.hot = true;
+					return module;
+				});
 
-				compilation.hooks.normalModuleLoader.tap(
-					"HotModuleReplacementPlugin",
-					context => {
+				NormalModule.getCompilationHooks(compilation).loader.tap(
+					PLUGIN_NAME,
+					(context) => {
 						context.hot = true;
 					}
 				);
 			}
 		);
 	}
-};
+}
 
-const hotInitCode = Template.getFunctionContent(
-	require("./HotModuleReplacement.runtime.js")
-);
+module.exports = HotModuleReplacementPlugin;
diff --git a/lib/HotUpdateChunk.js b/lib/HotUpdateChunk.js
index 17eaf128178..73b3925b074 100644
--- a/lib/HotUpdateChunk.js
+++ b/lib/HotUpdateChunk.js
@@ -2,6 +2,7 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
 const Chunk = require("./Chunk");
@@ -9,7 +10,6 @@ const Chunk = require("./Chunk");
 class HotUpdateChunk extends Chunk {
 	constructor() {
 		super();
-		this.removedModules = undefined;
 	}
 }
 
diff --git a/lib/HotUpdateChunkTemplate.js b/lib/HotUpdateChunkTemplate.js
deleted file mode 100644
index 92ab8db08c9..00000000000
--- a/lib/HotUpdateChunkTemplate.js
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const Template = require("./Template");
-const HotUpdateChunk = require("./HotUpdateChunk");
-const { Tapable, SyncWaterfallHook, SyncHook } = require("tapable");
-
-module.exports = class HotUpdateChunkTemplate extends Tapable {
-	constructor(outputOptions) {
-		super();
-		this.outputOptions = outputOptions || {};
-		this.hooks = {
-			modules: new SyncWaterfallHook([
-				"source",
-				"modules",
-				"removedModules",
-				"moduleTemplate",
-				"dependencyTemplates"
-			]),
-			render: new SyncWaterfallHook([
-				"source",
-				"modules",
-				"removedModules",
-				"hash",
-				"id",
-				"moduleTemplate",
-				"dependencyTemplates"
-			]),
-			hash: new SyncHook(["hash"])
-		};
-	}
-
-	render(
-		id,
-		modules,
-		removedModules,
-		hash,
-		moduleTemplate,
-		dependencyTemplates
-	) {
-		const hotUpdateChunk = new HotUpdateChunk();
-		hotUpdateChunk.id = id;
-		hotUpdateChunk.setModules(modules);
-		hotUpdateChunk.removedModules = removedModules;
-		const modulesSource = Template.renderChunkModules(
-			hotUpdateChunk,
-			m => typeof m.source === "function",
-			moduleTemplate,
-			dependencyTemplates
-		);
-		const core = this.hooks.modules.call(
-			modulesSource,
-			modules,
-			removedModules,
-			moduleTemplate,
-			dependencyTemplates
-		);
-		const source = this.hooks.render.call(
-			core,
-			modules,
-			removedModules,
-			hash,
-			id,
-			moduleTemplate,
-			dependencyTemplates
-		);
-		return source;
-	}
-
-	updateHash(hash) {
-		hash.update("HotUpdateChunkTemplate");
-		hash.update("1");
-		this.hooks.hash.call(hash);
-	}
-};
diff --git a/lib/IgnorePlugin.js b/lib/IgnorePlugin.js
index 5cc6c62f80f..01429879c97 100644
--- a/lib/IgnorePlugin.js
+++ b/lib/IgnorePlugin.js
@@ -2,68 +2,106 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
-class IgnorePlugin {
-	constructor(resourceRegExp, contextRegExp) {
-		this.resourceRegExp = resourceRegExp;
-		this.contextRegExp = contextRegExp;
+const RawModule = require("./RawModule");
+const EntryDependency = require("./dependencies/EntryDependency");
+
+/** @typedef {import("../declarations/plugins/IgnorePlugin").IgnorePluginOptions} IgnorePluginOptions */
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./NormalModuleFactory").ResolveData} ResolveData */
+/** @typedef {import("./ContextModuleFactory").BeforeContextResolveData} BeforeContextResolveData */
+
+/** @typedef {(resource: string, context: string) => boolean} CheckResourceFn */
+
+const PLUGIN_NAME = "IgnorePlugin";
 
+class IgnorePlugin {
+	/**
+	 * Creates an instance of IgnorePlugin.
+	 * @param {IgnorePluginOptions} options IgnorePlugin options
+	 */
+	constructor(options) {
+		/** @type {IgnorePluginOptions} */
+		this.options = options;
 		this.checkIgnore = this.checkIgnore.bind(this);
 	}
 
-	/*
-	 * Only returns true if a "resourceRegExp" exists
-	 * and the resource given matches the regexp.
+	/**
+	 * Note that if "contextRegExp" is given, both the "resourceRegExp" and "contextRegExp" have to match.
+	 * @param {ResolveData | BeforeContextResolveData} resolveData resolve data
+	 * @returns {false | undefined} returns false when the request should be ignored, otherwise undefined
 	 */
-	checkResource(resource) {
-		if (!this.resourceRegExp) {
+	checkIgnore(resolveData) {
+		if (
+			"checkResource" in this.options &&
+			this.options.checkResource &&
+			this.options.checkResource(resolveData.request, resolveData.context)
+		) {
 			return false;
 		}
-		return this.resourceRegExp.test(resource);
-	}
 
-	/*
-	 * Returns true if contextRegExp does not exist
-	 * or if context matches the given regexp.
-	 */
-	checkContext(context) {
-		if (!this.contextRegExp) {
-			return true;
+		if (
+			"resourceRegExp" in this.options &&
+			this.options.resourceRegExp &&
+			this.options.resourceRegExp.test(resolveData.request)
+		) {
+			if ("contextRegExp" in this.options && this.options.contextRegExp) {
+				// if "contextRegExp" is given,
+				// both the "resourceRegExp" and "contextRegExp" have to match.
+				if (this.options.contextRegExp.test(resolveData.context)) {
+					return false;
+				}
+			} else {
+				return false;
+			}
 		}
-		return this.contextRegExp.test(context);
 	}
 
-	/*
-	 * Returns true if result should be ignored.
-	 * false if it shouldn't.
-	 *
-	 * Not that if "contextRegExp" is given, both the "resourceRegExp"
-	 * and "contextRegExp" have to match.
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
 	 */
-	checkResult(result) {
-		if (!result) {
-			return true;
-		}
-		return (
-			this.checkResource(result.request) && this.checkContext(result.context)
-		);
-	}
+	apply(compiler) {
+		compiler.hooks.validate.tap(PLUGIN_NAME, () => {
+			compiler.validate(
+				/** @type {EXPECTED_ANY} */
+				(require("../schemas/plugins/IgnorePlugin.json")),
+				this.options,
+				{
+					name: "Ignore Plugin",
+					baseDataPath: "options"
+				},
+				(options) => require("../schemas/plugins/IgnorePlugin.check")(options)
+			);
+		});
 
-	checkIgnore(result) {
-		// check if result is ignored
-		if (this.checkResult(result)) {
-			return null;
-		}
-		return result;
-	}
+		compiler.hooks.normalModuleFactory.tap(PLUGIN_NAME, (nmf) => {
+			nmf.hooks.beforeResolve.tap(PLUGIN_NAME, (resolveData) => {
+				const result = this.checkIgnore(resolveData);
 
-	apply(compiler) {
-		compiler.hooks.normalModuleFactory.tap("IgnorePlugin", nmf => {
-			nmf.hooks.beforeResolve.tap("IgnorePlugin", this.checkIgnore);
+				if (
+					result === false &&
+					resolveData.dependencies.length > 0 &&
+					resolveData.dependencies[0] instanceof EntryDependency
+				) {
+					const module = new RawModule(
+						"",
+						"ignored-entry-module",
+						"(ignored-entry-module)"
+					);
+					module.factoryMeta = { sideEffectFree: true };
+
+					resolveData.ignoredModule = module;
+				}
+
+				return result;
+			});
 		});
-		compiler.hooks.contextModuleFactory.tap("IgnorePlugin", cmf => {
-			cmf.hooks.beforeResolve.tap("IgnorePlugin", this.checkIgnore);
+		compiler.hooks.contextModuleFactory.tap(PLUGIN_NAME, (cmf) => {
+			cmf.hooks.beforeResolve.tap(PLUGIN_NAME, this.checkIgnore);
 		});
 	}
 }
diff --git a/lib/IgnoreWarningsPlugin.js b/lib/IgnoreWarningsPlugin.js
new file mode 100644
index 00000000000..22a27f5ef13
--- /dev/null
+++ b/lib/IgnoreWarningsPlugin.js
@@ -0,0 +1,42 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./Compilation")} Compilation */
+
+/** @typedef {(warning: Error, compilation: Compilation) => boolean} IgnoreFn */
+
+const PLUGIN_NAME = "IgnoreWarningsPlugin";
+
+class IgnoreWarningsPlugin {
+	/**
+	 * Creates an instance of IgnoreWarningsPlugin.
+	 * @param {IgnoreFn[]} ignoreWarnings conditions to ignore warnings
+	 */
+	constructor(ignoreWarnings) {
+		/** @type {IgnoreFn[]} */
+		this._ignoreWarnings = ignoreWarnings;
+	}
+
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
+	apply(compiler) {
+		compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
+			compilation.hooks.processWarnings.tap(PLUGIN_NAME, (warnings) =>
+				warnings.filter(
+					(warning) =>
+						!this._ignoreWarnings.some((ignore) => ignore(warning, compilation))
+				)
+			);
+		});
+	}
+}
+
+module.exports = IgnoreWarningsPlugin;
diff --git a/lib/InitFragment.js b/lib/InitFragment.js
new file mode 100644
index 00000000000..6d53e80f59a
--- /dev/null
+++ b/lib/InitFragment.js
@@ -0,0 +1,207 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Florent Cailhol @ooflorent
+*/
+
+"use strict";
+
+const { ConcatSource } = require("webpack-sources");
+const makeSerializable = require("./util/makeSerializable");
+
+/** @typedef {import("webpack-sources").Source} Source */
+/** @typedef {import("./Generator").GenerateContext} GenerateContext */
+/** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */
+/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */
+
+/** @typedef {string} InitFragmentKey */
+
+/**
+ * Defines the maybe mergeable init fragment type used by this module.
+ * @template GenerateContext
+ * @typedef {object} MaybeMergeableInitFragment
+ * @property {InitFragmentKey=} key
+ * @property {number} stage
+ * @property {number} position
+ * @property {(context: GenerateContext) => string | Source | undefined} getContent
+ * @property {(context: GenerateContext) => string | Source | undefined} getEndContent
+ * @property {(fragments: MaybeMergeableInitFragment) => MaybeMergeableInitFragment=} merge
+ * @property {(fragments: MaybeMergeableInitFragment[]) => MaybeMergeableInitFragment[]=} mergeAll
+ */
+
+/**
+ * Represents InitFragment.
+ * @template GenerateContext
+ * @implements {MaybeMergeableInitFragment}
+ */
+class InitFragment {
+	/**
+	 * Creates an instance of InitFragment.
+	 * @param {string | Source | undefined} content the source code that will be included as initialization code
+	 * @param {number} stage category of initialization code (contribute to order)
+	 * @param {number} position position in the category (contribute to order)
+	 * @param {InitFragmentKey=} key unique key to avoid emitting the same initialization code twice
+	 * @param {string | Source=} endContent the source code that will be included at the end of the module
+	 */
+	constructor(content, stage, position, key, endContent) {
+		/** @type {string | Source | undefined} */
+		this.content = content;
+		/** @type {number} */
+		this.stage = stage;
+		/** @type {number} */
+		this.position = position;
+		/** @type {string | undefined} */
+		this.key = key;
+		/** @type {string | Source | undefined} */
+		this.endContent = endContent;
+	}
+
+	/**
+	 * Returns the source code that will be included as initialization code.
+	 * @param {GenerateContext} context context
+	 * @returns {string | Source | undefined} the source code that will be included as initialization code
+	 */
+	getContent(context) {
+		return this.content;
+	}
+
+	/**
+	 * Returns the source code that will be included at the end of the module.
+	 * @param {GenerateContext} context context
+	 * @returns {string | Source | undefined} the source code that will be included at the end of the module
+	 */
+	getEndContent(context) {
+		return this.endContent;
+	}
+
+	/**
+	 * Adds the provided source to the init fragment.
+	 * @template Context
+	 * @param {Source} source sources
+	 * @param {MaybeMergeableInitFragment[]} initFragments init fragments
+	 * @param {Context} context context
+	 * @returns {Source} source
+	 */
+	static addToSource(source, initFragments, context) {
+		if (initFragments.length > 0) {
+			// Sort fragment indices by (stage, position), falling back to the
+			// original index — one flat number array instead of N [fragment, index]
+			// tuples for a stable-by-index order.
+			const sortedIndices = initFragments.map((_, index) => index);
+			sortedIndices.sort((a, b) => {
+				const fragmentA = initFragments[a];
+				const fragmentB = initFragments[b];
+				const stageCmp = fragmentA.stage - fragmentB.stage;
+				if (stageCmp !== 0) return stageCmp;
+				const positionCmp = fragmentA.position - fragmentB.position;
+				if (positionCmp !== 0) return positionCmp;
+				return a - b;
+			});
+
+			// Deduplicate fragments. If a fragment has no key, it is always included.
+			// Keyless fragments get a unique numeric key; number keys never collide
+			// with the string `InitFragmentKey`s used for keyed fragments.
+			/** @type {Map | MaybeMergeableInitFragment[]>} */
+			const keyedFragments = new Map();
+			let keylessKey = 0;
+			for (const index of sortedIndices) {
+				const fragment = initFragments[index];
+				if (typeof fragment.mergeAll === "function") {
+					if (!fragment.key) {
+						throw new Error(
+							`InitFragment with mergeAll function must have a valid key: ${fragment.constructor.name}`
+						);
+					}
+					const oldValue = keyedFragments.get(fragment.key);
+					if (oldValue === undefined) {
+						keyedFragments.set(fragment.key, fragment);
+					} else if (Array.isArray(oldValue)) {
+						oldValue.push(fragment);
+					} else {
+						keyedFragments.set(fragment.key, [oldValue, fragment]);
+					}
+					continue;
+				} else if (typeof fragment.merge === "function") {
+					const key = /** @type {InitFragmentKey} */ (fragment.key);
+					const oldValue =
+						/** @type {MaybeMergeableInitFragment} */
+						(keyedFragments.get(key));
+					if (oldValue !== undefined) {
+						keyedFragments.set(key, fragment.merge(oldValue));
+						continue;
+					}
+				}
+				keyedFragments.set(fragment.key || keylessKey++, fragment);
+			}
+
+			const concatSource = new ConcatSource();
+			/** @type {(string | Source)[]} */
+			const endContents = [];
+			for (let fragment of keyedFragments.values()) {
+				if (Array.isArray(fragment)) {
+					fragment =
+						/** @type {[MaybeMergeableInitFragment & { mergeAll: (fragments: MaybeMergeableInitFragment[]) => MaybeMergeableInitFragment[] }, ...MaybeMergeableInitFragment[]]} */
+						(fragment)[0].mergeAll(fragment);
+				}
+				const content =
+					/** @type {MaybeMergeableInitFragment} */
+					(fragment).getContent(context);
+				if (content) {
+					concatSource.add(content);
+				}
+				const endContent =
+					/** @type {MaybeMergeableInitFragment} */
+					(fragment).getEndContent(context);
+				if (endContent) {
+					endContents.push(endContent);
+				}
+			}
+
+			concatSource.add(source);
+			for (const content of endContents.reverse()) {
+				concatSource.add(content);
+			}
+			return concatSource;
+		}
+		return source;
+	}
+
+	/**
+	 * Serializes this instance into the provided serializer context.
+	 * @param {ObjectSerializerContext} context context
+	 */
+	serialize(context) {
+		const { write } = context;
+
+		write(this.content);
+		write(this.stage);
+		write(this.position);
+		write(this.key);
+		write(this.endContent);
+	}
+
+	/**
+	 * Restores this instance from the provided deserializer context.
+	 * @param {ObjectDeserializerContext} context context
+	 */
+	deserialize(context) {
+		const { read } = context;
+
+		this.content = read();
+		this.stage = read();
+		this.position = read();
+		this.key = read();
+		this.endContent = read();
+	}
+}
+
+makeSerializable(InitFragment, "webpack/lib/InitFragment");
+
+InitFragment.STAGE_CONSTANTS = 10;
+InitFragment.STAGE_ASYNC_BOUNDARY = 20;
+InitFragment.STAGE_HARMONY_EXPORTS = 30;
+InitFragment.STAGE_HARMONY_IMPORTS = 40;
+InitFragment.STAGE_PROVIDES = 50;
+InitFragment.STAGE_ASYNC_DEPENDENCIES = 60;
+InitFragment.STAGE_ASYNC_HARMONY_IMPORTS = 70;
+
+module.exports = InitFragment;
diff --git a/lib/JavascriptGenerator.js b/lib/JavascriptGenerator.js
deleted file mode 100644
index 882f7d4d4a6..00000000000
--- a/lib/JavascriptGenerator.js
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const { RawSource, ReplaceSource } = require("webpack-sources");
-
-// TODO: clean up this file
-// replace with newer constructs
-
-// TODO: remove DependencyVariables and replace them with something better
-
-class JavascriptGenerator {
-	generate(module, dependencyTemplates, runtimeTemplate) {
-		const originalSource = module.originalSource();
-		if (!originalSource) {
-			return new RawSource("throw new Error('No source available');");
-		}
-
-		const source = new ReplaceSource(originalSource);
-
-		this.sourceBlock(
-			module,
-			module,
-			[],
-			dependencyTemplates,
-			source,
-			runtimeTemplate
-		);
-
-		return source;
-	}
-
-	sourceBlock(
-		module,
-		block,
-		availableVars,
-		dependencyTemplates,
-		source,
-		runtimeTemplate
-	) {
-		for (const dependency of block.dependencies) {
-			this.sourceDependency(
-				dependency,
-				dependencyTemplates,
-				source,
-				runtimeTemplate
-			);
-		}
-
-		/**
-		 * Get the variables of all blocks that we need to inject.
-		 * These will contain the variable name and its expression.
-		 * The name will be added as a parameter in a IIFE the expression as its value.
-		 */
-		const vars = block.variables.reduce((result, value) => {
-			const variable = this.sourceVariables(
-				value,
-				availableVars,
-				dependencyTemplates,
-				runtimeTemplate
-			);
-
-			if (variable) {
-				result.push(variable);
-			}
-
-			return result;
-		}, []);
-
-		/**
-		 * if we actually have variables
-		 * this is important as how #splitVariablesInUniqueNamedChunks works
-		 * it will always return an array in an array which would lead to a IIFE wrapper around
-		 * a module if we do this with an empty vars array.
-		 */
-		if (vars.length > 0) {
-			/**
-			 * Split all variables up into chunks of unique names.
-			 * e.g. imagine you have the following variable names that need to be injected:
-			 * [foo, bar, baz, foo, some, more]
-			 * we can not inject "foo" twice, therefore we just make two IIFEs like so:
-			 * (function(foo, bar, baz){
-			 *   (function(foo, some, more){
-			 *     …
-			 *   }(…));
-			 * }(…));
-			 *
-			 * "splitVariablesInUniqueNamedChunks" splits the variables shown above up to this:
-			 * [[foo, bar, baz], [foo, some, more]]
-			 */
-			const injectionVariableChunks = this.splitVariablesInUniqueNamedChunks(
-				vars
-			);
-
-			// create all the beginnings of IIFEs
-			const functionWrapperStarts = injectionVariableChunks.map(
-				variableChunk => {
-					return this.variableInjectionFunctionWrapperStartCode(
-						variableChunk.map(variable => variable.name)
-					);
-				}
-			);
-
-			// and all the ends
-			const functionWrapperEnds = injectionVariableChunks.map(variableChunk => {
-				return this.variableInjectionFunctionWrapperEndCode(
-					module,
-					variableChunk.map(variable => variable.expression),
-					block
-				);
-			});
-
-			// join them to one big string
-			const varStartCode = functionWrapperStarts.join("");
-
-			// reverse the ends first before joining them, as the last added must be the inner most
-			const varEndCode = functionWrapperEnds.reverse().join("");
-
-			// if we have anything, add it to the source
-			if (varStartCode && varEndCode) {
-				const start = block.range ? block.range[0] : -10;
-				const end = block.range
-					? block.range[1]
-					: module.originalSource().size() + 1;
-				source.insert(start + 0.5, varStartCode);
-				source.insert(end + 0.5, "\n/* WEBPACK VAR INJECTION */" + varEndCode);
-			}
-		}
-
-		for (const childBlock of block.blocks) {
-			this.sourceBlock(
-				module,
-				childBlock,
-				availableVars.concat(vars),
-				dependencyTemplates,
-				source,
-				runtimeTemplate
-			);
-		}
-	}
-
-	sourceDependency(dependency, dependencyTemplates, source, runtimeTemplate) {
-		const template = dependencyTemplates.get(dependency.constructor);
-		if (!template) {
-			throw new Error(
-				"No template for dependency: " + dependency.constructor.name
-			);
-		}
-		template.apply(dependency, source, runtimeTemplate, dependencyTemplates);
-	}
-
-	sourceVariables(
-		variable,
-		availableVars,
-		dependencyTemplates,
-		runtimeTemplate
-	) {
-		const name = variable.name;
-		const expr = variable.expressionSource(
-			dependencyTemplates,
-			runtimeTemplate
-		);
-
-		if (
-			availableVars.some(
-				v => v.name === name && v.expression.source() === expr.source()
-			)
-		) {
-			return;
-		}
-		return {
-			name: name,
-			expression: expr
-		};
-	}
-
-	/*
-	 * creates the start part of a IIFE around the module to inject a variable name
-	 * (function(…){   <- this part
-	 * }.call(…))
-	 */
-	variableInjectionFunctionWrapperStartCode(varNames) {
-		const args = varNames.join(", ");
-		return `/* WEBPACK VAR INJECTION */(function(${args}) {`;
-	}
-
-	contextArgument(module, block) {
-		if (this === block) {
-			return module.exportsArgument;
-		}
-		return "this";
-	}
-
-	/*
-	 * creates the end part of a IIFE around the module to inject a variable name
-	 * (function(…){
-	 * }.call(…))   <- this part
-	 */
-	variableInjectionFunctionWrapperEndCode(module, varExpressions, block) {
-		const firstParam = this.contextArgument(module, block);
-		const furtherParams = varExpressions.map(e => e.source()).join(", ");
-		return `}.call(${firstParam}, ${furtherParams}))`;
-	}
-
-	splitVariablesInUniqueNamedChunks(vars) {
-		const startState = [[]];
-		return vars.reduce((chunks, variable) => {
-			const current = chunks[chunks.length - 1];
-			// check if variable with same name exists already
-			// if so create a new chunk of variables.
-			const variableNameAlreadyExists = current.some(
-				v => v.name === variable.name
-			);
-
-			if (variableNameAlreadyExists) {
-				// start new chunk with current variable
-				chunks.push([variable]);
-			} else {
-				// else add it to current chunk
-				current.push(variable);
-			}
-			return chunks;
-		}, startState);
-	}
-}
-
-module.exports = JavascriptGenerator;
diff --git a/lib/JavascriptMetaInfoPlugin.js b/lib/JavascriptMetaInfoPlugin.js
new file mode 100644
index 00000000000..0ab5504de09
--- /dev/null
+++ b/lib/JavascriptMetaInfoPlugin.js
@@ -0,0 +1,108 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Sergey Melyukov @smelukov
+*/
+
+"use strict";
+
+const {
+	JAVASCRIPT_MODULE_TYPE_AUTO,
+	JAVASCRIPT_MODULE_TYPE_DYNAMIC,
+	JAVASCRIPT_MODULE_TYPE_ESM
+} = require("./ModuleTypeConstants");
+const { getInnerGraphUtils } = require("./optimize/InnerGraph");
+
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./Module").BuildInfo} BuildInfo */
+/** @typedef {import("./javascript/JavascriptModule").JavascriptModuleBuildInfo} JavascriptModuleBuildInfo */
+/** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */
+
+const PLUGIN_NAME = "JavascriptMetaInfoPlugin";
+
+class JavascriptMetaInfoPlugin {
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
+	apply(compiler) {
+		compiler.hooks.compilation.tap(
+			PLUGIN_NAME,
+			(compilation, { normalModuleFactory }) => {
+				const innerGraph = getInnerGraphUtils(compilation);
+				/**
+				 * Handles the hook callback for this code path.
+				 * @param {JavascriptParser} parser the parser
+				 * @returns {void}
+				 */
+				const handler = (parser) => {
+					parser.hooks.program.tap(PLUGIN_NAME, (ast) => {
+						// Nested blocks keep their own disposal scope, only a module-scope
+						// declaration ties disposal to the end of module evaluation.
+						if (
+							ast.body.some(
+								(statement) =>
+									statement.type === "VariableDeclaration" &&
+									(statement.kind === "using" ||
+										statement.kind === "await using")
+							)
+						) {
+							const buildInfo =
+								/** @type {JavascriptModuleBuildInfo} */
+								(parser.state.module.buildInfo);
+							buildInfo.usesTopLevelUsingDeclaration = true;
+							// Concatenation would defer disposal to the enclosing scope.
+							buildInfo.moduleConcatenationBailout =
+								"a top-level using declaration";
+						}
+					});
+					parser.hooks.call.for("eval").tap(PLUGIN_NAME, () => {
+						const buildInfo =
+							/** @type {JavascriptModuleBuildInfo} */
+							(parser.state.module.buildInfo);
+						buildInfo.moduleConcatenationBailout = "eval()";
+						const currentSymbol = innerGraph.getTopLevelSymbol(parser.state);
+						if (currentSymbol) {
+							innerGraph.addUsage(parser.state, null, currentSymbol);
+						} else {
+							// Only worth reporting when the analysis was actually running
+							const wasEnabled = innerGraph.isEnabled(parser.state);
+							innerGraph.bailout(parser.state);
+							if (wasEnabled) {
+								compilation.moduleGraph
+									.getOptimizationBailout(parser.state.module)
+									.push("Inner graph bailout: eval()");
+							}
+						}
+					});
+					parser.hooks.finish.tap(PLUGIN_NAME, () => {
+						const buildInfo =
+							/** @type {BuildInfo} */
+							(parser.state.module.buildInfo);
+						let topLevelDeclarations = buildInfo.topLevelDeclarations;
+						if (topLevelDeclarations === undefined) {
+							topLevelDeclarations = buildInfo.topLevelDeclarations = new Set();
+						}
+						for (const name of parser.scope.definitions.asSet()) {
+							if (parser.isVariableDefined(name)) {
+								topLevelDeclarations.add(name);
+							}
+						}
+					});
+				};
+
+				normalModuleFactory.hooks.parser
+					.for(JAVASCRIPT_MODULE_TYPE_AUTO)
+					.tap(PLUGIN_NAME, handler);
+				normalModuleFactory.hooks.parser
+					.for(JAVASCRIPT_MODULE_TYPE_DYNAMIC)
+					.tap(PLUGIN_NAME, handler);
+				normalModuleFactory.hooks.parser
+					.for(JAVASCRIPT_MODULE_TYPE_ESM)
+					.tap(PLUGIN_NAME, handler);
+			}
+		);
+	}
+}
+
+module.exports = JavascriptMetaInfoPlugin;
diff --git a/lib/JavascriptModulesPlugin.js b/lib/JavascriptModulesPlugin.js
deleted file mode 100644
index 5e0aaf8c4c7..00000000000
--- a/lib/JavascriptModulesPlugin.js
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const Parser = require("./Parser");
-const Template = require("./Template");
-const { ConcatSource } = require("webpack-sources");
-const JavascriptGenerator = require("./JavascriptGenerator");
-const createHash = require("./util/createHash");
-
-class JavascriptModulesPlugin {
-	apply(compiler) {
-		compiler.hooks.compilation.tap(
-			"JavascriptModulesPlugin",
-			(compilation, { normalModuleFactory }) => {
-				normalModuleFactory.hooks.createParser
-					.for("javascript/auto")
-					.tap("JavascriptModulesPlugin", options => {
-						return new Parser(options, "auto");
-					});
-				normalModuleFactory.hooks.createParser
-					.for("javascript/dynamic")
-					.tap("JavascriptModulesPlugin", options => {
-						return new Parser(options, "script");
-					});
-				normalModuleFactory.hooks.createParser
-					.for("javascript/esm")
-					.tap("JavascriptModulesPlugin", options => {
-						return new Parser(options, "module");
-					});
-				normalModuleFactory.hooks.createGenerator
-					.for("javascript/auto")
-					.tap("JavascriptModulesPlugin", () => {
-						return new JavascriptGenerator();
-					});
-				normalModuleFactory.hooks.createGenerator
-					.for("javascript/dynamic")
-					.tap("JavascriptModulesPlugin", () => {
-						return new JavascriptGenerator();
-					});
-				normalModuleFactory.hooks.createGenerator
-					.for("javascript/esm")
-					.tap("JavascriptModulesPlugin", () => {
-						return new JavascriptGenerator();
-					});
-				compilation.mainTemplate.hooks.renderManifest.tap(
-					"JavascriptModulesPlugin",
-					(result, options) => {
-						const chunk = options.chunk;
-						const hash = options.hash;
-						const fullHash = options.fullHash;
-						const outputOptions = options.outputOptions;
-						const moduleTemplates = options.moduleTemplates;
-						const dependencyTemplates = options.dependencyTemplates;
-
-						const filenameTemplate =
-							chunk.filenameTemplate || outputOptions.filename;
-
-						const useChunkHash = compilation.mainTemplate.useChunkHash(chunk);
-
-						result.push({
-							render: () =>
-								compilation.mainTemplate.render(
-									hash,
-									chunk,
-									moduleTemplates.javascript,
-									dependencyTemplates
-								),
-							filenameTemplate,
-							pathOptions: {
-								noChunkHash: !useChunkHash,
-								contentHashType: "javascript",
-								chunk
-							},
-							identifier: `chunk${chunk.id}`,
-							hash: useChunkHash ? chunk.hash : fullHash
-						});
-						return result;
-					}
-				);
-				compilation.mainTemplate.hooks.modules.tap(
-					"JavascriptModulesPlugin",
-					(source, chunk, hash, moduleTemplate, dependencyTemplates) => {
-						return Template.renderChunkModules(
-							chunk,
-							m => typeof m.source === "function",
-							moduleTemplate,
-							dependencyTemplates,
-							"/******/ "
-						);
-					}
-				);
-				compilation.chunkTemplate.hooks.renderManifest.tap(
-					"JavascriptModulesPlugin",
-					(result, options) => {
-						const chunk = options.chunk;
-						const outputOptions = options.outputOptions;
-						const moduleTemplates = options.moduleTemplates;
-						const dependencyTemplates = options.dependencyTemplates;
-						const filenameTemplate =
-							chunk.filenameTemplate || outputOptions.chunkFilename;
-
-						result.push({
-							render: () =>
-								this.renderJavascript(
-									compilation.chunkTemplate,
-									chunk,
-									moduleTemplates.javascript,
-									dependencyTemplates
-								),
-							filenameTemplate,
-							pathOptions: {
-								chunk,
-								contentHashType: "javascript"
-							},
-							identifier: `chunk${chunk.id}`,
-							hash: chunk.hash
-						});
-
-						return result;
-					}
-				);
-				compilation.hooks.contentHash.tap("JavascriptModulesPlugin", chunk => {
-					const outputOptions = compilation.outputOptions;
-					const {
-						hashSalt,
-						hashDigest,
-						hashDigestLength,
-						hashFunction
-					} = outputOptions;
-					const hash = createHash(hashFunction);
-					if (hashSalt) hash.update(hashSalt);
-					const template = chunk.hasRuntime()
-						? compilation.mainTemplate
-						: compilation.chunkTemplate;
-					template.updateHashForChunk(hash, chunk);
-					for (const m of chunk.modulesIterable) {
-						if (typeof m.source === "function") {
-							hash.update(m.hash);
-						}
-					}
-					chunk.contentHash.javascript = hash
-						.digest(hashDigest)
-						.substr(0, hashDigestLength);
-				});
-			}
-		);
-	}
-
-	renderJavascript(chunkTemplate, chunk, moduleTemplate, dependencyTemplates) {
-		const moduleSources = Template.renderChunkModules(
-			chunk,
-			m => typeof m.source === "function",
-			moduleTemplate,
-			dependencyTemplates
-		);
-		const core = chunkTemplate.hooks.modules.call(
-			moduleSources,
-			chunk,
-			moduleTemplate,
-			dependencyTemplates
-		);
-		let source = chunkTemplate.hooks.render.call(
-			core,
-			chunk,
-			moduleTemplate,
-			dependencyTemplates
-		);
-		if (chunk.hasEntryModule()) {
-			source = chunkTemplate.hooks.renderWithEntry.call(source, chunk);
-		}
-		chunk.rendered = true;
-		return new ConcatSource(source, ";");
-	}
-}
-
-module.exports = JavascriptModulesPlugin;
diff --git a/lib/JsonGenerator.js b/lib/JsonGenerator.js
deleted file mode 100644
index 508ae5ccc51..00000000000
--- a/lib/JsonGenerator.js
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const { ConcatSource } = require("webpack-sources");
-
-const stringifySafe = data =>
-	JSON.stringify(data).replace(
-		/\u2028|\u2029/g,
-		str => (str === "\u2029" ? "\\u2029" : "\\u2028")
-	); // invalid in JavaScript but valid JSON
-
-class JsonGenerator {
-	generate(module, dependencyTemplates, runtimeTemplate) {
-		const source = new ConcatSource();
-		const data = module.buildInfo.jsonData;
-		if (
-			Array.isArray(module.buildMeta.providedExports) &&
-			!module.isUsed("default")
-		) {
-			// Only some exports are used: We can optimize here, by only generating a part of the JSON
-			const reducedJson = {};
-			for (const exportName of module.buildMeta.providedExports) {
-				if (exportName === "default") continue;
-				const used = module.isUsed(exportName);
-				if (used) {
-					reducedJson[used] = data[exportName];
-				}
-			}
-			source.add(
-				`${module.moduleArgument}.exports = ${stringifySafe(reducedJson)};`
-			);
-		} else {
-			source.add(`${module.moduleArgument}.exports = ${stringifySafe(data)};`);
-		}
-		return source;
-	}
-}
-
-module.exports = JsonGenerator;
diff --git a/lib/JsonModulesPlugin.js b/lib/JsonModulesPlugin.js
deleted file mode 100644
index 20b8a034c8a..00000000000
--- a/lib/JsonModulesPlugin.js
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const JsonParser = require("./JsonParser");
-const JsonGenerator = require("./JsonGenerator");
-
-class JsonModulesPlugin {
-	apply(compiler) {
-		compiler.hooks.compilation.tap(
-			"JsonModulesPlugin",
-			(compilation, { normalModuleFactory }) => {
-				normalModuleFactory.hooks.createParser
-					.for("json")
-					.tap("JsonModulesPlugin", () => {
-						return new JsonParser();
-					});
-				normalModuleFactory.hooks.createGenerator
-					.for("json")
-					.tap("JsonModulesPlugin", () => {
-						return new JsonGenerator();
-					});
-			}
-		);
-	}
-}
-
-module.exports = JsonModulesPlugin;
diff --git a/lib/JsonParser.js b/lib/JsonParser.js
deleted file mode 100644
index f0c59777bb3..00000000000
--- a/lib/JsonParser.js
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const parseJson = require("json-parse-better-errors");
-const JsonExportsDependency = require("./dependencies/JsonExportsDependency");
-
-class JsonParser {
-	constructor(options) {
-		this.options = options;
-	}
-
-	parse(source, state) {
-		const data = parseJson(source[0] === "\ufeff" ? source.slice(1) : source);
-		state.module.buildInfo.jsonData = data;
-		state.module.buildMeta.exportsType = "named";
-		if (typeof data === "object" && data) {
-			state.module.addDependency(new JsonExportsDependency(Object.keys(data)));
-		}
-		state.module.addDependency(new JsonExportsDependency(["default"]));
-		return state;
-	}
-}
-
-module.exports = JsonParser;
diff --git a/lib/LazyBarrel.js b/lib/LazyBarrel.js
new file mode 100644
index 00000000000..eef84021c48
--- /dev/null
+++ b/lib/LazyBarrel.js
@@ -0,0 +1,389 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Haijie Xie @hai-x
+*/
+
+"use strict";
+
+const Dependency = require("./Dependency");
+
+/** @typedef {import("./Compilation")} Compilation */
+/** @typedef {import("./Compilation").DependencyConstructor} DependencyConstructor */
+/** @typedef {import("./Module")} Module */
+/** @typedef {import("./ModuleFactory")} ModuleFactory */
+
+/**
+ * Defines the deferred request group type used by this module.
+ * @typedef {object} DependencyGroup
+ * @property {ModuleFactory} factory factory for the request
+ * @property {Dependency[]} dependencies deferred dependencies of the request
+ * @property {string | undefined} context request context
+ */
+
+/**
+ * Defines the lazy barrel state type used by this module.
+ * @typedef {object} LazyBarrelState
+ * @property {Set | true} forwardedIds export names requested so far, true for all
+ * @property {LazyBarrelInfo | undefined} lazyBarrelInfo deferred dependencies, undefined until classified
+ */
+
+/**
+ * Defines the lazy barrel unlazy item type used by this module.
+ * @typedef {object} UnlazyDependencyInfo
+ * @property {ModuleFactory} factory factory for the request
+ * @property {Dependency[]} dependencies deferred dependencies of the request
+ * @property {string | undefined} context request context
+ * @property {Module} originModule the lazy barrel module
+ */
+
+/**
+ * Collects the export names a dependency group requests from its target.
+ * @param {Dependency[]} dependencies dependency group resolving to one module
+ * @returns {Set | true} requested export names, true for all
+ */
+function getForwardedIds(dependencies) {
+	/** @type {Set} */
+	const ids = new Set();
+	for (const dependency of dependencies) {
+		// TODO remove in webpack 6
+		// It may be missing on custom dependency types not extending the base Dependency
+		if (!("getForwardId" in dependency)) continue;
+
+		const id = dependency.getForwardId();
+		if (id === true) return true;
+		if (id !== null) ids.add(id);
+	}
+	return ids;
+}
+
+/**
+ * Tracks the deferred (not yet factorized/built) dependencies of a
+ * side-effect-free barrel module and which export names resolve to them.
+ */
+class LazyBarrelInfo {
+	constructor() {
+		/** @type {Map} forward id -> request key */
+		this._forwardIdToRequest = new Map();
+		/** @type {Map} request key -> still-deferred group */
+		this._requestToDepGroup = new Map();
+		/** @type {Set | undefined} locally provided export names */
+		this._terminalIds = undefined;
+		/** @type {Set | undefined} requests of star re-exports */
+		this._fallbackRequests = undefined;
+	}
+
+	/**
+	 * Records a deferred dependency under its request key.
+	 * @param {string} requestKey request key
+	 * @param {Dependency} dependency dependency to defer
+	 * @param {ModuleFactory} factory factory for the request
+	 * @param {string | undefined} context request context
+	 */
+	addLazy(requestKey, dependency, factory, context) {
+		let group = this._requestToDepGroup.get(requestKey);
+		if (group === undefined) {
+			group = { factory, dependencies: [], context };
+			this._requestToDepGroup.set(requestKey, group);
+		}
+		group.dependencies.push(dependency);
+	}
+
+	/**
+	 * Maps an export name to the request providing it.
+	 * @param {string} id export name
+	 * @param {string} requestKey request key
+	 */
+	addForwardId(id, requestKey) {
+		this._forwardIdToRequest.set(id, requestKey);
+	}
+
+	/**
+	 * Registers a locally provided export name.
+	 * @param {string} id export name
+	 */
+	addTerminal(id) {
+		if (this._terminalIds === undefined) this._terminalIds = new Set();
+		this._terminalIds.add(id);
+	}
+
+	/**
+	 * Registers a star re-export request key.
+	 * @param {string} requestKey request key
+	 */
+	addFallback(requestKey) {
+		if (this._fallbackRequests === undefined) {
+			this._fallbackRequests = new Set();
+		}
+		this._fallbackRequests.add(requestKey);
+	}
+
+	/**
+	 * Returns whether nothing is deferred.
+	 * @returns {boolean} true, when no dependency is deferred
+	 */
+	isEmpty() {
+		return this._requestToDepGroup.size === 0;
+	}
+
+	/**
+	 * Returns the requests of the still-lazy groups.
+	 * @returns {Iterable} still-lazy requests
+	 */
+	getLazyRequests() {
+		return this._requestToDepGroup.keys();
+	}
+
+	/**
+	 * Drops the export-name lookups once no group stays deferred; they only serve
+	 * to find still-deferred groups, so keeping them per module wastes memory.
+	 */
+	_release() {
+		this._forwardIdToRequest.clear();
+		this._terminalIds = undefined;
+		this._fallbackRequests = undefined;
+	}
+
+	/**
+	 * Removes and returns the groups needed to provide the requested export names.
+	 * @param {Set | true} forwardedIds requested export names, true for all
+	 * @returns {DependencyGroup[]} groups that must be processed now
+	 */
+	request(forwardedIds) {
+		/** @type {DependencyGroup[]} */
+		const result = [];
+		/**
+		 * @param {DependencyGroup} group taken group
+		 */
+		const unlazy = (group) => {
+			for (const dependency of group.dependencies) {
+				dependency.setLazy(false);
+			}
+			result.push(group);
+		};
+		if (forwardedIds === true) {
+			for (const group of this._requestToDepGroup.values()) unlazy(group);
+			this._requestToDepGroup.clear();
+			this._release();
+			return result;
+		}
+		/**
+		 * @param {string} requestKey request key to take
+		 */
+		const take = (requestKey) => {
+			const group = this._requestToDepGroup.get(requestKey);
+			if (group === undefined) return;
+			this._requestToDepGroup.delete(requestKey);
+			unlazy(group);
+		};
+		for (const id of forwardedIds) {
+			if (this._terminalIds !== undefined && this._terminalIds.has(id)) {
+				continue;
+			}
+			const requestKey = this._forwardIdToRequest.get(id);
+			if (requestKey !== undefined) {
+				take(requestKey);
+			} else if (this._fallbackRequests !== undefined) {
+				// unknown name: any star re-export may provide it
+				for (const fallbackKey of this._fallbackRequests) take(fallbackKey);
+			}
+		}
+		if (this._requestToDepGroup.size === 0) this._release();
+		return result;
+	}
+}
+
+const esmDependencyCategory = "esm";
+
+/**
+ * Owns the per side-effect-free module lazy barrel state for a `Compilation`,
+ * keeping the deferral bookkeeping out of `Compilation` itself.
+ */
+class LazyBarrelController {
+	/**
+	 * @param {Compilation} compilation the owning compilation
+	 */
+	constructor(compilation) {
+		/** @type {Compilation} */
+		this._compilation = compilation;
+		/** @type {WeakMap} */
+		this._modules = new WeakMap();
+	}
+
+	/**
+	 * Releases all per-module deferral state. The last consumer is
+	 * `FlagDependencyExportsPlugin`'s cache key during `finishModules`, so the
+	 * bookkeeping is dead weight once sealing starts.
+	 */
+	clear() {
+		this._modules = new WeakMap();
+	}
+
+	/**
+	 * requests of the re-export targets `module` still keeps lazy.
+	 * `FlagDependencyExportsPlugin` folds them into its provided-exports cache
+	 * key, so a barrel whose built targets differ from a cached run does not
+	 * reuse that run's stale exports.
+	 * @param {Module} module the module
+	 * @returns {Iterable | undefined} still-lazy requests, if any
+	 */
+	getLazyRequests(module) {
+		const factoryMeta = module.factoryMeta;
+		if (factoryMeta === undefined || !factoryMeta.sideEffectFree) return;
+		const state = this._modules.get(module);
+		if (state === undefined) return;
+		const info = state.lazyBarrelInfo;
+		if (info === undefined || info.isEmpty()) return;
+		return info.getLazyRequests();
+	}
+
+	/**
+	 * Classifies the re-export dependencies of a side-effect-free module (lazy barrel)
+	 * into deferrable groups and marks the deferred ones.
+	 * @param {Module} module the module whose dependencies are processed
+	 * @returns {boolean} true, when some dependencies were deferred
+	 */
+	classify(module) {
+		const modules = this._modules;
+		const factoryMeta = module.factoryMeta;
+		if (factoryMeta === undefined || !factoryMeta.sideEffectFree) {
+			return false;
+		}
+		const dependencies = module.dependencies;
+		const dependencyFactories = this._compilation.dependencyFactories;
+		/** @type {LazyBarrelInfo | undefined} */
+		let info;
+		let hasFallback = false;
+		for (const dep of dependencies) {
+			// TODO remove in webpack 6
+			// It may be missing on custom dependency types not extending the base Dependency
+			if (!("getLazyUntil" in dep)) continue;
+
+			const until = dep.getLazyUntil();
+			// null (eager) and LAZY_UNTIL_LOCAL (terminal) defer nothing; terminals are
+			// only consulted alongside a star re-export, so they are recorded below
+			if (until === null || until === Dependency.LAZY_UNTIL_LOCAL) continue;
+			// deferrable: setLazy is needed to toggle its deferred state
+			if (!("setLazy" in dep)) continue;
+			const resourceIdent = dep.getResourceIdentifier();
+			if (resourceIdent === null) continue;
+			const factory = dependencyFactories.get(
+				/** @type {DependencyConstructor} */ (dep.constructor)
+			);
+			if (factory === undefined) continue;
+			const category = dep.category;
+			// Match the request grouping key of `processDependencyForResolving`
+			const requestKey =
+				category === esmDependencyCategory
+					? resourceIdent
+					: `${category}${resourceIdent}`;
+			if (info === undefined) info = new LazyBarrelInfo();
+			if (dep.isLazy()) {
+				info.addLazy(requestKey, dep, factory, dep.getContext());
+			}
+			if (until === Dependency.LAZY_UNTIL_ID) {
+				info.addForwardId(
+					/** @type {string} */ (dep.getLazyName()),
+					requestKey
+				);
+			} else if (until === Dependency.LAZY_UNTIL_FALLBACK) {
+				info.addFallback(requestKey);
+				hasFallback = true;
+			}
+		}
+		const state = modules.get(module);
+		// info is only created once a deferrable target exists, so it is never empty here
+		if (info === undefined) {
+			if (state !== undefined) modules.delete(module);
+			return false;
+		}
+		// terminals only matter together with a star re-export (to avoid building it
+		// for a locally-provided name); skip the extra pass otherwise
+		if (hasFallback) {
+			for (const dep of dependencies) {
+				if (
+					"getLazyUntil" in dep &&
+					dep.getLazyUntil() === Dependency.LAZY_UNTIL_LOCAL
+				) {
+					info.addTerminal(/** @type {string} */ (dep.getLazyName()));
+				}
+			}
+		}
+		if (state !== undefined) {
+			// barrel classified after its targets were already requested: replay the
+			// recorded ids so processDependency un-lazies and builds those targets now
+			info.request(state.forwardedIds);
+			// stay lazy only while some targets remain deferred
+			if (info.isEmpty()) return false;
+			state.lazyBarrelInfo = info;
+			return true;
+		}
+
+		modules.set(module, {
+			forwardedIds: new Set(),
+			lazyBarrelInfo: info
+		});
+
+		return true;
+	}
+
+	/**
+	 * Requests the export names that `dependencies` need from a lazy barrel and
+	 * returns the deferred re-export targets that must be built now. Records the
+	 * request when the barrel's dependencies were not classified yet.
+	 * @param {Module} module the resolved module
+	 * @param {Dependency[]} dependencies the dependencies that resolved to the module
+	 * @returns {UnlazyDependencyInfo[] | undefined} items to process, if any
+	 */
+	request(module, dependencies) {
+		// state only exists for side-effect-free modules, so a non-side-effect-free
+		// module never has state — skip the WeakMap lookup entirely for it
+		const factoryMeta = module.factoryMeta;
+		if (factoryMeta === undefined || !factoryMeta.sideEffectFree) return;
+		const modules = this._modules;
+		const state = modules.get(module);
+
+		if (state === undefined) {
+			const forwardedIds = getForwardedIds(dependencies);
+			if (forwardedIds !== true && forwardedIds.size === 0) return;
+			modules.set(module, {
+				forwardedIds,
+				lazyBarrelInfo: undefined
+			});
+			return;
+		}
+
+		const forwardedIds = getForwardedIds(dependencies);
+		if (forwardedIds !== true && forwardedIds.size === 0) return;
+		if (state.forwardedIds !== true) {
+			if (forwardedIds === true) state.forwardedIds = true;
+			else for (const id of forwardedIds) state.forwardedIds.add(id);
+		}
+		const info = state.lazyBarrelInfo;
+		// Pending requests will be replayed during `classify` later
+		if (info === undefined) return;
+		const groups = info.request(forwardedIds);
+		// drop the per-module state once every deferred target has been built
+		if (info.isEmpty()) state.lazyBarrelInfo = undefined;
+		if (groups.length === 0) return;
+		return groups.map((group) => ({
+			factory: group.factory,
+			dependencies: group.dependencies,
+			context: group.context,
+			originModule: module
+		}));
+	}
+
+	/**
+	 * Queues the deferred dependency groups of a lazy barrel requested by a single dependency.
+	 * @param {Module} module the resolved module of the dependency
+	 * @param {Dependency} dependency the requesting dependency
+	 * @param {{ factory: ModuleFactory, dependencies: Dependency[], context: string | undefined, originModule: Module | null }[]} sortedDependencies item list to append to
+	 */
+	unlazyForDependency(module, dependency, sortedDependencies) {
+		const unlazyItems = this.request(module, [dependency]);
+		if (unlazyItems === undefined) return;
+		for (const item of unlazyItems) sortedDependencies.push(item);
+	}
+}
+
+module.exports = LazyBarrelController;
diff --git a/lib/LibManifestPlugin.js b/lib/LibManifestPlugin.js
deleted file mode 100644
index 54dbf10a0fa..00000000000
--- a/lib/LibManifestPlugin.js
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const path = require("path");
-const asyncLib = require("neo-async");
-const SingleEntryDependency = require("./dependencies/SingleEntryDependency");
-
-class LibManifestPlugin {
-	constructor(options) {
-		this.options = options;
-	}
-
-	apply(compiler) {
-		compiler.hooks.emit.tapAsync(
-			"LibManifestPlugin",
-			(compilation, callback) => {
-				asyncLib.forEach(
-					compilation.chunks,
-					(chunk, callback) => {
-						if (!chunk.isOnlyInitial()) {
-							callback();
-							return;
-						}
-						const targetPath = compilation.getPath(this.options.path, {
-							hash: compilation.hash,
-							chunk
-						});
-						const name =
-							this.options.name &&
-							compilation.getPath(this.options.name, {
-								hash: compilation.hash,
-								chunk
-							});
-						const manifest = {
-							name,
-							type: this.options.type,
-							content: Array.from(chunk.modulesIterable, module => {
-								if (
-									this.options.entryOnly &&
-									!module.reasons.some(
-										r => r.dependency instanceof SingleEntryDependency
-									)
-								) {
-									return;
-								}
-								if (module.libIdent) {
-									const ident = module.libIdent({
-										context: this.options.context || compiler.options.context
-									});
-									if (ident) {
-										return {
-											ident,
-											data: {
-												id: module.id,
-												buildMeta: module.buildMeta
-											}
-										};
-									}
-								}
-							})
-								.filter(Boolean)
-								.reduce((obj, item) => {
-									obj[item.ident] = item.data;
-									return obj;
-								}, Object.create(null))
-						};
-						const content = Buffer.from(JSON.stringify(manifest), "utf8");
-						compiler.outputFileSystem.mkdirp(path.dirname(targetPath), err => {
-							if (err) return callback(err);
-							compiler.outputFileSystem.writeFile(
-								targetPath,
-								content,
-								callback
-							);
-						});
-					},
-					callback
-				);
-			}
-		);
-	}
-}
-module.exports = LibManifestPlugin;
diff --git a/lib/LibraryTemplatePlugin.js b/lib/LibraryTemplatePlugin.js
index 484442efcbc..e45782e6aa1 100644
--- a/lib/LibraryTemplatePlugin.js
+++ b/lib/LibraryTemplatePlugin.js
@@ -2,151 +2,47 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
-const SetVarMainTemplatePlugin = require("./SetVarMainTemplatePlugin");
+const EnableLibraryPlugin = require("./library/EnableLibraryPlugin");
 
+/** @typedef {import("../declarations/WebpackOptions").AuxiliaryComment} AuxiliaryComment */
+/** @typedef {import("../declarations/WebpackOptions").LibraryExport} LibraryExport */
+/** @typedef {import("../declarations/WebpackOptions").LibraryName} LibraryName */
+/** @typedef {import("../declarations/WebpackOptions").LibraryType} LibraryType */
+/** @typedef {import("../declarations/WebpackOptions").UmdNamedDefine} UmdNamedDefine */
 /** @typedef {import("./Compiler")} Compiler */
 
-/**
- * @param {string[]} accessor the accessor to convert to path
- * @returns {string} the path
- */
-const accessorToObjectAccess = accessor => {
-	return accessor.map(a => `[${JSON.stringify(a)}]`).join("");
-};
-
-/**
- * @param {string=} base the path prefix
- * @param {string|string[]} accessor the accessor
- * @param {string=} joinWith the element separator
- * @returns {string} the path
- */
-const accessorAccess = (base, accessor, joinWith = "; ") => {
-	const accessors = Array.isArray(accessor) ? accessor : [accessor];
-	return accessors
-		.map((_, idx) => {
-			const a = base
-				? base + accessorToObjectAccess(accessors.slice(0, idx + 1))
-				: accessors[0] + accessorToObjectAccess(accessors.slice(1, idx + 1));
-			if (idx === accessors.length - 1) return a;
-			if (idx === 0 && typeof base === "undefined") {
-				return `${a} = typeof ${a} === "object" ? ${a} : {}`;
-			}
-			return `${a} = ${a} || {}`;
-		})
-		.join(joinWith);
-};
-
+// TODO webpack 6 remove
 class LibraryTemplatePlugin {
 	/**
-	 * @param {string} name name of library
-	 * @param {string} target type of library
-	 * @param {boolean} umdNamedDefine setting this to true will name the UMD module
-	 * @param {string|TODO} auxiliaryComment comment in the UMD wrapper
-	 * @param {string|string[]} exportProperty which export should be exposed as library
+	 * Creates an instance of LibraryTemplatePlugin.
+	 * @param {LibraryName} name name of library
+	 * @param {LibraryType} target type of library
+	 * @param {UmdNamedDefine} umdNamedDefine setting this to true will name the UMD module
+	 * @param {AuxiliaryComment} auxiliaryComment comment in the UMD wrapper
+	 * @param {LibraryExport} exportProperty which export should be exposed as library
 	 */
 	constructor(name, target, umdNamedDefine, auxiliaryComment, exportProperty) {
-		this.name = name;
-		this.target = target;
-		this.umdNamedDefine = umdNamedDefine;
-		this.auxiliaryComment = auxiliaryComment;
-		this.exportProperty = exportProperty;
+		this.library = {
+			type: target || "var",
+			name,
+			umdNamedDefine,
+			auxiliaryComment,
+			export: exportProperty
+		};
 	}
 
 	/**
+	 * Applies the plugin by registering its hooks on the compiler.
 	 * @param {Compiler} compiler the compiler instance
 	 * @returns {void}
 	 */
 	apply(compiler) {
-		compiler.hooks.thisCompilation.tap("LibraryTemplatePlugin", compilation => {
-			if (this.exportProperty) {
-				const ExportPropertyMainTemplatePlugin = require("./ExportPropertyMainTemplatePlugin");
-				new ExportPropertyMainTemplatePlugin(this.exportProperty).apply(
-					compilation
-				);
-			}
-			switch (this.target) {
-				case "var":
-					new SetVarMainTemplatePlugin(
-						`var ${accessorAccess(undefined, this.name)}`,
-						false
-					).apply(compilation);
-					break;
-				case "assign":
-					new SetVarMainTemplatePlugin(
-						accessorAccess(undefined, this.name),
-						false
-					).apply(compilation);
-					break;
-				case "this":
-				case "self":
-				case "window":
-					if (this.name) {
-						new SetVarMainTemplatePlugin(
-							accessorAccess(this.target, this.name),
-							false
-						).apply(compilation);
-					} else {
-						new SetVarMainTemplatePlugin(this.target, true).apply(compilation);
-					}
-					break;
-				case "global":
-					if (this.name) {
-						new SetVarMainTemplatePlugin(
-							accessorAccess(
-								compilation.runtimeTemplate.outputOptions.globalObject,
-								this.name
-							),
-							false
-						).apply(compilation);
-					} else {
-						new SetVarMainTemplatePlugin(
-							compilation.runtimeTemplate.outputOptions.globalObject,
-							true
-						).apply(compilation);
-					}
-					break;
-				case "commonjs":
-					if (this.name) {
-						new SetVarMainTemplatePlugin(
-							accessorAccess("exports", this.name),
-							false
-						).apply(compilation);
-					} else {
-						new SetVarMainTemplatePlugin("exports", true).apply(compilation);
-					}
-					break;
-				case "commonjs2":
-				case "commonjs-module":
-					new SetVarMainTemplatePlugin("module.exports", false).apply(
-						compilation
-					);
-					break;
-				case "amd": {
-					const AmdMainTemplatePlugin = require("./AmdMainTemplatePlugin");
-					new AmdMainTemplatePlugin(this.name).apply(compilation);
-					break;
-				}
-				case "umd":
-				case "umd2": {
-					const UmdMainTemplatePlugin = require("./UmdMainTemplatePlugin");
-					new UmdMainTemplatePlugin(this.name, {
-						optionalAmdExternalAsGlobal: this.target === "umd2",
-						namedDefine: this.umdNamedDefine,
-						auxiliaryComment: this.auxiliaryComment
-					}).apply(compilation);
-					break;
-				}
-				case "jsonp": {
-					const JsonpExportMainTemplatePlugin = require("./web/JsonpExportMainTemplatePlugin");
-					new JsonpExportMainTemplatePlugin(this.name).apply(compilation);
-					break;
-				}
-				default:
-					throw new Error(`${this.target} is not a valid Library target`);
-			}
-		});
+		const { output } = compiler.options;
+		output.library = this.library;
+		new EnableLibraryPlugin(this.library.type).apply(compiler);
 	}
 }
 
diff --git a/lib/LoaderOptionsPlugin.js b/lib/LoaderOptionsPlugin.js
index 5d13e2c0643..b3db6e822bb 100644
--- a/lib/LoaderOptionsPlugin.js
+++ b/lib/LoaderOptionsPlugin.js
@@ -2,46 +2,78 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
 const ModuleFilenameHelpers = require("./ModuleFilenameHelpers");
+const NormalModule = require("./NormalModule");
 
-const validateOptions = require("schema-utils");
-const schema = require("../schemas/plugins/LoaderOptionsPlugin.json");
+/** @typedef {import("../declarations/plugins/LoaderOptionsPlugin").LoaderOptionsPluginOptions} LoaderOptionsPluginOptions */
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./ModuleFilenameHelpers").MatchObject} MatchObject  */
 
-class LoaderOptionsPlugin {
-	constructor(options) {
-		validateOptions(schema, options || {}, "Loader Options Plugin");
+/**
+ * Defines the loader context type used by this module.
+ * @template T
+ * @typedef {import("../declarations/LoaderContext").LoaderContext} LoaderContext
+ */
 
+const PLUGIN_NAME = "LoaderOptionsPlugin";
+
+class LoaderOptionsPlugin {
+	/**
+	 * Creates an instance of LoaderOptionsPlugin.
+	 * @param {LoaderOptionsPluginOptions & MatchObject} options options object
+	 */
+	constructor(options = {}) {
+		// If no options are set then generate empty options object
 		if (typeof options !== "object") options = {};
 		if (!options.test) {
-			options.test = {
-				test: () => true
-			};
+			options.test = () => true;
 		}
+		/** @type {LoaderOptionsPluginOptions & MatchObject} */
 		this.options = options;
 	}
 
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
 	apply(compiler) {
-		const options = this.options;
-		compiler.hooks.compilation.tap("LoaderOptionsPlugin", compilation => {
-			compilation.hooks.normalModuleLoader.tap(
-				"LoaderOptionsPlugin",
+		compiler.hooks.validate.tap(PLUGIN_NAME, () => {
+			compiler.validate(
+				() => require("../schemas/plugins/LoaderOptionsPlugin.json"),
+				this.options,
+				{
+					name: "Loader Options Plugin",
+					baseDataPath: "options"
+				},
+				(options) =>
+					require("../schemas/plugins/LoaderOptionsPlugin.check")(options)
+			);
+		});
+
+		compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
+			NormalModule.getCompilationHooks(compilation).loader.tap(
+				PLUGIN_NAME,
 				(context, module) => {
 					const resource = module.resource;
 					if (!resource) return;
 					const i = resource.indexOf("?");
 					if (
 						ModuleFilenameHelpers.matchObject(
-							options,
-							i < 0 ? resource : resource.substr(0, i)
+							this.options,
+							i < 0 ? resource : resource.slice(0, i)
 						)
 					) {
-						for (const key of Object.keys(options)) {
+						for (const key of Object.keys(this.options)) {
 							if (key === "include" || key === "exclude" || key === "test") {
 								continue;
 							}
-							context[key] = options[key];
+
+							/** @type {LoaderContext & Record} */
+							(context)[key] = this.options[key];
 						}
 					}
 				}
diff --git a/lib/LoaderTargetPlugin.js b/lib/LoaderTargetPlugin.js
index 99ffbc9979d..8ed0903183e 100644
--- a/lib/LoaderTargetPlugin.js
+++ b/lib/LoaderTargetPlugin.js
@@ -2,18 +2,35 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
+const NormalModule = require("./NormalModule");
+
+/** @typedef {import("./Compiler")} Compiler */
+
+const PLUGIN_NAME = "LoaderTargetPlugin";
+
 class LoaderTargetPlugin {
+	/**
+	 * Creates an instance of LoaderTargetPlugin.
+	 * @param {string} target the target
+	 */
 	constructor(target) {
+		/** @type {string} */
 		this.target = target;
 	}
 
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
 	apply(compiler) {
-		compiler.hooks.compilation.tap("LoaderTargetPlugin", compilation => {
-			compilation.hooks.normalModuleLoader.tap(
-				"LoaderTargetPlugin",
-				loaderContext => {
+		compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
+			NormalModule.getCompilationHooks(compilation).loader.tap(
+				PLUGIN_NAME,
+				(loaderContext) => {
 					loaderContext.target = this.target;
 				}
 			);
diff --git a/lib/MainTemplate.js b/lib/MainTemplate.js
index 3463546fea6..9c038f3fff9 100644
--- a/lib/MainTemplate.js
+++ b/lib/MainTemplate.js
@@ -2,86 +2,275 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
-const {
-	ConcatSource,
-	OriginalSource,
-	PrefixSource,
-	RawSource
-} = require("webpack-sources");
-const {
-	Tapable,
-	SyncWaterfallHook,
-	SyncHook,
-	SyncBailHook
-} = require("tapable");
-const Template = require("./Template");
+const util = require("util");
+const { SyncWaterfallHook } = require("tapable");
+const RuntimeGlobals = require("./RuntimeGlobals");
+const JavascriptModulesPlugin = require("./javascript/JavascriptModulesPlugin");
+const memoize = require("./util/memoize");
+
+const getLoadScriptRuntimeModule = memoize(() =>
+	require("./runtime/LoadScriptRuntimeModule")
+);
+
+const getJsonpTemplatePlugin = memoize(() =>
+	require("./web/JsonpTemplatePlugin")
+);
 
-/** @typedef {import("webpack-sources").ConcatSource} ConcatSource */
+/** @typedef {import("tapable").Tap} Tap */
+/** @typedef {import("webpack-sources").Source} Source */
+/** @typedef {import("../declarations/WebpackOptions").Output} OutputOptions */
 /** @typedef {import("./ModuleTemplate")} ModuleTemplate */
 /** @typedef {import("./Chunk")} Chunk */
-/** @typedef {import("./Module")} Module} */
-/** @typedef {import("crypto").Hash} Hash} */
-
+/** @typedef {import("./Compilation")} Compilation */
+/** @typedef {import("./Compilation").AssetInfo} AssetInfo */
+/** @typedef {import("./Compilation").InterpolatedPathAndAssetInfo} InterpolatedPathAndAssetInfo */
+/** @typedef {import("./util/Hash")} Hash */
+/** @typedef {import("./DependencyTemplates")} DependencyTemplates */
+/** @typedef {import("./javascript/JavascriptModulesPlugin").RenderBootstrapContext} RenderBootstrapContext */
+/** @typedef {import("./Template").RenderManifestOptions} RenderManifestOptions */
+/** @typedef {import("./Template").RenderManifestEntry} RenderManifestEntry */
+/** @typedef {import("./TemplatedPathPlugin").TemplatePath} TemplatePath */
+/** @typedef {import("./TemplatedPathPlugin").PathData} PathData */
 /**
- * @typedef {Object} RenderManifestOptions
- * @property {Chunk} chunk the chunk used to render
- * @property {Hash} hash
- * @property {string} fullHash
- * @property {TODO} outputOptions
- * @property {{javascript: ModuleTemplate, webassembly: ModuleTemplate}} moduleTemplates
- * @property {Map} dependencyTemplates
+ * Defines the if set type used by this module.
+ * @template T
+ * @typedef {import("tapable").IfSet} IfSet
  */
 
-// require function shortcuts:
-// __webpack_require__.s = the module id of the entry point
-// __webpack_require__.c = the module cache
-// __webpack_require__.m = the module functions
-// __webpack_require__.p = the bundle public path
-// __webpack_require__.i = the identity function used for harmony imports
-// __webpack_require__.e = the chunk ensure function
-// __webpack_require__.d = the exported property define getter function
-// __webpack_require__.o = Object.prototype.hasOwnProperty.call
-// __webpack_require__.r = define compatibility on export
-// __webpack_require__.t = create a fake namespace object
-// __webpack_require__.n = compatibility get default export
-// __webpack_require__.h = the webpack hash
-// __webpack_require__.w = an object containing all installed WebAssembly.Instance export objects keyed by module id
-// __webpack_require__.oe = the uncaught error handler for the webpack runtime
-// __webpack_require__.nc = the script nonce
-
-module.exports = class MainTemplate extends Tapable {
+// TODO webpack 6 remove this class
+class MainTemplate {
 	/**
-	 *
-	 * @param {TODO=} outputOptions output options for the MainTemplate
+	 * Creates an instance of MainTemplate.
+	 * @param {OutputOptions} outputOptions output options for the MainTemplate
+	 * @param {Compilation} compilation the compilation
 	 */
-	constructor(outputOptions) {
-		super();
-		/** @type {TODO?} */
-		this.outputOptions = outputOptions || {};
-		this.hooks = {
-			/** @type {SyncWaterfallHook} */
-			renderManifest: new SyncWaterfallHook(["result", "options"]),
-			modules: new SyncWaterfallHook([
-				"modules",
-				"chunk",
-				"hash",
-				"moduleTemplate",
-				"dependencyTemplates"
-			]),
-			moduleObj: new SyncWaterfallHook([
-				"source",
-				"chunk",
-				"hash",
-				"moduleIdExpression"
-			]),
-			requireEnsure: new SyncWaterfallHook([
-				"source",
-				"chunk",
-				"hash",
-				"chunkIdExpression"
-			]),
+	constructor(outputOptions, compilation) {
+		/** @type {OutputOptions} */
+		this._outputOptions = outputOptions || {};
+		this.hooks = Object.freeze({
+			renderManifest: {
+				tap: util.deprecate(
+					/**
+					 * Handles the callback logic for this hook.
+					 * @template AdditionalOptions
+					 * @param {string | Tap & IfSet} options options
+					 * @param {(renderManifestEntries: RenderManifestEntry[], renderManifestOptions: RenderManifestOptions) => RenderManifestEntry[]} fn fn
+					 */
+					(options, fn) => {
+						compilation.hooks.renderManifest.tap(
+							options,
+							(entries, options) => {
+								if (!options.chunk.hasRuntime()) return entries;
+								return fn(entries, options);
+							}
+						);
+					},
+					"MainTemplate.hooks.renderManifest is deprecated (use Compilation.hooks.renderManifest instead)",
+					"DEP_WEBPACK_MAIN_TEMPLATE_RENDER_MANIFEST"
+				)
+			},
+			modules: {
+				tap: () => {
+					throw new Error(
+						"MainTemplate.hooks.modules has been removed (there is no replacement, please create an issue to request that)"
+					);
+				}
+			},
+			moduleObj: {
+				tap: () => {
+					throw new Error(
+						"MainTemplate.hooks.moduleObj has been removed (there is no replacement, please create an issue to request that)"
+					);
+				}
+			},
+			require: {
+				tap: util.deprecate(
+					/**
+					 * Handles the callback logic for this hook.
+					 * @template AdditionalOptions
+					 * @param {string | Tap & IfSet} options options
+					 * @param {(value: string, renderBootstrapContext: RenderBootstrapContext) => string} fn fn
+					 */
+					(options, fn) => {
+						JavascriptModulesPlugin.getCompilationHooks(
+							compilation
+						).renderRequire.tap(options, fn);
+					},
+					"MainTemplate.hooks.require is deprecated (use JavascriptModulesPlugin.getCompilationHooks().renderRequire instead)",
+					"DEP_WEBPACK_MAIN_TEMPLATE_REQUIRE"
+				)
+			},
+			beforeStartup: {
+				tap: () => {
+					throw new Error(
+						"MainTemplate.hooks.beforeStartup has been removed (use RuntimeGlobals.startupOnlyBefore instead)"
+					);
+				}
+			},
+			startup: {
+				tap: () => {
+					throw new Error(
+						"MainTemplate.hooks.startup has been removed (use RuntimeGlobals.startup instead)"
+					);
+				}
+			},
+			afterStartup: {
+				tap: () => {
+					throw new Error(
+						"MainTemplate.hooks.afterStartup has been removed (use RuntimeGlobals.startupOnlyAfter instead)"
+					);
+				}
+			},
+			render: {
+				tap: util.deprecate(
+					/**
+					 * Handles the callback logic for this hook.
+					 * @template AdditionalOptions
+					 * @param {string | Tap & IfSet} options options
+					 * @param {(source: Source, chunk: Chunk, hash: string | undefined, moduleTemplate: ModuleTemplate, dependencyTemplates: DependencyTemplates) => Source} fn fn
+					 */
+					(options, fn) => {
+						JavascriptModulesPlugin.getCompilationHooks(compilation).render.tap(
+							options,
+							(source, renderContext) => {
+								if (
+									renderContext.chunkGraph.getNumberOfEntryModules(
+										renderContext.chunk
+									) === 0 ||
+									!renderContext.chunk.hasRuntime()
+								) {
+									return source;
+								}
+								return fn(
+									source,
+									renderContext.chunk,
+									compilation.hash,
+									compilation.moduleTemplates.javascript,
+									compilation.dependencyTemplates
+								);
+							}
+						);
+					},
+					"MainTemplate.hooks.render is deprecated (use JavascriptModulesPlugin.getCompilationHooks().render instead)",
+					"DEP_WEBPACK_MAIN_TEMPLATE_RENDER"
+				)
+			},
+			renderWithEntry: {
+				tap: util.deprecate(
+					/**
+					 * Handles the callback logic for this hook.
+					 * @template AdditionalOptions
+					 * @param {string | Tap & IfSet} options options
+					 * @param {(source: Source, chunk: Chunk, hash: string | undefined) => Source} fn fn
+					 */
+					(options, fn) => {
+						JavascriptModulesPlugin.getCompilationHooks(compilation).render.tap(
+							options,
+							(source, renderContext) => {
+								if (
+									renderContext.chunkGraph.getNumberOfEntryModules(
+										renderContext.chunk
+									) === 0 ||
+									!renderContext.chunk.hasRuntime()
+								) {
+									return source;
+								}
+								return fn(source, renderContext.chunk, compilation.hash);
+							}
+						);
+					},
+					"MainTemplate.hooks.renderWithEntry is deprecated (use JavascriptModulesPlugin.getCompilationHooks().render instead)",
+					"DEP_WEBPACK_MAIN_TEMPLATE_RENDER_WITH_ENTRY"
+				)
+			},
+			assetPath: {
+				tap: util.deprecate(
+					/**
+					 * Handles the callback logic for this hook.
+					 * @template AdditionalOptions
+					 * @param {string | Tap & IfSet} options options
+					 * @param {(value: string, path: PathData, assetInfo: AssetInfo | undefined) => string} fn fn
+					 */
+					(options, fn) => {
+						compilation.hooks.assetPath.tap(options, fn);
+					},
+					"MainTemplate.hooks.assetPath is deprecated (use Compilation.hooks.assetPath instead)",
+					"DEP_WEBPACK_MAIN_TEMPLATE_ASSET_PATH"
+				),
+				call: util.deprecate(
+					/**
+					 * Handles the call callback for this hook.
+					 * @param {TemplatePath} filename used to get asset path with hash
+					 * @param {PathData} options context data
+					 * @returns {string} interpolated path
+					 */
+					(filename, options) => compilation.getAssetPath(filename, options),
+					"MainTemplate.hooks.assetPath is deprecated (use Compilation.hooks.assetPath instead)",
+					"DEP_WEBPACK_MAIN_TEMPLATE_ASSET_PATH"
+				)
+			},
+			hash: {
+				tap: util.deprecate(
+					/**
+					 * Handles the callback logic for this hook.
+					 * @template AdditionalOptions
+					 * @param {string | Tap & IfSet} options options
+					 * @param {(hash: Hash) => void} fn fn
+					 */
+					(options, fn) => {
+						compilation.hooks.fullHash.tap(options, fn);
+					},
+					"MainTemplate.hooks.hash is deprecated (use Compilation.hooks.fullHash instead)",
+					"DEP_WEBPACK_MAIN_TEMPLATE_HASH"
+				)
+			},
+			hashForChunk: {
+				tap: util.deprecate(
+					/**
+					 * Handles the callback logic for this hook.
+					 * @template AdditionalOptions
+					 * @param {string | Tap & IfSet} options options
+					 * @param {(hash: Hash, chunk: Chunk) => void} fn fn
+					 */
+					(options, fn) => {
+						JavascriptModulesPlugin.getCompilationHooks(
+							compilation
+						).chunkHash.tap(options, (chunk, hash) => {
+							if (!chunk.hasRuntime()) return;
+							return fn(hash, chunk);
+						});
+					},
+					"MainTemplate.hooks.hashForChunk is deprecated (use JavascriptModulesPlugin.getCompilationHooks().chunkHash instead)",
+					"DEP_WEBPACK_MAIN_TEMPLATE_HASH_FOR_CHUNK"
+				)
+			},
+			globalHashPaths: {
+				tap: util.deprecate(
+					() => {},
+					"MainTemplate.hooks.globalHashPaths has been removed (it's no longer needed)",
+					"DEP_WEBPACK_MAIN_TEMPLATE_HASH_FOR_CHUNK"
+				)
+			},
+			globalHash: {
+				tap: util.deprecate(
+					() => {},
+					"MainTemplate.hooks.globalHash has been removed (it's no longer needed)",
+					"DEP_WEBPACK_MAIN_TEMPLATE_HASH_FOR_CHUNK"
+				)
+			},
+			hotBootstrap: {
+				tap: () => {
+					throw new Error(
+						"MainTemplate.hooks.hotBootstrap has been removed (use your own RuntimeModule instead)"
+					);
+				}
+			},
+
+			// for compatibility:
+			/** @type {SyncWaterfallHook<[string, Chunk, string, ModuleTemplate, DependencyTemplates]>} */
 			bootstrap: new SyncWaterfallHook([
 				"source",
 				"chunk",
@@ -89,405 +278,110 @@ module.exports = class MainTemplate extends Tapable {
 				"moduleTemplate",
 				"dependencyTemplates"
 			]),
+			/** @type {SyncWaterfallHook<[string, Chunk, string]>} */
 			localVars: new SyncWaterfallHook(["source", "chunk", "hash"]),
-			require: new SyncWaterfallHook(["source", "chunk", "hash"]),
+			/** @type {SyncWaterfallHook<[string, Chunk, string]>} */
 			requireExtensions: new SyncWaterfallHook(["source", "chunk", "hash"]),
-			beforeStartup: new SyncWaterfallHook(["source", "chunk", "hash"]),
-			startup: new SyncWaterfallHook(["source", "chunk", "hash"]),
-			render: new SyncWaterfallHook([
-				"source",
-				"chunk",
-				"hash",
-				"moduleTemplate",
-				"dependencyTemplates"
-			]),
-			renderWithEntry: new SyncWaterfallHook(["source", "chunk", "hash"]),
-			moduleRequire: new SyncWaterfallHook([
-				"source",
-				"chunk",
-				"hash",
-				"moduleIdExpression"
-			]),
-			addModule: new SyncWaterfallHook([
+			/** @type {SyncWaterfallHook<[string, Chunk, string, string]>} */
+			requireEnsure: new SyncWaterfallHook([
 				"source",
 				"chunk",
 				"hash",
-				"moduleIdExpression",
-				"moduleExpression"
+				"chunkIdExpression"
 			]),
-			currentHash: new SyncWaterfallHook(["source", "requestedLength"]),
-			assetPath: new SyncWaterfallHook(["path", "options"]),
-			hash: new SyncHook(["hash"]),
-			hashForChunk: new SyncHook(["hash", "chunk"]),
-			globalHashPaths: new SyncWaterfallHook(["paths"]),
-			globalHash: new SyncBailHook(["chunk", "paths"]),
-
-			// TODO this should be moved somewhere else
-			// It's weird here
-			hotBootstrap: new SyncWaterfallHook(["source", "chunk", "hash"])
-		};
-		this.hooks.startup.tap("MainTemplate", (source, chunk, hash) => {
-			/** @type {string[]} */
-			const buf = [];
-			if (chunk.entryModule) {
-				buf.push("// Load entry module and return exports");
-				buf.push(
-					`return ${this.renderRequireFunctionForModule(
-						hash,
-						chunk,
-						JSON.stringify(chunk.entryModule.id)
-					)}(${this.requireFn}.s = ${JSON.stringify(chunk.entryModule.id)});`
-				);
-			}
-			return Template.asString(buf);
-		});
-		this.hooks.render.tap(
-			"MainTemplate",
-			(bootstrapSource, chunk, hash, moduleTemplate, dependencyTemplates) => {
-				const source = new ConcatSource();
-				source.add("/******/ (function(modules) { // webpackBootstrap\n");
-				source.add(new PrefixSource("/******/", bootstrapSource));
-				source.add("/******/ })\n");
-				source.add(
-					"/************************************************************************/\n"
-				);
-				source.add("/******/ (");
-				source.add(
-					this.hooks.modules.call(
-						new RawSource(""),
-						chunk,
-						hash,
-						moduleTemplate,
-						dependencyTemplates
-					)
-				);
-				source.add(")");
-				return source;
-			}
-		);
-		this.hooks.localVars.tap("MainTemplate", (source, chunk, hash) => {
-			return Template.asString([
-				source,
-				"// The module cache",
-				"var installedModules = {};"
-			]);
-		});
-		this.hooks.require.tap("MainTemplate", (source, chunk, hash) => {
-			return Template.asString([
-				source,
-				"// Check if module is in cache",
-				"if(installedModules[moduleId]) {",
-				Template.indent("return installedModules[moduleId].exports;"),
-				"}",
-				"// Create a new module (and put it into the cache)",
-				"var module = installedModules[moduleId] = {",
-				Template.indent(this.hooks.moduleObj.call("", chunk, hash, "moduleId")),
-				"};",
-				"",
-				Template.asString(
-					outputOptions.strictModuleExceptionHandling
-						? [
-								"// Execute the module function",
-								"var threw = true;",
-								"try {",
-								Template.indent([
-									`modules[moduleId].call(module.exports, module, module.exports, ${this.renderRequireFunctionForModule(
-										hash,
-										chunk,
-										"moduleId"
-									)});`,
-									"threw = false;"
-								]),
-								"} finally {",
-								Template.indent([
-									"if(threw) delete installedModules[moduleId];"
-								]),
-								"}"
-						  ]
-						: [
-								"// Execute the module function",
-								`modules[moduleId].call(module.exports, module, module.exports, ${this.renderRequireFunctionForModule(
-									hash,
-									chunk,
-									"moduleId"
-								)});`
-						  ]
-				),
-				"",
-				"// Flag the module as loaded",
-				"module.l = true;",
-				"",
-				"// Return the exports of the module",
-				"return module.exports;"
-			]);
-		});
-		this.hooks.moduleObj.tap(
-			"MainTemplate",
-			(source, chunk, hash, varModuleId) => {
-				return Template.asString(["i: moduleId,", "l: false,", "exports: {}"]);
-			}
-		);
-		this.hooks.requireExtensions.tap("MainTemplate", (source, chunk, hash) => {
-			const buf = [];
-			const chunkMaps = chunk.getChunkMaps();
-			// Check if there are non initial chunks which need to be imported using require-ensure
-			if (Object.keys(chunkMaps.hash).length) {
-				buf.push("// This file contains only the entry chunk.");
-				buf.push("// The chunk loading function for additional chunks");
-				buf.push(`${this.requireFn}.e = function requireEnsure(chunkId) {`);
-				buf.push(Template.indent("var promises = [];"));
-				buf.push(
-					Template.indent(
-						this.hooks.requireEnsure.call("", chunk, hash, "chunkId")
-					)
-				);
-				buf.push(Template.indent("return Promise.all(promises);"));
-				buf.push("};");
+			get jsonpScript() {
+				const hooks =
+					getLoadScriptRuntimeModule().getCompilationHooks(compilation);
+				return hooks.createScript;
+			},
+			get linkPrefetch() {
+				const hooks = getJsonpTemplatePlugin().getCompilationHooks(compilation);
+				return hooks.linkPrefetch;
+			},
+			get linkPreload() {
+				const hooks = getJsonpTemplatePlugin().getCompilationHooks(compilation);
+				return hooks.linkPreload;
 			}
-			buf.push("");
-			buf.push("// expose the modules object (__webpack_modules__)");
-			buf.push(`${this.requireFn}.m = modules;`);
-
-			buf.push("");
-			buf.push("// expose the module cache");
-			buf.push(`${this.requireFn}.c = installedModules;`);
-
-			buf.push("");
-			buf.push("// define getter function for harmony exports");
-			buf.push(`${this.requireFn}.d = function(exports, name, getter) {`);
-			buf.push(
-				Template.indent([
-					`if(!${this.requireFn}.o(exports, name)) {`,
-					Template.indent([
-						"Object.defineProperty(exports, name, { enumerable: true, get: getter });"
-					]),
-					"}"
-				])
-			);
-			buf.push("};");
-
-			buf.push("");
-			buf.push("// define __esModule on exports");
-			buf.push(`${this.requireFn}.r = function(exports) {`);
-			buf.push(
-				Template.indent([
-					"if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {",
-					Template.indent([
-						"Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });"
-					]),
-					"}",
-					"Object.defineProperty(exports, '__esModule', { value: true });"
-				])
-			);
-			buf.push("};");
-
-			buf.push("");
-			buf.push("// create a fake namespace object");
-			buf.push("// mode & 1: value is a module id, require it");
-			buf.push("// mode & 2: merge all properties of value into the ns");
-			buf.push("// mode & 4: return value when already ns object");
-			buf.push("// mode & 8|1: behave like require");
-			buf.push(`${this.requireFn}.t = function(value, mode) {`);
-			buf.push(
-				Template.indent([
-					`if(mode & 1) value = ${this.requireFn}(value);`,
-					`if(mode & 8) return value;`,
-					"if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;",
-					"var ns = Object.create(null);",
-					`${this.requireFn}.r(ns);`,
-					"Object.defineProperty(ns, 'default', { enumerable: true, value: value });",
-					"if(mode & 2 && typeof value != 'string') for(var key in value) " +
-						`${this.requireFn}.d(ns, key, function(key) { ` +
-						"return value[key]; " +
-						"}.bind(null, key));",
-					"return ns;"
-				])
-			);
-			buf.push("};");
-
-			buf.push("");
-			buf.push(
-				"// getDefaultExport function for compatibility with non-harmony modules"
-			);
-			buf.push(this.requireFn + ".n = function(module) {");
-			buf.push(
-				Template.indent([
-					"var getter = module && module.__esModule ?",
-					Template.indent([
-						"function getDefault() { return module['default']; } :",
-						"function getModuleExports() { return module; };"
-					]),
-					`${this.requireFn}.d(getter, 'a', getter);`,
-					"return getter;"
-				])
-			);
-			buf.push("};");
-
-			buf.push("");
-			buf.push("// Object.prototype.hasOwnProperty.call");
-			buf.push(
-				`${
-					this.requireFn
-				}.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };`
-			);
-
-			const publicPath = this.getPublicPath({
-				hash: hash
-			});
-			buf.push("");
-			buf.push("// __webpack_public_path__");
-			buf.push(`${this.requireFn}.p = ${JSON.stringify(publicPath)};`);
-			return Template.asString(buf);
 		});
 
-		this.requireFn = "__webpack_require__";
-	}
-
-	/**
-	 *
-	 * @param {RenderManifestOptions} options render manifest options
-	 * @returns {TODO[]} returns render manifest
-	 */
-	getRenderManifest(options) {
-		const result = [];
-
-		this.hooks.renderManifest.call(result, options);
-
-		return result;
-	}
-
-	/**
-	 *
-	 * @param {string} hash hash to be used for render call
-	 * @param {Chunk} chunk Chunk instance
-	 * @param {ModuleTemplate} moduleTemplate ModuleTemplate instance for render
-	 * @param {TODO} dependencyTemplates DependencyTemplate[]s
-	 * @returns {ConcatSource} the newly generated source from rendering
-	 */
-	render(hash, chunk, moduleTemplate, dependencyTemplates) {
-		const buf = [];
-		buf.push(
-			this.hooks.bootstrap.call(
-				"",
-				chunk,
-				hash,
-				moduleTemplate,
-				dependencyTemplates
-			)
-		);
-		buf.push(this.hooks.localVars.call("", chunk, hash));
-		buf.push("");
-		buf.push("// The require function");
-		buf.push(`function ${this.requireFn}(moduleId) {`);
-		buf.push(Template.indent(this.hooks.require.call("", chunk, hash)));
-		buf.push("}");
-		buf.push("");
-		buf.push(
-			Template.asString(this.hooks.requireExtensions.call("", chunk, hash))
+		this.renderCurrentHashCode = util.deprecate(
+			/**
+			 * Handles the require ensure callback for this hook.
+			 * @deprecated
+			 * @param {string} hash the hash
+			 * @param {number=} length length of the hash
+			 * @returns {string} generated code
+			 */
+			(hash, length) => {
+				if (length) {
+					return `${RuntimeGlobals.getFullHash} ? ${
+						RuntimeGlobals.getFullHash
+					}().slice(0, ${length}) : ${hash.slice(0, length)}`;
+				}
+				return `${RuntimeGlobals.getFullHash} ? ${RuntimeGlobals.getFullHash}() : ${hash}`;
+			},
+			"MainTemplate.renderCurrentHashCode is deprecated (use RuntimeGlobals.getFullHash runtime function instead)",
+			"DEP_WEBPACK_MAIN_TEMPLATE_RENDER_CURRENT_HASH_CODE"
 		);
-		buf.push("");
-		buf.push(Template.asString(this.hooks.beforeStartup.call("", chunk, hash)));
-		buf.push(Template.asString(this.hooks.startup.call("", chunk, hash)));
-		let source = this.hooks.render.call(
-			new OriginalSource(
-				Template.prefix(buf, " \t") + "\n",
-				"webpack/bootstrap"
-			),
-			chunk,
-			hash,
-			moduleTemplate,
-			dependencyTemplates
-		);
-		if (chunk.hasEntryModule()) {
-			source = this.hooks.renderWithEntry.call(source, chunk, hash);
-		}
-		if (!source) {
-			throw new Error(
-				"Compiler error: MainTemplate plugin 'render' should return something"
-			);
-		}
-		chunk.rendered = true;
-		return new ConcatSource(source, ";");
-	}
-
-	/**
-	 *
-	 * @param {string} hash hash for render fn
-	 * @param {Chunk} chunk Chunk instance for require
-	 * @param {(number|string)=} varModuleId module id
-	 * @returns {TODO} the moduleRequire hook call return signature
-	 */
-	renderRequireFunctionForModule(hash, chunk, varModuleId) {
-		return this.hooks.moduleRequire.call(
-			this.requireFn,
-			chunk,
-			hash,
-			varModuleId
-		);
-	}
 
-	/**
-	 *
-	 * @param {string} hash hash for render add fn
-	 * @param {Chunk} chunk Chunk instance for require add fn
-	 * @param {(string|number)=} varModuleId module id
-	 * @param {Module} varModule Module instance
-	 * @returns {TODO} renderAddModule call
-	 */
-	renderAddModule(hash, chunk, varModuleId, varModule) {
-		return this.hooks.addModule.call(
-			`modules[${varModuleId}] = ${varModule};`,
-			chunk,
-			hash,
-			varModuleId,
-			varModule
+		this.getPublicPath = util.deprecate(
+			/**
+			 * Handles the callback logic for this hook.
+			 * @param {PathData} options context data
+			 * @returns {string} interpolated path
+			 */ (options) =>
+				compilation.getAssetPath(compilation.outputOptions.publicPath, options),
+			"MainTemplate.getPublicPath is deprecated (use Compilation.getAssetPath(compilation.outputOptions.publicPath, options) instead)",
+			"DEP_WEBPACK_MAIN_TEMPLATE_GET_PUBLIC_PATH"
 		);
-	}
 
-	/**
-	 *
-	 * @param {string} hash string hash
-	 * @param {number} length length
-	 * @returns {string} call hook return
-	 */
-	renderCurrentHashCode(hash, length) {
-		length = length || Infinity;
-		return this.hooks.currentHash.call(
-			JSON.stringify(hash.substr(0, length)),
-			length
+		this.getAssetPath = util.deprecate(
+			/**
+			 * Handles the callback logic for this hook.
+			 * @param {TemplatePath} path used to get asset path with hash
+			 * @param {PathData} options context data
+			 * @returns {string} interpolated path
+			 */
+			(path, options) => compilation.getAssetPath(path, options),
+			"MainTemplate.getAssetPath is deprecated (use Compilation.getAssetPath instead)",
+			"DEP_WEBPACK_MAIN_TEMPLATE_GET_ASSET_PATH"
 		);
-	}
 
-	/**
-	 *
-	 * @param {object} options get public path options
-	 * @returns {string} hook call
-	 */
-	getPublicPath(options) {
-		return this.hooks.assetPath.call(
-			this.outputOptions.publicPath || "",
-			options
+		this.getAssetPathWithInfo = util.deprecate(
+			/**
+			 * Handles the callback logic for this hook.
+			 * @param {TemplatePath} path used to get asset path with hash
+			 * @param {PathData} options context data
+			 * @returns {InterpolatedPathAndAssetInfo} interpolated path and asset info
+			 */
+			(path, options) => compilation.getAssetPathWithInfo(path, options),
+			"MainTemplate.getAssetPathWithInfo is deprecated (use Compilation.getAssetPath instead)",
+			"DEP_WEBPACK_MAIN_TEMPLATE_GET_ASSET_PATH_WITH_INFO"
 		);
 	}
+}
 
-	getAssetPath(path, options) {
-		return this.hooks.assetPath.call(path, options);
-	}
+Object.defineProperty(MainTemplate.prototype, "requireFn", {
+	get: util.deprecate(
+		() => RuntimeGlobals.require,
+		`MainTemplate.requireFn is deprecated (use "${RuntimeGlobals.require}")`,
+		"DEP_WEBPACK_MAIN_TEMPLATE_REQUIRE_FN"
+	)
+});
 
-	updateHash(hash) {
-		hash.update("maintemplate");
-		hash.update("3");
-		hash.update(this.outputOptions.publicPath + "");
-		this.hooks.hash.call(hash);
-	}
+Object.defineProperty(MainTemplate.prototype, "outputOptions", {
+	get: util.deprecate(
+		/**
+		 * Returns output options.
+		 * @this {MainTemplate}
+		 * @returns {OutputOptions} output options
+		 */
+		function outputOptions() {
+			return this._outputOptions;
+		},
+		"MainTemplate.outputOptions is deprecated (use Compilation.outputOptions instead)",
+		"DEP_WEBPACK_MAIN_TEMPLATE_OUTPUT_OPTIONS"
+	)
+});
 
-	updateHashForChunk(hash, chunk) {
-		this.updateHash(hash);
-		this.hooks.hashForChunk.call(hash, chunk);
-	}
-
-	useChunkHash(chunk) {
-		const paths = this.hooks.globalHashPaths.call([]);
-		return !this.hooks.globalHash.call(chunk, paths);
-	}
-};
+module.exports = MainTemplate;
diff --git a/lib/ManifestPlugin.js b/lib/ManifestPlugin.js
new file mode 100644
index 00000000000..9076292aa3e
--- /dev/null
+++ b/lib/ManifestPlugin.js
@@ -0,0 +1,251 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Haijie Xie @hai-x
+*/
+
+"use strict";
+
+const { RawSource } = require("webpack-sources");
+const Compilation = require("./Compilation");
+const HotUpdateChunk = require("./HotUpdateChunk");
+
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./Chunk")} Chunk */
+/** @typedef {import("./Chunk").ChunkName} ChunkName */
+/** @typedef {import("./Chunk").ChunkId} ChunkId */
+/** @typedef {import("./Compilation").Asset} Asset */
+/** @typedef {import("./Compilation").AssetInfo} AssetInfo */
+
+/** @typedef {import("../declarations/plugins/ManifestPlugin").ManifestPluginOptions} ManifestPluginOptions */
+/** @typedef {import("../declarations/plugins/ManifestPlugin").ManifestObject} ManifestObject */
+/** @typedef {import("../declarations/plugins/ManifestPlugin").ManifestEntrypoint} ManifestEntrypoint */
+/** @typedef {import("../declarations/plugins/ManifestPlugin").ManifestItem} ManifestItem */
+
+/** @typedef {(item: ManifestItem) => boolean} Filter */
+/** @typedef {(manifest: ManifestObject) => ManifestObject} Generate */
+/** @typedef {(manifest: ManifestObject) => string} Serialize */
+
+const PLUGIN_NAME = "ManifestPlugin";
+
+/**
+ * Returns extname.
+ * @param {string} filename filename
+ * @returns {string} extname
+ */
+const extname = (filename) => {
+	const replaced = filename.replace(/\?.*/, "");
+	const split = replaced.split(".");
+	const last = split.pop();
+	if (!last) return "";
+	return last && /^(?:gz|br|map)$/i.test(last)
+		? `${split.pop()}.${last}`
+		: last;
+};
+
+const DEFAULT_PREFIX = "[publicpath]";
+const DEFAULT_FILENAME = "manifest.json";
+
+class ManifestPlugin {
+	/**
+	 * Creates an instance of ManifestPlugin.
+	 * @param {ManifestPluginOptions} options options
+	 */
+	constructor(options = {}) {
+		/** @type {ManifestPluginOptions} */
+		this.options = options;
+	}
+
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
+	apply(compiler) {
+		compiler.hooks.validate.tap(PLUGIN_NAME, () => {
+			compiler.validate(
+				() => require("../schemas/plugins/ManifestPlugin.json"),
+				this.options,
+				{
+					name: "ManifestPlugin",
+					baseDataPath: "options"
+				},
+				(options) => require("../schemas/plugins/ManifestPlugin.check")(options)
+			);
+		});
+
+		const entrypoints =
+			this.options.entrypoints !== undefined ? this.options.entrypoints : true;
+		const serialize =
+			this.options.serialize ||
+			((manifest) => JSON.stringify(manifest, null, 2));
+
+		compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
+			compilation.hooks.processAssets.tap(
+				{
+					name: PLUGIN_NAME,
+					stage: Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE
+				},
+				() => {
+					const hashDigestLength = compilation.outputOptions.hashDigestLength;
+					const publicPath = compilation.getPath(
+						compilation.outputOptions.publicPath
+					);
+
+					/**
+					 * Creates a hash reg exp.
+					 * @param {string | string[]} value value
+					 * @returns {RegExp} regexp to remove hash
+					 */
+					const createHashRegExp = (value) =>
+						new RegExp(
+							`(?:\\.${Array.isArray(value) ? `(${value.join("|")})` : value})(?=\\.)`,
+							"gi"
+						);
+
+					/**
+					 * Removes the provided name from the manifest plugin.
+					 * @param {string} name name
+					 * @param {AssetInfo | null} info asset info
+					 * @returns {string} hash removed name
+					 */
+					const removeHash = (name, info) => {
+						// Handles hashes that match configured `hashDigestLength`
+						// i.e. index.XXXX.html -> index.html (html-webpack-plugin)
+						if (hashDigestLength <= 0) return name;
+						const reg = createHashRegExp(`[a-f0-9]{${hashDigestLength},32}`);
+						return name.replace(reg, "");
+					};
+
+					/**
+					 * Returns chunk name or chunk id.
+					 * @param {Chunk} chunk chunk
+					 * @returns {ChunkName | ChunkId} chunk name or chunk id
+					 */
+					const getName = (chunk) => {
+						if (chunk.name) return chunk.name;
+
+						return chunk.id;
+					};
+
+					/** @type {ManifestObject} */
+					let manifest = {};
+
+					if (entrypoints) {
+						/** @type {ManifestObject["entrypoints"]} */
+						const entrypoints = {};
+
+						for (const [name, entrypoint] of compilation.entrypoints) {
+							/** @type {string[]} */
+							const imports = [];
+
+							for (const chunk of entrypoint.chunks) {
+								for (const file of chunk.files) {
+									const name = getName(chunk);
+
+									imports.push(name ? `${name}.${extname(file)}` : file);
+								}
+							}
+
+							/** @type {ManifestEntrypoint} */
+							const item = { imports };
+							const parents = entrypoint
+								.getParents()
+								.map((item) => /** @type {string} */ (item.name));
+
+							if (parents.length > 0) {
+								item.parents = parents;
+							}
+
+							entrypoints[name] = item;
+						}
+
+						manifest.entrypoints = entrypoints;
+					}
+
+					/** @type {ManifestObject["assets"]} */
+					const assets = {};
+
+					/** @type {Set} */
+					const added = new Set();
+
+					/**
+					 * Processes the provided file.
+					 * @param {string} file file
+					 * @param {string=} usedName usedName
+					 * @returns {void}
+					 */
+					const handleFile = (file, usedName) => {
+						if (added.has(file)) return;
+						added.add(file);
+
+						const asset = compilation.getAsset(file);
+						if (!asset) return;
+						const sourceFilename = asset.info.sourceFilename;
+						const name =
+							usedName ||
+							sourceFilename ||
+							// Fallback for unofficial plugins, just remove hash from filename
+							removeHash(file, asset.info);
+
+						const prefix = (this.options.prefix || DEFAULT_PREFIX).replace(
+							/\[publicpath\]/gi,
+							() => (publicPath === "auto" ? "/" : publicPath)
+						);
+						/** @type {ManifestItem} */
+						const item = { file: prefix + file };
+
+						if (sourceFilename) {
+							item.src = sourceFilename;
+						}
+
+						if (this.options.filter) {
+							const needKeep = this.options.filter(item);
+
+							if (!needKeep) {
+								return;
+							}
+						}
+
+						assets[name] = item;
+					};
+
+					for (const chunk of compilation.chunks) {
+						if (chunk instanceof HotUpdateChunk) continue;
+
+						for (const auxiliaryFile of chunk.auxiliaryFiles) {
+							handleFile(auxiliaryFile);
+						}
+
+						const name = getName(chunk);
+
+						for (const file of chunk.files) {
+							handleFile(file, name ? `${name}.${extname(file)}` : file);
+						}
+					}
+
+					for (const asset of compilation.getAssets()) {
+						if (asset.info.hotModuleReplacement) {
+							continue;
+						}
+
+						handleFile(asset.name);
+					}
+
+					manifest.assets = assets;
+
+					if (this.options.generate) {
+						manifest = this.options.generate(manifest);
+					}
+
+					compilation.emitAsset(
+						this.options.filename || DEFAULT_FILENAME,
+						new RawSource(serialize(manifest)),
+						{ manifest: true }
+					);
+				}
+			);
+		});
+	}
+}
+
+module.exports = ManifestPlugin;
diff --git a/lib/MemoryOutputFileSystem.js b/lib/MemoryOutputFileSystem.js
deleted file mode 100644
index 8476148882a..00000000000
--- a/lib/MemoryOutputFileSystem.js
+++ /dev/null
@@ -1,5 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-module.exports = require("memory-fs");
diff --git a/lib/Module.js b/lib/Module.js
index 4341414aca4..781e61f684d 100644
--- a/lib/Module.js
+++ b/lib/Module.js
@@ -2,384 +2,1460 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
 const util = require("util");
-
+const ChunkGraph = require("./ChunkGraph");
 const DependenciesBlock = require("./DependenciesBlock");
-const ModuleReason = require("./ModuleReason");
-const SortableSet = require("./util/SortableSet");
-const Template = require("./Template");
+const ModuleGraph = require("./ModuleGraph");
+const {
+	JAVASCRIPT_TYPE,
+	UNKNOWN_TYPE
+} = require("./ModuleSourceTypeConstants");
+const { JAVASCRIPT_TYPES } = require("./ModuleSourceTypeConstants");
+const RuntimeGlobals = require("./RuntimeGlobals");
+const { first } = require("./util/SetHelpers");
+const { compareChunksById } = require("./util/comparators");
+const makeSerializable = require("./util/makeSerializable");
 
+/** @typedef {import("webpack-sources").Source} Source */
+/** @typedef {import("../declarations/WebpackOptions").ResolveOptions} ResolveOptions */
+/** @typedef {import("./config/defaults").WebpackOptionsNormalizedWithDefaults} WebpackOptions */
 /** @typedef {import("./Chunk")} Chunk */
+/** @typedef {import("./ChunkGraph").ModuleId} ModuleId */
+/** @typedef {import("./ChunkGroup")} ChunkGroup */
+/** @typedef {import("./CodeGenerationResults")} CodeGenerationResults */
+/** @typedef {import("./Compilation")} Compilation */
+/** @typedef {import("./Compilation").AssetInfo} AssetInfo */
+/** @typedef {import("./Compilation").FileSystemDependencies} FileSystemDependencies */
+/** @typedef {import("./Compilation").UnsafeCacheData} UnsafeCacheData */
+/** @typedef {import("./ConcatenationScope")} ConcatenationScope */
+/** @typedef {import("./Dependency")} Dependency */
+/** @typedef {import("./Dependency").UpdateHashContext} UpdateHashContext */
+/** @typedef {import("./DependencyTemplates")} DependencyTemplates */
+/** @typedef {import("./ModuleSourceTypeConstants").AllTypes} AllTypes */
+/** @typedef {import("./FileSystemInfo")} FileSystemInfo */
+/** @typedef {import("./ModuleGraphConnection").ConnectionState} ConnectionState */
+/** @typedef {import("./ModuleTypeConstants").ModuleTypes} ModuleTypes */
+/** @typedef {import("./ModuleGraph").OptimizationBailouts} OptimizationBailouts */
+/** @typedef {import("./ModuleProfile")} ModuleProfile */
+/** @typedef {import("./NormalModuleFactory")} NormalModuleFactory */
 /** @typedef {import("./RequestShortener")} RequestShortener */
+/** @typedef {import("./ResolverFactory").ResolverWithOptions} ResolverWithOptions */
+/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */
+/**
+ * Defines the init fragment type used by this module.
+ * @template T
+ * @typedef {import("./InitFragment")} InitFragment
+ */
+/** @typedef {import("./errors/WebpackError")} WebpackError */
+/** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */
+/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */
+/** @typedef {import("./util/Hash")} Hash */
+/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
+/** @typedef {import("./util/identifier").AssociatedObjectForCache} AssociatedObjectForCache */
+/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */
+/**
+ * @template T
+ * @typedef {import("./util/SortableSet")} SortableSet
+ */
+/** @typedef {"namespace" | "default-only" | "default-with-named" | "dynamic"} ExportsType */
+
+/**
+ * Defines the shared type used by this module.
+ * @template T
+ * @typedef {import("./util/LazySet")} LazySet
+ */
+
+/**
+ * Defines the source context type used by this module.
+ * @typedef {object} SourceContext
+ * @property {DependencyTemplates} dependencyTemplates the dependency templates
+ * @property {RuntimeTemplate} runtimeTemplate the runtime template
+ * @property {ModuleGraph} moduleGraph the module graph
+ * @property {ChunkGraph} chunkGraph the chunk graph
+ * @property {RuntimeSpec} runtime the runtimes code should be generated for
+ * @property {string=} type the type of source that should be generated
+ */
+
+/** @typedef {AllTypes} KnownSourceType */
+/** @typedef {KnownSourceType | string} SourceType */
+/** @typedef {ReadonlySet} SourceTypes */
+
+/** @typedef {ReadonlySet} BasicSourceTypes */
+
+// TODO webpack 6: compilation will be required in CodeGenerationContext
+/**
+ * Defines the code generation context type used by this module.
+ * @typedef {object} CodeGenerationContext
+ * @property {DependencyTemplates} dependencyTemplates the dependency templates
+ * @property {RuntimeTemplate} runtimeTemplate the runtime template
+ * @property {ModuleGraph} moduleGraph the module graph
+ * @property {ChunkGraph} chunkGraph the chunk graph
+ * @property {RuntimeSpec} runtime the runtimes code should be generated for
+ * @property {RuntimeSpec[]} runtimes all runtimes code should be generated for
+ * @property {ConcatenationScope=} concatenationScope when in concatenated module, information about other concatenated modules
+ * @property {CodeGenerationResults | undefined} codeGenerationResults code generation results of other modules (need to have a codeGenerationDependency to use that)
+ * @property {Compilation=} compilation the compilation
+ * @property {SourceTypes=} sourceTypes source types
+ */
+
+/**
+ * Defines the concatenation bailout reason context type used by this module.
+ * @typedef {object} ConcatenationBailoutReasonContext
+ * @property {ModuleGraph} moduleGraph the module graph
+ * @property {ChunkGraph} chunkGraph the chunk graph
+ * @property {boolean=} concatenateCommonJsModules whether eligible CommonJS modules may be concatenated
+ */
+
+/** @typedef {Set} RuntimeRequirements */
+/** @typedef {ReadonlySet} ReadOnlyRuntimeRequirements */
+
+/**
+ * Defines the all code generation schemas type used by this module.
+ * @typedef {object} AllCodeGenerationSchemas
+ * @property {Set} topLevelDeclarations top level declarations for javascript modules
+ * @property {Set} freeNames free identifier names in the rendered source for javascript modules
+ * @property {InitFragment[]} chunkInitFragments chunk init fragments for javascript modules
+ * @property {{ javascript?: string, ["asset-url"]?: string }} url url for asset modules
+ * @property {string} filename a filename for asset modules
+ * @property {AssetInfo} assetInfo an asset info for asset modules
+ * @property {string} fullContentHash a full content hash for asset modules
+ * @property {[{ shareScope: string, initStage: number, init: string }]} share-init share-init for modules federation
+ */
+
+/**
+ * Defines the code gen value type used by this module.
+ * @template {string} K
+ * @typedef {K extends (keyof AllCodeGenerationSchemas) ? AllCodeGenerationSchemas[K] : EXPECTED_ANY} CodeGenValue
+ */
+
+/**
+ * Defines the code gen map overloads type used by this module.
+ * @typedef {object} CodeGenMapOverloads
+ * @property {(key: K) => CodeGenValue | undefined} get
+ * @property {(key: K, value: CodeGenValue) => CodeGenerationResultData} set
+ * @property {(key: K) => boolean} has
+ * @property {(key: K) => boolean} delete
+ */
+
+/**
+ * Defines the code generation result data type used by this module.
+ * @typedef {Omit, "get" | "set" | "has" | "delete"> & CodeGenMapOverloads} CodeGenerationResultData
+ */
+
+/** @typedef {Map} Sources */
+
+/**
+ * Defines the code generation result type used by this module.
+ * @typedef {object} CodeGenerationResult
+ * @property {Sources} sources the resulting sources for all source types
+ * @property {CodeGenerationResultData=} data the resulting data for all source types
+ * @property {ReadOnlyRuntimeRequirements | null} runtimeRequirements the runtime requirements
+ * @property {string=} hash a hash of the code generation result (will be automatically calculated from sources and runtimeRequirements if not provided)
+ */
+
+/**
+ * Defines the lib ident options type used by this module.
+ * @typedef {object} LibIdentOptions
+ * @property {string} context absolute context path to which lib ident is relative to
+ * @property {AssociatedObjectForCache=} associatedObjectForCache object for caching
+ */
+
+/**
+ * Defines the build meta properties common to all module types.
+ * Module type specific properties live in the `Known*BuildMeta` typedef of the dedicated module class.
+ * @typedef {object} KnownBuildMeta
+ * @property {("default" | "namespace" | "flagged" | "dynamic")=} exportsType
+ * @property {(false | "redirect" | "redirect-warn")=} defaultObject
+ * @property {boolean=} async
+ * @property {boolean=} sideEffectFree
+ * @property {Map>=} exportsFinalNameByRuntime using in ModuleLibraryPlugin
+ * @property {Map=} exportsSourceByRuntime using in ModuleLibraryPlugin
+ * @property {Map=} exportsBindingSourceByRuntime export definitions `ModuleLibraryPlugin` took over, re-emitted only where the module is wrapped
+ */
+
+/**
+ * Defines the build info properties common to all module types.
+ * Module type specific properties live in the `Known*BuildInfo` typedef of the dedicated module class.
+ * @typedef {object} KnownBuildInfo
+ * @property {boolean=} cacheable
+ * @property {string[]=} notCacheableReasons reasons why the module is not cacheable (e.g. paths of loaders that marked it)
+ * @property {boolean=} strict
+ * @property {string=} moduleArgument
+ * @property {string=} exportsArgument
+ * @property {Record=} assets assets added by loaders or plugins
+ * @property {Map=} assetsInfo
+ * @property {Set=} topLevelDeclarations top level declaration names
+ * @property {boolean=} isCircular true when the module is part of a circular dependency chain
+ * @property {boolean=} usesTopLevelAwaitForOf module uses top-level `for await…of` or `await using`, which can't be lowered to a generator
+ */
+
+/** @typedef {string | Set} ValueCacheVersion */
+/** @typedef {Map} ValueCacheVersions */
+
+/**
+ * Defines the need build context type used by this module.
+ * @typedef {object} NeedBuildContext
+ * @property {Compilation} compilation
+ * @property {FileSystemInfo} fileSystemInfo
+ * @property {ValueCacheVersions} valueCacheVersions
+ */
+
+/** @typedef {(err?: WebpackError | null, needBuild?: boolean) => void} NeedBuildCallback */
+
+/** @typedef {(err?: WebpackError) => void} BuildCallback */
+
+/** @typedef {KnownBuildMeta & Record} BuildMeta */
+/** @typedef {KnownBuildInfo & Record} BuildInfo */
+
+/**
+ * Defines the factory meta type used by this module.
+ * @typedef {object} FactoryMeta
+ * @property {boolean=} sideEffectFree
+ */
 
 const EMPTY_RESOLVE_OPTIONS = {};
 
 let debugId = 1000;
 
-const sortById = (a, b) => {
-	return a.id - b.id;
-};
+/** @type {SourceTypes} */
+const DEFAULT_TYPES_UNKNOWN = new Set([UNKNOWN_TYPE]);
+
+const deprecatedNeedRebuild = util.deprecate(
+	/**
+	 * Handles the callback logic for this hook.
+	 * @param {Module} module the module
+	 * @param {NeedBuildContext} context context info
+	 * @returns {boolean} true, when rebuild is needed
+	 */
+	(module, context) =>
+		module.needRebuild(
+			context.fileSystemInfo.getDeprecatedFileTimestamps(),
+			context.fileSystemInfo.getDeprecatedContextTimestamps()
+		),
+	"Module.needRebuild is deprecated in favor of Module.needBuild",
+	"DEP_WEBPACK_MODULE_NEED_REBUILD"
+);
 
-const sortByDebugId = (a, b) => {
-	return a.debugId - b.debugId;
-};
+/** @typedef {string} LibIdent */
+/** @typedef {string} NameForCondition */
 
 /** @typedef {(requestShortener: RequestShortener) => string} OptimizationBailoutFunction */
 
 class Module extends DependenciesBlock {
-	constructor(type, context = null) {
+	/**
+	 * Creates an instance of Module.
+	 * @param {ModuleTypes | ""} type the module type, when deserializing the type is not known and is an empty string
+	 * @param {(string | null)=} context an optional context
+	 * @param {(string | null)=} layer an optional layer in which the module is
+	 */
+	constructor(type, context = null, layer = null) {
 		super();
-		/** @type {string} */
+
+		/** @type {ModuleTypes} */
 		this.type = type;
-		/** @type {string} */
+		/** @type {string | null} */
 		this.context = context;
+		/** @type {string | null} */
+		this.layer = layer;
+		/** @type {boolean} */
+		this.needId = true;
 
 		// Unique Id
 		/** @type {number} */
 		this.debugId = debugId++;
 
-		// Hash
-		/** @type {string} */
-		this.hash = undefined;
-		/** @type {string} */
-		this.renderedHash = undefined;
-
 		// Info from Factory
-		/** @type {object} */
+		/** @type {ResolveOptions | undefined} */
 		this.resolveOptions = EMPTY_RESOLVE_OPTIONS;
-		/** @type {object} */
-		this.factoryMeta = {};
+		/** @type {FactoryMeta | undefined} */
+		this.factoryMeta = undefined;
+		// TODO refactor this -> options object filled from Factory
+		// TODO webpack 6: use an enum
+		/** @type {boolean} */
+		this.useSourceMap = false;
+		/** @type {boolean} */
+		this.useSimpleSourceMap = false;
 
 		// Info from Build
-		/** @type {Error[]} */
-		this.warnings = [];
-		/** @type {Error[]} */
-		this.errors = [];
-		/** @type {object} */
+		/** @type {Error[] | undefined} */
+		this._warnings = undefined;
+		/** @type {Error[] | undefined} */
+		this._errors = undefined;
+		// Subclasses with type specific build info/meta redeclare these with a narrowed type
+		/** @type {BuildMeta | undefined} */
 		this.buildMeta = undefined;
-		/** @type {object} */
+		/** @type {BuildInfo | undefined} */
 		this.buildInfo = undefined;
+		/** @type {Dependency[] | undefined} */
+		this.presentationalDependencies = undefined;
+		/** @type {Dependency[] | undefined} */
+		this.codeGenerationDependencies = undefined;
+	}
 
-		// Graph (per Compilation)
-		/** @type {ModuleReason[]} */
-		this.reasons = [];
-		/** @type {SortableSet} */
-		this._chunks = new SortableSet(undefined, sortById);
+	// TODO remove in webpack 6
+	// BACKWARD-COMPAT START
+	/**
+	 * Returns the module id assigned by the chunk graph.
+	 * @deprecated
+	 * @returns {ModuleId | null} module id
+	 */
+	get id() {
+		return ChunkGraph.getChunkGraphForModule(
+			this,
+			"Module.id",
+			"DEP_WEBPACK_MODULE_ID"
+		).getModuleId(this);
+	}
 
-		// Info from Compilation (per Compilation)
-		/** @type {number|string} */
-		this.id = null;
-		/** @type {number} */
-		this.index = null;
-		/** @type {number} */
-		this.index2 = null;
-		/** @type {number} */
-		this.depth = null;
-		/** @type {Module} */
-		this.issuer = null;
-		/** @type {undefined | object} */
-		this.profile = undefined;
-		/** @type {boolean} */
-		this.prefetched = false;
-		/** @type {boolean} */
-		this.built = false;
+	/**
+	 * Updates the module id using the provided value.
+	 * @deprecated
+	 * @param {ModuleId} value value
+	 */
+	set id(value) {
+		if (value === "") {
+			this.needId = false;
+			return;
+		}
+		ChunkGraph.getChunkGraphForModule(
+			this,
+			"Module.id",
+			"DEP_WEBPACK_MODULE_ID"
+		).setModuleId(this, value);
+	}
 
-		// Info from Optimization (per Compilation)
-		/** @type {null | boolean} */
-		this.used = null;
-		/** @type {false | true | string[]} */
-		this.usedExports = null;
-		/** @type {(string | OptimizationBailoutFunction)[]} */
-		this.optimizationBailout = [];
+	/**
+	 * Returns the hash of the module.
+	 * @deprecated
+	 * @returns {string} the hash of the module
+	 */
+	get hash() {
+		return ChunkGraph.getChunkGraphForModule(
+			this,
+			"Module.hash",
+			"DEP_WEBPACK_MODULE_HASH"
+		).getModuleHash(this, undefined);
+	}
 
-		// delayed operations
-		/** @type {undefined | {oldChunk: Chunk, newChunks: Chunk[]}[] } */
-		this._rewriteChunkInReasons = undefined;
+	/**
+	 * Returns the rendered hash of the module.
+	 * @deprecated
+	 * @returns {string} the shortened hash of the module
+	 */
+	get renderedHash() {
+		return ChunkGraph.getChunkGraphForModule(
+			this,
+			"Module.renderedHash",
+			"DEP_WEBPACK_MODULE_RENDERED_HASH"
+		).getRenderedModuleHash(this, undefined);
+	}
 
-		/** @type {boolean} */
-		this.useSourceMap = false;
+	/**
+	 * @deprecated
+	 * @returns {ModuleProfile | undefined} module profile
+	 */
+	get profile() {
+		return ModuleGraph.getModuleGraphForModule(
+			this,
+			"Module.profile",
+			"DEP_WEBPACK_MODULE_PROFILE"
+		).getProfile(this);
+	}
 
-		// info from build
-		this._source = null;
+	/**
+	 * @deprecated
+	 * @param {ModuleProfile | undefined} value module profile
+	 */
+	set profile(value) {
+		ModuleGraph.getModuleGraphForModule(
+			this,
+			"Module.profile",
+			"DEP_WEBPACK_MODULE_PROFILE"
+		).setProfile(this, value);
 	}
 
-	get exportsArgument() {
-		return (this.buildInfo && this.buildInfo.exportsArgument) || "exports";
+	/**
+	 * Returns the pre-order index.
+	 * @deprecated
+	 * @returns {number | null} the pre order index
+	 */
+	get index() {
+		return ModuleGraph.getModuleGraphForModule(
+			this,
+			"Module.index",
+			"DEP_WEBPACK_MODULE_INDEX"
+		).getPreOrderIndex(this);
 	}
 
-	get moduleArgument() {
-		return (this.buildInfo && this.buildInfo.moduleArgument) || "module";
+	/**
+	 * Updates the pre-order index using the provided value.
+	 * @deprecated
+	 * @param {number} value the pre order index
+	 */
+	set index(value) {
+		ModuleGraph.getModuleGraphForModule(
+			this,
+			"Module.index",
+			"DEP_WEBPACK_MODULE_INDEX"
+		).setPreOrderIndex(this, value);
+	}
+
+	/**
+	 * Returns the post-order index.
+	 * @deprecated
+	 * @returns {number | null} the post order index
+	 */
+	get index2() {
+		return ModuleGraph.getModuleGraphForModule(
+			this,
+			"Module.index2",
+			"DEP_WEBPACK_MODULE_INDEX2"
+		).getPostOrderIndex(this);
 	}
 
-	disconnect() {
-		this.hash = undefined;
-		this.renderedHash = undefined;
+	/**
+	 * Updates the post-order index using the provided value.
+	 * @deprecated
+	 * @param {number} value the post order index
+	 */
+	set index2(value) {
+		ModuleGraph.getModuleGraphForModule(
+			this,
+			"Module.index2",
+			"DEP_WEBPACK_MODULE_INDEX2"
+		).setPostOrderIndex(this, value);
+	}
 
-		this.reasons.length = 0;
-		this._rewriteChunkInReasons = undefined;
-		this._chunks.clear();
+	/**
+	 * Returns the depth.
+	 * @deprecated
+	 * @returns {number | null} the depth
+	 */
+	get depth() {
+		return ModuleGraph.getModuleGraphForModule(
+			this,
+			"Module.depth",
+			"DEP_WEBPACK_MODULE_DEPTH"
+		).getDepth(this);
+	}
 
-		this.id = null;
-		this.index = null;
-		this.index2 = null;
-		this.depth = null;
-		this.issuer = null;
-		this.profile = undefined;
-		this.prefetched = false;
-		this.built = false;
+	/**
+	 * Updates the depth using the provided value.
+	 * @deprecated
+	 * @param {number} value the depth
+	 */
+	set depth(value) {
+		ModuleGraph.getModuleGraphForModule(
+			this,
+			"Module.depth",
+			"DEP_WEBPACK_MODULE_DEPTH"
+		).setDepth(this, value);
+	}
 
-		this.used = null;
-		this.usedExports = null;
-		this.optimizationBailout.length = 0;
-		super.disconnect();
+	/**
+	 * Returns the issuer.
+	 * @deprecated
+	 * @returns {Module | null | undefined} issuer
+	 */
+	get issuer() {
+		return ModuleGraph.getModuleGraphForModule(
+			this,
+			"Module.issuer",
+			"DEP_WEBPACK_MODULE_ISSUER"
+		).getIssuer(this);
 	}
 
-	unseal() {
-		this.id = null;
-		this.index = null;
-		this.index2 = null;
-		this.depth = null;
-		this._chunks.clear();
-		super.unseal();
+	/**
+	 * Updates the issuer using the provided value.
+	 * @deprecated
+	 * @param {Module | null} value issuer
+	 */
+	set issuer(value) {
+		ModuleGraph.getModuleGraphForModule(
+			this,
+			"Module.issuer",
+			"DEP_WEBPACK_MODULE_ISSUER"
+		).setIssuer(this, value);
 	}
 
-	setChunks(chunks) {
-		this._chunks = new SortableSet(chunks, sortById);
+	/**
+	 * @deprecated
+	 * @returns {boolean | SortableSet | null} used exports
+	 */
+	get usedExports() {
+		return ModuleGraph.getModuleGraphForModule(
+			this,
+			"Module.usedExports",
+			"DEP_WEBPACK_MODULE_USED_EXPORTS"
+		).getUsedExports(this, undefined);
 	}
 
+	/**
+	 * Gets optimization bailout.
+	 * @deprecated
+	 * @returns {OptimizationBailouts} list
+	 */
+	get optimizationBailout() {
+		return ModuleGraph.getModuleGraphForModule(
+			this,
+			"Module.optimizationBailout",
+			"DEP_WEBPACK_MODULE_OPTIMIZATION_BAILOUT"
+		).getOptimizationBailout(this);
+	}
+
+	/**
+	 * @deprecated
+	 * @returns {boolean} true when optional, otherwise false
+	 */
+	get optional() {
+		return this.isOptional(
+			ModuleGraph.getModuleGraphForModule(
+				this,
+				"Module.optional",
+				"DEP_WEBPACK_MODULE_OPTIONAL"
+			)
+		);
+	}
+
+	/**
+	 * Adds the provided chunk to the module.
+	 * @deprecated
+	 * @param {Chunk} chunk the chunk
+	 * @returns {boolean} true, when the module was added
+	 */
 	addChunk(chunk) {
-		if (this._chunks.has(chunk)) return false;
-		this._chunks.add(chunk);
+		const chunkGraph = ChunkGraph.getChunkGraphForModule(
+			this,
+			"Module.addChunk",
+			"DEP_WEBPACK_MODULE_ADD_CHUNK"
+		);
+		if (chunkGraph.isModuleInChunk(this, chunk)) return false;
+		chunkGraph.connectChunkAndModule(chunk, this);
 		return true;
 	}
 
+	/**
+	 * Removes the provided chunk from the module.
+	 * @deprecated
+	 * @param {Chunk} chunk the chunk
+	 * @returns {void}
+	 */
 	removeChunk(chunk) {
-		if (this._chunks.delete(chunk)) {
-			chunk.removeModule(this);
-			return true;
-		}
-		return false;
+		return ChunkGraph.getChunkGraphForModule(
+			this,
+			"Module.removeChunk",
+			"DEP_WEBPACK_MODULE_REMOVE_CHUNK"
+		).disconnectChunkAndModule(chunk, this);
 	}
 
+	/**
+	 * Checks whether this module is in the provided chunk.
+	 * @deprecated
+	 * @param {Chunk} chunk the chunk
+	 * @returns {boolean} true, when the module is in the chunk
+	 */
 	isInChunk(chunk) {
-		return this._chunks.has(chunk);
+		return ChunkGraph.getChunkGraphForModule(
+			this,
+			"Module.isInChunk",
+			"DEP_WEBPACK_MODULE_IS_IN_CHUNK"
+		).isModuleInChunk(this, chunk);
 	}
 
+	/**
+	 * @deprecated
+	 * @returns {boolean} true when is entry module, otherwise false
+	 */
 	isEntryModule() {
-		for (const chunk of this._chunks) {
-			if (chunk.entryModule === this) return true;
-		}
-		return false;
-	}
-
-	get optional() {
-		return (
-			this.reasons.length > 0 &&
-			this.reasons.every(r => r.dependency && r.dependency.optional)
-		);
+		return ChunkGraph.getChunkGraphForModule(
+			this,
+			"Module.isEntryModule",
+			"DEP_WEBPACK_MODULE_IS_ENTRY_MODULE"
+		).isEntryModule(this);
 	}
 
+	/**
+	 * @deprecated
+	 * @returns {Chunk[]} chunks
+	 */
 	getChunks() {
-		return Array.from(this._chunks);
+		return ChunkGraph.getChunkGraphForModule(
+			this,
+			"Module.getChunks",
+			"DEP_WEBPACK_MODULE_GET_CHUNKS"
+		).getModuleChunks(this);
 	}
 
+	/**
+	 * @deprecated
+	 * @returns {number} number of chunks
+	 */
 	getNumberOfChunks() {
-		return this._chunks.size;
+		return ChunkGraph.getChunkGraphForModule(
+			this,
+			"Module.getNumberOfChunks",
+			"DEP_WEBPACK_MODULE_GET_NUMBER_OF_CHUNKS"
+		).getNumberOfModuleChunks(this);
 	}
 
+	/**
+	 * @deprecated
+	 * @returns {Iterable} chunks
+	 */
 	get chunksIterable() {
-		return this._chunks;
-	}
-
-	hasEqualsChunks(otherModule) {
-		if (this._chunks.size !== otherModule._chunks.size) return false;
-		this._chunks.sortWith(sortByDebugId);
-		otherModule._chunks.sortWith(sortByDebugId);
-		const a = this._chunks[Symbol.iterator]();
-		const b = otherModule._chunks[Symbol.iterator]();
-		// eslint-disable-next-line no-constant-condition
-		while (true) {
-			const aItem = a.next();
-			const bItem = b.next();
-			if (aItem.done) return true;
-			if (aItem.value !== bItem.value) return false;
-		}
+		return ChunkGraph.getChunkGraphForModule(
+			this,
+			"Module.chunksIterable",
+			"DEP_WEBPACK_MODULE_CHUNKS_ITERABLE"
+		).getOrderedModuleChunksIterable(this, compareChunksById);
+	}
+
+	/**
+	 * Checks whether this module provides the specified export.
+	 * @deprecated
+	 * @param {string} exportName a name of an export
+	 * @returns {boolean | null} true, if the export is provided why the module.
+	 * null, if it's unknown.
+	 * false, if it's not provided.
+	 */
+	isProvided(exportName) {
+		return ModuleGraph.getModuleGraphForModule(
+			this,
+			"Module.usedExports",
+			"DEP_WEBPACK_MODULE_USED_EXPORTS"
+		).isExportProvided(this, exportName);
+	}
+	// BACKWARD-COMPAT END
+
+	/**
+	 * Gets exports argument.
+	 * @returns {string} name of the exports argument
+	 */
+	get exportsArgument() {
+		return (this.buildInfo && this.buildInfo.exportsArgument) || "exports";
 	}
 
-	addReason(module, dependency, explanation) {
-		this.reasons.push(new ModuleReason(module, dependency, explanation));
+	/**
+	 * Gets module argument.
+	 * @returns {string} name of the module argument
+	 */
+	get moduleArgument() {
+		return (this.buildInfo && this.buildInfo.moduleArgument) || "module";
 	}
 
-	removeReason(module, dependency) {
-		for (let i = 0; i < this.reasons.length; i++) {
-			let r = this.reasons[i];
-			if (r.module === module && r.dependency === dependency) {
-				this.reasons.splice(i, 1);
-				return true;
+	/**
+	 * Returns export type.
+	 * @param {ModuleGraph} moduleGraph the module graph
+	 * @param {boolean | undefined} strict the importing module is strict
+	 * @returns {ExportsType} export type
+	 * "namespace": Exports is already a namespace object. namespace = exports.
+	 * "dynamic": Check at runtime if __esModule is set. When set: namespace = { ...exports, default: exports }. When not set: namespace = { default: exports }.
+	 * "default-only": Provide a namespace object with only default export. namespace = { default: exports }
+	 * "default-with-named": Provide a namespace object with named and default export. namespace = { ...exports, default: exports }
+	 */
+	getExportsType(moduleGraph, strict) {
+		switch (this.buildMeta && this.buildMeta.exportsType) {
+			case "flagged":
+				return strict ? "default-with-named" : "namespace";
+			case "namespace":
+				return "namespace";
+			case "default":
+				switch (/** @type {BuildMeta} */ (this.buildMeta).defaultObject) {
+					case "redirect":
+						return "default-with-named";
+					case "redirect-warn":
+						return strict ? "default-only" : "default-with-named";
+					default:
+						return "default-only";
+				}
+			case "dynamic": {
+				if (strict) return "default-with-named";
+				// Try to figure out value of __esModule by following reexports
+				const handleDefault = () => {
+					switch (/** @type {BuildMeta} */ (this.buildMeta).defaultObject) {
+						case "redirect":
+						case "redirect-warn":
+							return "default-with-named";
+						default:
+							return "default-only";
+					}
+				};
+				const exportInfo = moduleGraph.getReadOnlyExportInfo(
+					this,
+					"__esModule"
+				);
+				if (exportInfo.provided === false) {
+					return handleDefault();
+				}
+				const target = exportInfo.getTarget(moduleGraph);
+				if (
+					!target ||
+					!target.export ||
+					target.export.length !== 1 ||
+					target.export[0] !== "__esModule"
+				) {
+					return "dynamic";
+				}
+				switch (
+					target.module.buildMeta &&
+					target.module.buildMeta.exportsType
+				) {
+					case "flagged":
+					case "namespace":
+						return "namespace";
+					case "default":
+						return handleDefault();
+					default:
+						return "dynamic";
+				}
 			}
+			default:
+				return strict ? "default-with-named" : "dynamic";
 		}
-		return false;
 	}
 
-	hasReasonForChunk(chunk) {
-		if (this._rewriteChunkInReasons) {
-			for (const operation of this._rewriteChunkInReasons) {
-				this._doRewriteChunkInReasons(operation.oldChunk, operation.newChunks);
-			}
-			this._rewriteChunkInReasons = undefined;
+	/**
+	 * Adds presentational dependency.
+	 * @param {Dependency} presentationalDependency dependency being tied to module.
+	 * This is a Dependency without edge in the module graph. It's only for presentation.
+	 * @returns {void}
+	 */
+	addPresentationalDependency(presentationalDependency) {
+		if (this.presentationalDependencies === undefined) {
+			this.presentationalDependencies = [];
 		}
-		for (let i = 0; i < this.reasons.length; i++) {
-			if (this.reasons[i].hasChunk(chunk)) return true;
+		this.presentationalDependencies.push(presentationalDependency);
+	}
+
+	/**
+	 * Adds code generation dependency.
+	 * @param {Dependency} codeGenerationDependency dependency being tied to module.
+	 * This is a Dependency where the code generation result of the referenced module is needed during code generation.
+	 * The Dependency should also be added to normal dependencies via addDependency.
+	 * @returns {void}
+	 */
+	addCodeGenerationDependency(codeGenerationDependency) {
+		if (this.codeGenerationDependencies === undefined) {
+			this.codeGenerationDependencies = [];
 		}
-		return false;
+		this.codeGenerationDependencies.push(codeGenerationDependency);
 	}
 
-	hasReasons() {
-		return this.reasons.length > 0;
+	/**
+	 * Clear dependencies and blocks.
+	 * @returns {void}
+	 */
+	clearDependenciesAndBlocks() {
+		if (this.presentationalDependencies !== undefined) {
+			this.presentationalDependencies.length = 0;
+		}
+		if (this.codeGenerationDependencies !== undefined) {
+			this.codeGenerationDependencies.length = 0;
+		}
+		super.clearDependenciesAndBlocks();
 	}
 
-	rewriteChunkInReasons(oldChunk, newChunks) {
-		// This is expensive. Delay operation until we really need the data
-		if (this._rewriteChunkInReasons === undefined) {
-			this._rewriteChunkInReasons = [];
+	/**
+	 * Adds the provided warning to the module.
+	 * @param {Error} warning the warning
+	 * @returns {void}
+	 */
+	addWarning(warning) {
+		if (this._warnings === undefined) {
+			this._warnings = [];
 		}
-		this._rewriteChunkInReasons.push({
-			oldChunk,
-			newChunks
-		});
+		this._warnings.push(warning);
+	}
+
+	/**
+	 * Returns list of warnings if any.
+	 * @returns {Error[] | undefined} list of warnings if any
+	 */
+	getWarnings() {
+		return this._warnings;
 	}
 
-	_doRewriteChunkInReasons(oldChunk, newChunks) {
-		for (let i = 0; i < this.reasons.length; i++) {
-			this.reasons[i].rewriteChunks(oldChunk, newChunks);
+	/**
+	 * Gets number of warnings.
+	 * @returns {number} number of warnings
+	 */
+	getNumberOfWarnings() {
+		return this._warnings !== undefined ? this._warnings.length : 0;
+	}
+
+	/**
+	 * Adds the provided error to the module.
+	 * @param {Error} error the error
+	 * @returns {void}
+	 */
+	addError(error) {
+		if (this._errors === undefined) {
+			this._errors = [];
 		}
+		this._errors.push(error);
+	}
+
+	/**
+	 * Returns list of errors if any.
+	 * @returns {Error[] | undefined} list of errors if any
+	 */
+	getErrors() {
+		return this._errors;
 	}
 
-	isUsed(exportName) {
-		if (!exportName) return this.used !== false;
-		if (this.used === null || this.usedExports === null) return exportName;
-		if (!this.used) return false;
-		if (!this.usedExports) return false;
-		if (this.usedExports === true) return exportName;
-		let idx = this.usedExports.indexOf(exportName);
-		if (idx < 0) return false;
+	/**
+	 * Gets number of errors.
+	 * @returns {number} number of errors
+	 */
+	getNumberOfErrors() {
+		return this._errors !== undefined ? this._errors.length : 0;
+	}
 
-		// Mangle export name if possible
-		if (this.isProvided(exportName)) {
-			if (this.buildMeta.exportsType === "namespace") {
-				return Template.numberToIdentifer(idx);
-			}
+	/**
+	 * removes all warnings and errors
+	 * @returns {void}
+	 */
+	clearWarningsAndErrors() {
+		if (this._warnings !== undefined) {
+			this._warnings.length = 0;
+		}
+		if (this._errors !== undefined) {
+			this._errors.length = 0;
+		}
+	}
+
+	/**
+	 * Checks whether this module is optional.
+	 * @param {ModuleGraph} moduleGraph the module graph
+	 * @returns {boolean} true, if the module is optional
+	 */
+	isOptional(moduleGraph) {
+		let hasConnections = false;
+		for (const r of moduleGraph.getIncomingConnections(this)) {
 			if (
-				this.buildMeta.exportsType === "named" &&
-				!this.usedExports.includes("default")
+				!r.dependency ||
+				!r.dependency.optional ||
+				!r.isTargetActive(undefined)
 			) {
-				return Template.numberToIdentifer(idx);
+				return false;
 			}
+			hasConnections = true;
 		}
-		return exportName;
+		return hasConnections;
 	}
 
-	isProvided(exportName) {
-		if (!Array.isArray(this.buildMeta.providedExports)) return null;
-		return this.buildMeta.providedExports.includes(exportName);
+	/**
+	 * Checks whether this module is accessible in chunk.
+	 * @param {ChunkGraph} chunkGraph the chunk graph
+	 * @param {Chunk} chunk a chunk
+	 * @param {Chunk=} ignoreChunk chunk to be ignored
+	 * @returns {boolean} true, if the module is accessible from "chunk" when ignoring "ignoreChunk"
+	 */
+	isAccessibleInChunk(chunkGraph, chunk, ignoreChunk) {
+		// Check if module is accessible in ALL chunk groups
+		for (const chunkGroup of chunk.groupsIterable) {
+			if (!this.isAccessibleInChunkGroup(chunkGraph, chunkGroup)) return false;
+		}
+		return true;
+	}
+
+	/**
+	 * Checks whether this module is accessible in chunk group.
+	 * @param {ChunkGraph} chunkGraph the chunk graph
+	 * @param {ChunkGroup} chunkGroup a chunk group
+	 * @param {Chunk=} ignoreChunk chunk to be ignored
+	 * @returns {boolean} true, if the module is accessible from "chunkGroup" when ignoring "ignoreChunk"
+	 */
+	isAccessibleInChunkGroup(chunkGraph, chunkGroup, ignoreChunk) {
+		const queue = new Set([chunkGroup]);
+
+		// Check if module is accessible from all items of the queue
+		queueFor: for (const cg of queue) {
+			// 1. If module is in one of the chunks of the group we can continue checking the next items
+			//    because it's accessible.
+			for (const chunk of cg.chunks) {
+				if (chunk !== ignoreChunk && chunkGraph.isModuleInChunk(this, chunk)) {
+					continue queueFor;
+				}
+			}
+			// 2. If the chunk group is initial, we can break here because it's not accessible.
+			if (chunkGroup.isInitial()) return false;
+			// 3. Enqueue all parents because it must be accessible from ALL parents
+			for (const parent of chunkGroup.parentsIterable) queue.add(parent);
+		}
+		// When we processed through the whole list and we didn't bailout, the module is accessible
+		return true;
 	}
 
+	/**
+	 * Checks whether this module contains the chunk.
+	 * @param {Chunk} chunk a chunk
+	 * @param {ModuleGraph} moduleGraph the module graph
+	 * @param {ChunkGraph} chunkGraph the chunk graph
+	 * @returns {boolean} true, if the module has any reason why "chunk" should be included
+	 */
+	hasReasonForChunk(chunk, moduleGraph, chunkGraph) {
+		// check for each reason if we need the chunk
+		for (const [
+			fromModule,
+			connections
+		] of moduleGraph.getIncomingConnectionsByOriginModule(this)) {
+			if (!connections.some((c) => c.isTargetActive(chunk.runtime))) continue;
+			for (const originChunk of chunkGraph.getModuleChunksIterable(
+				/** @type {Module} */ (fromModule)
+			)) {
+				// return true if module this is not reachable from originChunk when ignoring chunk
+				if (!this.isAccessibleInChunk(chunkGraph, originChunk, chunk)) {
+					return true;
+				}
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Checks whether this module contains the module graph.
+	 * @param {ModuleGraph} moduleGraph the module graph
+	 * @param {RuntimeSpec} runtime the runtime
+	 * @returns {boolean} true if at least one other module depends on this module
+	 */
+	hasReasons(moduleGraph, runtime) {
+		for (const c of moduleGraph.getIncomingConnections(this)) {
+			if (c.isTargetActive(runtime)) return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Returns a string representation.
+	 * @returns {string} for debugging
+	 */
 	toString() {
-		return `Module[${this.id || this.debugId}]`;
+		return `Module[${this.debugId}: ${this.identifier()}]`;
+	}
+
+	/**
+	 * Checks whether the module needs to be rebuilt for the current build state.
+	 * @param {NeedBuildContext} context context info
+	 * @param {NeedBuildCallback} callback callback function, returns true, if the module needs a rebuild
+	 * @returns {void}
+	 */
+	needBuild(context, callback) {
+		callback(
+			null,
+			!this.buildMeta ||
+				this.needRebuild === Module.prototype.needRebuild ||
+				deprecatedNeedRebuild(this, context)
+		);
 	}
 
+	/**
+	 * Checks whether it needs rebuild.
+	 * @deprecated Use needBuild instead
+	 * @param {Map} fileTimestamps timestamps of files
+	 * @param {Map} contextTimestamps timestamps of directories
+	 * @returns {boolean} true, if the module needs a rebuild
+	 */
 	needRebuild(fileTimestamps, contextTimestamps) {
 		return true;
 	}
 
-	updateHash(hash) {
-		hash.update(`${this.id}`);
-		hash.update(JSON.stringify(this.usedExports));
-		super.updateHash(hash);
+	/**
+	 * Updates the hash with the data contributed by this instance.
+	 * @param {Hash} hash the hash used to track dependencies
+	 * @param {UpdateHashContext} context context
+	 * @returns {void}
+	 */
+	updateHash(
+		hash,
+		context = {
+			chunkGraph: ChunkGraph.getChunkGraphForModule(
+				this,
+				"Module.updateHash",
+				"DEP_WEBPACK_MODULE_UPDATE_HASH"
+			),
+			runtime: undefined
+		}
+	) {
+		const { chunkGraph, runtime } = context;
+		hash.update(chunkGraph.getModuleGraphHash(this, runtime));
+		if (this.presentationalDependencies !== undefined) {
+			for (const dep of this.presentationalDependencies) {
+				dep.updateHash(hash, context);
+			}
+		}
+		super.updateHash(hash, context);
+	}
+
+	/**
+	 * Invalidates the cached state associated with this value.
+	 * @returns {void}
+	 */
+	invalidateBuild() {
+		// should be overridden to support this feature
+	}
+
+	/* istanbul ignore next */
+	/**
+	 * Returns the unique identifier used to reference this module.
+	 * @abstract
+	 * @returns {string} a unique identifier of the module
+	 */
+	identifier() {
+		const AbstractMethodError = require("./errors/AbstractMethodError");
+
+		throw new AbstractMethodError();
 	}
 
-	sortItems(sortChunks) {
-		super.sortItems();
-		if (sortChunks) this._chunks.sort();
-		this.reasons.sort((a, b) => {
-			if (a.module === b.module) return 0;
-			if (!a.module) return -1;
-			if (!b.module) return 1;
-			return sortById(a.module, b.module);
-		});
-		if (Array.isArray(this.usedExports)) {
-			this.usedExports.sort();
+	/* istanbul ignore next */
+	/**
+	 * Returns a human-readable identifier for this module.
+	 * @abstract
+	 * @param {RequestShortener} requestShortener the request shortener
+	 * @returns {string} a user readable identifier of the module
+	 */
+	readableIdentifier(requestShortener) {
+		const AbstractMethodError = require("./errors/AbstractMethodError");
+
+		throw new AbstractMethodError();
+	}
+
+	/* istanbul ignore next */
+	/**
+	 * Builds the module using the provided compilation context.
+	 * @abstract
+	 * @param {WebpackOptions} options webpack options
+	 * @param {Compilation} compilation the compilation
+	 * @param {ResolverWithOptions} resolver the resolver
+	 * @param {InputFileSystem} fs the file system
+	 * @param {BuildCallback} callback callback function
+	 * @returns {void}
+	 */
+	build(options, compilation, resolver, fs, callback) {
+		const AbstractMethodError = require("./errors/AbstractMethodError");
+
+		throw new AbstractMethodError();
+	}
+
+	/**
+	 * Returns the source types this module can generate.
+	 * @abstract
+	 * @returns {SourceTypes} types available (do not mutate)
+	 */
+	getSourceTypes() {
+		// Better override this method to return the correct types
+		if (this.source === Module.prototype.source) {
+			return DEFAULT_TYPES_UNKNOWN;
 		}
+		return JAVASCRIPT_TYPES;
 	}
 
-	unbuild() {
-		this.dependencies.length = 0;
-		this.blocks.length = 0;
-		this.variables.length = 0;
-		this.buildMeta = undefined;
-		this.buildInfo = undefined;
-		this.disconnect();
+	/**
+	 * Freshly recomputed source types when they depend on incoming connections, for chunk-graph cache invalidation; undefined otherwise. #20800
+	 * @returns {SourceTypes | undefined} source types or undefined
+	 */
+	getReferencedSourceTypes() {
+		return undefined;
 	}
 
-	get arguments() {
-		throw new Error("Module.arguments was removed, there is no replacement.");
+	/**
+	 * Basic source types are high-level categories like javascript, css, webassembly, etc.
+	 * We only have built-in knowledge about the javascript basic type here; other basic types may be
+	 * added or changed over time by generators and do not need to be handled or detected here.
+	 *
+	 * Some modules, e.g. RemoteModule, may return non-basic source types like "remote" and "share-init"
+	 * from getSourceTypes(), but their generated output is still JavaScript, i.e. their basic type is JS.
+	 * @returns {BasicSourceTypes} types available (do not mutate)
+	 */
+	getSourceBasicTypes() {
+		return this.getSourceTypes();
 	}
 
-	set arguments(value) {
-		throw new Error("Module.arguments was removed, there is no replacement.");
+	/**
+	 * Returns generated source.
+	 * @abstract
+	 * @deprecated Use codeGeneration() instead
+	 * @param {DependencyTemplates} dependencyTemplates the dependency templates
+	 * @param {RuntimeTemplate} runtimeTemplate the runtime template
+	 * @param {SourceType=} type the type of source that should be generated
+	 * @returns {Source} generated source
+	 */
+	source(dependencyTemplates, runtimeTemplate, type = JAVASCRIPT_TYPE) {
+		if (this.codeGeneration === Module.prototype.codeGeneration) {
+			const AbstractMethodError = require("./errors/AbstractMethodError");
+
+			throw new AbstractMethodError();
+		}
+		const chunkGraph = ChunkGraph.getChunkGraphForModule(
+			this,
+			"Module.source() is deprecated. Use Compilation.codeGenerationResults.getSource(module, runtime, type) instead",
+			"DEP_WEBPACK_MODULE_SOURCE"
+		);
+		/** @type {CodeGenerationContext} */
+		const codeGenContext = {
+			dependencyTemplates,
+			runtimeTemplate,
+			moduleGraph: chunkGraph.moduleGraph,
+			chunkGraph,
+			runtime: undefined,
+			runtimes: [],
+			codeGenerationResults: undefined
+		};
+		const sources = this.codeGeneration(codeGenContext).sources;
+
+		return /** @type {Source} */ (
+			type
+				? sources.get(type)
+				: sources.get(/** @type {SourceType} */ (first(this.getSourceTypes())))
+		);
+	}
+
+	/* istanbul ignore next */
+	/**
+	 * Returns the estimated size for the requested source type.
+	 * @abstract
+	 * @param {string=} type the source type for which the size should be estimated
+	 * @returns {number} the estimated size of the module (must be non-zero)
+	 */
+	size(type) {
+		const AbstractMethodError = require("./errors/AbstractMethodError");
+
+		throw new AbstractMethodError();
+	}
+
+	/**
+	 * Gets the library identifier.
+	 * @param {LibIdentOptions} options options
+	 * @returns {LibIdent | null} an identifier for library inclusion
+	 */
+	libIdent(options) {
+		return null;
+	}
+
+	/**
+	 * Returns the path used when matching this module against rule conditions.
+	 * @returns {NameForCondition | null} absolute path which should be used for condition matching (usually the resource path)
+	 */
+	nameForCondition() {
+		return null;
+	}
+
+	/**
+	 * Returns the reason this module cannot be concatenated, when one exists.
+	 * @param {ConcatenationBailoutReasonContext} context context
+	 * @returns {string | undefined} reason why this module can't be concatenated, undefined when it can be concatenated
+	 */
+	getConcatenationBailoutReason(context) {
+		return `Module Concatenation is not implemented for ${this.constructor.name}`;
+	}
+
+	/**
+	 * Gets side effects connection state.
+	 * @param {ModuleGraph} moduleGraph the module graph
+	 * @returns {ConnectionState} how this module should be connected to referencing modules when consumed for side-effects only
+	 */
+	getSideEffectsConnectionState(moduleGraph) {
+		return true;
+	}
+
+	/**
+	 * Generates code and runtime requirements for this module.
+	 * @param {CodeGenerationContext} context context for code generation
+	 * @returns {CodeGenerationResult} result
+	 */
+	codeGeneration(context) {
+		// Best override this method
+		/** @type {Sources} */
+		const sources = new Map();
+		for (const type of this.getSourceTypes()) {
+			if (type !== UNKNOWN_TYPE) {
+				sources.set(
+					type,
+					this.source(
+						context.dependencyTemplates,
+						context.runtimeTemplate,
+						type
+					)
+				);
+			}
+		}
+		return {
+			sources,
+			runtimeRequirements: new Set([
+				RuntimeGlobals.module,
+				RuntimeGlobals.exports,
+				RuntimeGlobals.require
+			])
+		};
+	}
+
+	/**
+	 * Returns true if the module can be placed in the chunk.
+	 * @param {Chunk} chunk the chunk which condition should be checked
+	 * @param {Compilation} compilation the compilation
+	 * @returns {boolean} true if the module can be placed in the chunk
+	 */
+	chunkCondition(chunk, compilation) {
+		return true;
+	}
+
+	hasChunkCondition() {
+		return this.chunkCondition !== Module.prototype.chunkCondition;
+	}
+
+	/**
+	 * Assuming this module is in the cache. Update the (cached) module with
+	 * the fresh module from the factory. Usually updates internal references
+	 * and properties.
+	 * @param {Module} module fresh module
+	 * @returns {void}
+	 */
+	updateCacheModule(module) {
+		this.type = module.type;
+		this.layer = module.layer;
+		this.context = module.context;
+		this.factoryMeta = module.factoryMeta;
+		this.resolveOptions = module.resolveOptions;
+	}
+
+	/**
+	 * Module should be unsafe cached. Get data that's needed for that.
+	 * This data will be passed to restoreFromUnsafeCache later.
+	 * @returns {UnsafeCacheData} cached data
+	 */
+	getUnsafeCacheData() {
+		return {
+			factoryMeta: this.factoryMeta,
+			resolveOptions: this.resolveOptions
+		};
+	}
+
+	/**
+	 * restore unsafe cache data
+	 * @param {UnsafeCacheData} unsafeCacheData data from getUnsafeCacheData
+	 * @param {NormalModuleFactory} normalModuleFactory the normal module factory handling the unsafe caching
+	 */
+	_restoreFromUnsafeCache(unsafeCacheData, normalModuleFactory) {
+		this.factoryMeta = unsafeCacheData.factoryMeta;
+		this.resolveOptions = unsafeCacheData.resolveOptions;
+	}
+
+	/**
+	 * Assuming this module is in the cache. Remove internal references to allow freeing some memory.
+	 */
+	cleanupForCache() {
+		this.factoryMeta = undefined;
+		this.resolveOptions = undefined;
+	}
+
+	/**
+	 * Re-acquire factory-derived helpers (parser/generator) for an incremental
+	 * rebuild so this compilation's plugin hooks apply.
+	 * @param {NormalModuleFactory} normalModuleFactory the normal module factory
+	 * @returns {void}
+	 */
+	_restoreParserAndGenerator(normalModuleFactory) {}
+
+	/**
+	 * Releases the cached parser between incremental rebuilds so it doesn't keep
+	 * the previous compilation alive.
+	 * @returns {void}
+	 */
+	_cleanupParserForCache() {}
+
+	/**
+	 * Gets the original source.
+	 * @returns {Source | null} the original source for the module before webpack transformation
+	 */
+	originalSource() {
+		return null;
+	}
+
+	/**
+	 * Adds the provided file dependencies to the module.
+	 * @param {FileSystemDependencies} fileDependencies set where file dependencies are added to
+	 * @param {FileSystemDependencies} contextDependencies set where context dependencies are added to
+	 * @param {FileSystemDependencies} missingDependencies set where missing dependencies are added to
+	 * @param {FileSystemDependencies} buildDependencies set where build dependencies are added to
+	 */
+	addCacheDependencies(
+		fileDependencies,
+		contextDependencies,
+		missingDependencies,
+		buildDependencies
+	) {}
+
+	/**
+	 * Serializes this instance into the provided serializer context.
+	 * @param {ObjectSerializerContext} context context
+	 */
+	serialize(context) {
+		const { write } = context;
+		write(this.type);
+		write(this.layer);
+		write(this.context);
+		write(this.resolveOptions);
+		write(this.factoryMeta);
+		write(this.useSourceMap);
+		write(this.useSimpleSourceMap);
+		write(
+			this._warnings !== undefined && this._warnings.length === 0
+				? undefined
+				: this._warnings
+		);
+		write(
+			this._errors !== undefined && this._errors.length === 0
+				? undefined
+				: this._errors
+		);
+		write(this.buildMeta);
+		write(this.buildInfo);
+		write(this.presentationalDependencies);
+		write(this.codeGenerationDependencies);
+		super.serialize(context);
+	}
+
+	/**
+	 * Restores this instance from the provided deserializer context.
+	 * @param {ObjectDeserializerContext} context context
+	 */
+	deserialize(context) {
+		const { read } = context;
+		this.type = read();
+		this.layer = read();
+		this.context = read();
+		this.resolveOptions = read();
+		this.factoryMeta = read();
+		this.useSourceMap = read();
+		this.useSimpleSourceMap = read();
+		this._warnings = read();
+		this._errors = read();
+		this.buildMeta = read();
+		this.buildInfo = read();
+		this.presentationalDependencies = read();
+		this.codeGenerationDependencies = read();
+		super.deserialize(context);
+	}
+
+	// TODO remove in webpack 6
+	/**
+	 * Gets source basic types.
+	 * @deprecated In webpack 6, call getSourceBasicTypes() directly on the module instance instead of using this static method.
+	 * @param {Module} module the module
+	 * @returns {ReturnType} the source types of the module
+	 */
+	static getSourceBasicTypes(module) {
+		if (!(module instanceof Module)) {
+			// https://github.com/webpack/webpack/issues/20597
+			// fallback to javascript
+			return JAVASCRIPT_TYPES;
+		}
+		return module.getSourceBasicTypes();
 	}
 }
 
-// TODO remove in webpack 5
-Object.defineProperty(Module.prototype, "forEachChunk", {
-	configurable: false,
-	value: util.deprecate(function(fn) {
-		this._chunks.forEach(fn);
-	}, "Module.forEachChunk: Use for(const chunk of module.chunksIterable) instead")
-});
+makeSerializable(Module, "webpack/lib/Module");
 
-// TODO remove in webpack 5
-Object.defineProperty(Module.prototype, "mapChunks", {
-	configurable: false,
-	value: util.deprecate(function(fn) {
-		return Array.from(this._chunks, fn);
-	}, "Module.mapChunks: Use Array.from(module.chunksIterable, fn) instead")
+// TODO remove in webpack 6
+Object.defineProperty(Module.prototype, "hasEqualsChunks", {
+	/**
+	 * Gets has equals chunks.
+	 * @deprecated
+	 * @returns {EXPECTED_ANY} throw an error
+	 */
+	get() {
+		throw new Error(
+			"Module.hasEqualsChunks was renamed (use hasEqualChunks instead)"
+		);
+	}
 });
 
-// TODO remove in webpack 5
-Object.defineProperty(Module.prototype, "entry", {
-	configurable: false,
+// TODO remove in webpack 6
+Object.defineProperty(Module.prototype, "isUsed", {
+	/**
+	 * Returns throw an error.
+	 * @deprecated
+	 * @returns {EXPECTED_ANY} throw an error
+	 */
 	get() {
-		throw new Error("Module.entry was removed. Use Chunk.entryModule");
-	},
-	set() {
-		throw new Error("Module.entry was removed. Use Chunk.entryModule");
+		throw new Error(
+			"Module.isUsed was renamed (use getUsedName, isExportUsed or isModuleUsed instead)"
+		);
 	}
 });
 
-// TODO remove in webpack 5
-Object.defineProperty(Module.prototype, "meta", {
-	configurable: false,
-	get: util.deprecate(function() {
-		return this.buildMeta;
-	}, "Module.meta was renamed to Module.buildMeta"),
-	set: util.deprecate(function(value) {
-		this.buildMeta = value;
-	}, "Module.meta was renamed to Module.buildMeta")
+// TODO remove in webpack 6
+Object.defineProperty(Module.prototype, "errors", {
+	/**
+	 * Returns errors.
+	 * @deprecated
+	 * @returns {Error[]} errors
+	 */
+	get: util.deprecate(
+		/**
+		 * Returns errors.
+		 * @this {Module}
+		 * @returns {Error[]} errors
+		 */
+		function errors() {
+			if (this._errors === undefined) {
+				this._errors = [];
+			}
+			return this._errors;
+		},
+		"Module.errors was removed (use getErrors instead)",
+		"DEP_WEBPACK_MODULE_ERRORS"
+	)
 });
 
-/** @type {function(): string} */
-Module.prototype.identifier = null;
-
-/** @type {function(RequestShortener): string} */
-Module.prototype.readableIdentifier = null;
+// TODO remove in webpack 6
+Object.defineProperty(Module.prototype, "warnings", {
+	/**
+	 * Returns warnings.
+	 * @deprecated
+	 * @returns {Error[]} warnings
+	 */
+	get: util.deprecate(
+		/**
+		 * Returns warnings.
+		 * @this {Module}
+		 * @returns {Error[]} warnings
+		 */
+		function warnings() {
+			if (this._warnings === undefined) {
+				this._warnings = [];
+			}
+			return this._warnings;
+		},
+		"Module.warnings was removed (use getWarnings instead)",
+		"DEP_WEBPACK_MODULE_WARNINGS"
+	)
+});
 
-Module.prototype.build = null;
-Module.prototype.source = null;
-Module.prototype.size = null;
-Module.prototype.nameForCondition = null;
-Module.prototype.updateCacheModule = null;
+// TODO remove in webpack 6
+Object.defineProperty(Module.prototype, "used", {
+	/**
+	 * Returns throw an error.
+	 * @deprecated
+	 * @returns {EXPECTED_ANY} throw an error
+	 */
+	get() {
+		throw new Error(
+			"Module.used was refactored (use ModuleGraph.getUsedExports instead)"
+		);
+	},
+	/**
+	 * Updates used using the provided value.
+	 * @param {EXPECTED_ANY} value value
+	 */
+	set(value) {
+		throw new Error(
+			"Module.used was refactored (use ModuleGraph.setUsedExports instead)"
+		);
+	}
+});
 
 module.exports = Module;
diff --git a/lib/ModuleBuildError.js b/lib/ModuleBuildError.js
deleted file mode 100644
index d6b498eef23..00000000000
--- a/lib/ModuleBuildError.js
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const WebpackError = require("./WebpackError");
-const { cutOffLoaderExecution } = require("./ErrorHelpers");
-
-class ModuleBuildError extends WebpackError {
-	constructor(module, err, { from = null } = {}) {
-		let message = "Module build failed";
-		let details = undefined;
-		if (from) {
-			message += ` (from ${from}):\n`;
-		} else {
-			message += ": ";
-		}
-		if (err !== null && typeof err === "object") {
-			if (typeof err.stack === "string" && err.stack) {
-				const stack = cutOffLoaderExecution(err.stack);
-				if (!err.hideStack) {
-					message += stack;
-				} else {
-					details = stack;
-					if (typeof err.message === "string" && err.message) {
-						message += err.message;
-					} else {
-						message += err;
-					}
-				}
-			} else if (typeof err.message === "string" && err.message) {
-				message += err.message;
-			} else {
-				message += err;
-			}
-		} else {
-			message = err;
-		}
-
-		super(message);
-
-		this.name = "ModuleBuildError";
-		this.details = details;
-		this.module = module;
-		this.error = err;
-
-		Error.captureStackTrace(this, this.constructor);
-	}
-}
-
-module.exports = ModuleBuildError;
diff --git a/lib/ModuleDependencyError.js b/lib/ModuleDependencyError.js
deleted file mode 100644
index cb16cc34a1a..00000000000
--- a/lib/ModuleDependencyError.js
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const WebpackError = require("./WebpackError");
-
-/** @typedef {import("./Module")} Module */
-
-class ModuleDependencyError extends WebpackError {
-	/**
-	 * Creates an instance of ModuleDependencyError.
-	 * @param {Module} module module tied to dependency
-	 * @param {Error} err error thrown
-	 * @param {TODO} loc location of dependency
-	 */
-	constructor(module, err, loc) {
-		super(err.message);
-
-		this.name = "ModuleDependencyError";
-		this.details = err.stack
-			.split("\n")
-			.slice(1)
-			.join("\n");
-		this.module = module;
-		this.loc = loc;
-		this.error = err;
-		this.origin = module.issuer;
-
-		Error.captureStackTrace(this, this.constructor);
-	}
-}
-
-module.exports = ModuleDependencyError;
diff --git a/lib/ModuleDependencyWarning.js b/lib/ModuleDependencyWarning.js
deleted file mode 100644
index be62791cbdc..00000000000
--- a/lib/ModuleDependencyWarning.js
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const WebpackError = require("./WebpackError");
-
-module.exports = class ModuleDependencyWarning extends WebpackError {
-	constructor(module, err, loc) {
-		super(err.message);
-
-		this.name = "ModuleDependencyWarning";
-		this.details = err.stack
-			.split("\n")
-			.slice(1)
-			.join("\n");
-		this.module = module;
-		this.loc = loc;
-		this.error = err;
-		this.origin = module.issuer;
-
-		Error.captureStackTrace(this, this.constructor);
-	}
-};
diff --git a/lib/ModuleError.js b/lib/ModuleError.js
deleted file mode 100644
index 7079d613292..00000000000
--- a/lib/ModuleError.js
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const WebpackError = require("./WebpackError");
-const { cleanUp } = require("./ErrorHelpers");
-
-class ModuleError extends WebpackError {
-	constructor(module, err, { from = null } = {}) {
-		let message = "Module Error";
-		if (from) {
-			message += ` (from ${from}):\n`;
-		} else {
-			message += ": ";
-		}
-		if (err && typeof err === "object" && err.message) {
-			message += err.message;
-		} else if (err) {
-			message += err;
-		}
-		super(message);
-		this.name = "ModuleError";
-		this.module = module;
-		this.error = err;
-		this.details =
-			err && typeof err === "object" && err.stack
-				? cleanUp(err.stack, this.message)
-				: undefined;
-
-		Error.captureStackTrace(this, this.constructor);
-	}
-}
-
-module.exports = ModuleError;
diff --git a/lib/ModuleFactory.js b/lib/ModuleFactory.js
new file mode 100644
index 00000000000..25c30842d6a
--- /dev/null
+++ b/lib/ModuleFactory.js
@@ -0,0 +1,62 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+/** @typedef {import("../declarations/WebpackOptions").ResolveOptions} ResolveOptions */
+/** @typedef {import("./Dependency")} Dependency */
+/** @typedef {import("./Module")} Module */
+
+/**
+ * Defines the module factory result type used by this module.
+ * @typedef {object} ModuleFactoryResult
+ * @property {Module=} module the created module or unset if no module was created
+ * @property {Set=} fileDependencies
+ * @property {Set=} contextDependencies
+ * @property {Set=} missingDependencies
+ * @property {boolean=} cacheable allow to use the unsafe cache
+ */
+
+/** @typedef {string | null} IssuerLayer */
+
+/**
+ * Defines the module factory create data context info type used by this module.
+ * @typedef {object} ModuleFactoryCreateDataContextInfo
+ * @property {string} issuer
+ * @property {IssuerLayer} issuerLayer
+ * @property {string=} compiler
+ */
+
+/**
+ * Defines the module factory create data type used by this module.
+ * @typedef {object} ModuleFactoryCreateData
+ * @property {ModuleFactoryCreateDataContextInfo} contextInfo
+ * @property {ResolveOptions=} resolveOptions
+ * @property {string} context
+ * @property {Dependency[]} dependencies
+ */
+
+/**
+ * Represents the module factory runtime component.
+ * @typedef {(err?: Error | null, result?: ModuleFactoryResult) => void} ModuleFactoryCallback
+ */
+
+class ModuleFactory {
+	/* istanbul ignore next */
+	/**
+	 * Processes the provided data.
+	 * @abstract
+	 * @param {ModuleFactoryCreateData} data data object
+	 * @param {ModuleFactoryCallback} callback callback
+	 * @returns {void}
+	 */
+	create(data, callback) {
+		const AbstractMethodError = require("./errors/AbstractMethodError");
+
+		throw new AbstractMethodError();
+	}
+}
+
+module.exports = ModuleFactory;
diff --git a/lib/ModuleFilenameHelpers.js b/lib/ModuleFilenameHelpers.js
index 105e89e3fa8..1ef6aae9e62 100644
--- a/lib/ModuleFilenameHelpers.js
+++ b/lib/ModuleFilenameHelpers.js
@@ -2,20 +2,42 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
+const NormalModule = require("./NormalModule");
+const { DEFAULTS } = require("./config/defaults");
 const createHash = require("./util/createHash");
+const memoize = require("./util/memoize");
+
+/** @typedef {import("../declarations/WebpackOptions").HashFunction} HashFunction */
+/** @typedef {import("./ChunkGraph")} ChunkGraph */
+/** @typedef {import("./Module")} Module */
+/** @typedef {import("./RequestShortener")} RequestShortener */
+
+/** @typedef {(str: string) => boolean} MatcherFn */
+/** @typedef {string | RegExp | MatcherFn | (string | RegExp | MatcherFn)[]} Matcher */
+/** @typedef {{ test?: Matcher, include?: Matcher, exclude?: Matcher }} MatchObject */
 
-const ModuleFilenameHelpers = exports;
+const ModuleFilenameHelpers = module.exports;
 
+ModuleFilenameHelpers.DEFAULT_MODULE_FILENAME_TEMPLATE =
+	"webpack://[namespace]/[resourcePath]";
+ModuleFilenameHelpers.DEFAULT_FALLBACK_MODULE_FILENAME_TEMPLATE =
+	"webpack://[namespace]/[resourcePath]?[hash]";
+
+// TODO webpack 6: consider removing these
 ModuleFilenameHelpers.ALL_LOADERS_RESOURCE = "[all-loaders][resource]";
-ModuleFilenameHelpers.REGEXP_ALL_LOADERS_RESOURCE = /\[all-?loaders\]\[resource\]/gi;
+ModuleFilenameHelpers.REGEXP_ALL_LOADERS_RESOURCE =
+	/\[all-?loaders\]\[resource\]/gi;
 ModuleFilenameHelpers.LOADERS_RESOURCE = "[loaders][resource]";
 ModuleFilenameHelpers.REGEXP_LOADERS_RESOURCE = /\[loaders\]\[resource\]/gi;
 ModuleFilenameHelpers.RESOURCE = "[resource]";
 ModuleFilenameHelpers.REGEXP_RESOURCE = /\[resource\]/gi;
 ModuleFilenameHelpers.ABSOLUTE_RESOURCE_PATH = "[absolute-resource-path]";
-ModuleFilenameHelpers.REGEXP_ABSOLUTE_RESOURCE_PATH = /\[abs(olute)?-?resource-?path\]/gi;
+// cSpell:words olute
+ModuleFilenameHelpers.REGEXP_ABSOLUTE_RESOURCE_PATH =
+	/\[abs(olute)?-?resource-?path\]/gi;
 ModuleFilenameHelpers.RESOURCE_PATH = "[resource-path]";
 ModuleFilenameHelpers.REGEXP_RESOURCE_PATH = /\[resource-?path\]/gi;
 ModuleFilenameHelpers.ALL_LOADERS = "[all-loaders]";
@@ -31,148 +53,355 @@ ModuleFilenameHelpers.REGEXP_HASH = /\[hash\]/gi;
 ModuleFilenameHelpers.NAMESPACE = "[namespace]";
 ModuleFilenameHelpers.REGEXP_NAMESPACE = /\[namespace\]/gi;
 
-const getAfter = (str, token) => {
+/** @typedef {() => string} ReturnStringCallback */
+
+/**
+ * Returns a function that returns the part of the string after the token
+ * @param {ReturnStringCallback} strFn the function to get the string
+ * @param {string} token the token to search for
+ * @returns {ReturnStringCallback} a function that returns the part of the string after the token
+ */
+const getAfter = (strFn, token) => () => {
+	const str = strFn();
 	const idx = str.indexOf(token);
-	return idx < 0 ? "" : str.substr(idx);
+	return idx < 0 ? "" : str.slice(idx);
 };
 
-const getBefore = (str, token) => {
+/**
+ * Returns a function that returns the part of the string before the token
+ * @param {ReturnStringCallback} strFn the function to get the string
+ * @param {string} token the token to search for
+ * @returns {ReturnStringCallback} a function that returns the part of the string before the token
+ */
+const getBefore = (strFn, token) => () => {
+	const str = strFn();
 	const idx = str.lastIndexOf(token);
-	return idx < 0 ? "" : str.substr(0, idx);
+	return idx < 0 ? "" : str.slice(0, idx);
 };
 
-const getHash = str => {
-	const hash = createHash("md4");
-	hash.update(str);
-	return hash.digest("hex").substr(0, 4);
-};
+/**
+ * Returns a function that returns a hash of the string
+ * @param {ReturnStringCallback} strFn the function to get the string
+ * @param {HashFunction=} hashFunction the hash function to use
+ * @returns {ReturnStringCallback} a function that returns the hash of the string
+ */
+const getHash =
+	(strFn, hashFunction = DEFAULTS.HASH_FUNCTION) =>
+	() => {
+		const hash = createHash(hashFunction);
+		hash.update(strFn());
+		const digest = hash.digest("hex");
+		return digest.slice(0, 4);
+	};
 
-const asRegExp = test => {
-	if (typeof test === "string") {
-		test = new RegExp("^" + test.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"));
+/**
+ * Returns the lazy access object.
+ * @template T
+ * Returns a lazy object. The object is lazy in the sense that the properties are
+ * only evaluated when they are accessed. This is only obtained by setting a function as the value for each key.
+ * @param {Record T>} obj the object to convert to a lazy access object
+ * @returns {Record} the lazy access object
+ */
+const lazyObject = (obj) => {
+	const newObj = /** @type {Record} */ ({});
+	for (const key of Object.keys(obj)) {
+		const fn = obj[key];
+		Object.defineProperty(newObj, key, {
+			get: () => fn(),
+			set: (v) => {
+				Object.defineProperty(newObj, key, {
+					value: v,
+					enumerable: true,
+					writable: true
+				});
+			},
+			enumerable: true,
+			configurable: true
+		});
 	}
-	return test;
+	return newObj;
 };
 
-ModuleFilenameHelpers.createFilename = (module, options, requestShortener) => {
-	const opts = Object.assign(
-		{
-			namespace: "",
-			moduleFilenameTemplate: ""
-		},
-		typeof options === "object"
+const SQUARE_BRACKET_TAG_REGEXP = /\[\\*([\w-]+)\\*\]/g;
+/**
+ * Defines the module filename template context type used by this module.
+ * @typedef {object} ModuleFilenameTemplateContext
+ * @property {string} identifier the identifier of the module
+ * @property {string} shortIdentifier the shortened identifier of the module
+ * @property {string} resource the resource of the module request
+ * @property {string} resourcePath the resource path of the module request
+ * @property {string} absoluteResourcePath the absolute resource path of the module request
+ * @property {string} loaders the loaders of the module request
+ * @property {string} allLoaders the all loaders of the module request
+ * @property {string} query the query of the module identifier
+ * @property {string} moduleId the module id of the module
+ * @property {string} hash the hash of the module identifier
+ * @property {string} namespace the module namespace
+ */
+/** @typedef {((context: ModuleFilenameTemplateContext) => string)} ModuleFilenameTemplateFunction */
+/** @typedef {string | ModuleFilenameTemplateFunction} ModuleFilenameTemplate */
+
+/**
+ * Returns the filename.
+ * @param {Module | string} module the module
+ * @param {{ namespace?: string, moduleFilenameTemplate?: ModuleFilenameTemplate }} options options
+ * @param {{ requestShortener: RequestShortener, chunkGraph: ChunkGraph, hashFunction?: HashFunction }} contextInfo context info
+ * @returns {string} the filename
+ */
+ModuleFilenameHelpers.createFilename = (
+	// eslint-disable-next-line default-param-last
+	module = "",
+	options,
+	{ requestShortener, chunkGraph, hashFunction = DEFAULTS.HASH_FUNCTION }
+) => {
+	const opts = {
+		namespace: "",
+		moduleFilenameTemplate: "",
+		...(typeof options === "object"
 			? options
 			: {
 					moduleFilenameTemplate: options
-			  }
-	);
+				})
+	};
 
+	/** @type {ReturnStringCallback} */
 	let absoluteResourcePath;
+	/** @type {ReturnStringCallback} */
 	let hash;
+	/** @type {ReturnStringCallback} */
 	let identifier;
+	/** @type {ReturnStringCallback} */
 	let moduleId;
+	/** @type {ReturnStringCallback} */
 	let shortIdentifier;
-	if (module === undefined) module = "";
+	/** @type {ReturnStringCallback} */
+	let resourceIdentifier;
 	if (typeof module === "string") {
-		shortIdentifier = requestShortener.shorten(module);
+		shortIdentifier =
+			/** @type {ReturnStringCallback} */
+			(memoize(() => requestShortener.shorten(module)));
 		identifier = shortIdentifier;
-		moduleId = "";
-		absoluteResourcePath = module.split("!").pop();
-		hash = getHash(identifier);
+		resourceIdentifier = shortIdentifier;
+		moduleId = () => "";
+		absoluteResourcePath = () =>
+			/** @type {string} */ (module.split("!").pop());
+		hash = getHash(identifier, hashFunction);
 	} else {
-		shortIdentifier = module.readableIdentifier(requestShortener);
-		identifier = requestShortener.shorten(module.identifier());
-		moduleId = module.id;
-		absoluteResourcePath = module
-			.identifier()
-			.split("!")
-			.pop();
-		hash = getHash(identifier);
+		shortIdentifier = memoize(() =>
+			module.readableIdentifier(requestShortener)
+		);
+		// `[resource]` and `[loaders]` must stay request paths: a subclass's
+		// readable identifier may carry display-only decorations (e.g. CssModule's
+		// `css ` prefix)
+		resourceIdentifier = memoize(() =>
+			module instanceof NormalModule
+				? /** @type {string} */ (requestShortener.shorten(module.userRequest))
+				: module.readableIdentifier(requestShortener)
+		);
+		identifier =
+			/** @type {ReturnStringCallback} */
+			(memoize(() => requestShortener.shorten(module.identifier())));
+		moduleId =
+			/** @type {ReturnStringCallback} */
+			(() => chunkGraph.getModuleId(module));
+		absoluteResourcePath = () =>
+			module instanceof NormalModule
+				? module.resource
+				: /** @type {string} */ (module.identifier().split("!").pop());
+		hash = getHash(identifier, hashFunction);
 	}
-	const resource = shortIdentifier.split("!").pop();
-	const loaders = getBefore(shortIdentifier, "!");
+	const resource =
+		/** @type {ReturnStringCallback} */
+		(memoize(() => resourceIdentifier().split("!").pop()));
+
+	const loaders = getBefore(resourceIdentifier, "!");
 	const allLoaders = getBefore(identifier, "!");
 	const query = getAfter(resource, "?");
-	const resourcePath = resource.substr(0, resource.length - query.length);
+	const resourcePath = () => {
+		const q = query().length;
+		return q === 0 ? resource() : resource().slice(0, -q);
+	};
 	if (typeof opts.moduleFilenameTemplate === "function") {
-		return opts.moduleFilenameTemplate({
-			identifier: identifier,
-			shortIdentifier: shortIdentifier,
-			resource: resource,
-			resourcePath: resourcePath,
-			absoluteResourcePath: absoluteResourcePath,
-			allLoaders: allLoaders,
-			query: query,
-			moduleId: moduleId,
-			hash: hash,
-			namespace: opts.namespace
-		});
+		return opts.moduleFilenameTemplate(
+			/** @type {ModuleFilenameTemplateContext} */
+			(
+				lazyObject({
+					identifier,
+					shortIdentifier,
+					resource,
+					resourcePath: memoize(resourcePath),
+					absoluteResourcePath: memoize(absoluteResourcePath),
+					loaders: memoize(loaders),
+					allLoaders: memoize(allLoaders),
+					query: memoize(query),
+					moduleId: memoize(moduleId),
+					hash: memoize(hash),
+					namespace: () => opts.namespace
+				})
+			)
+		);
 	}
-	return opts.moduleFilenameTemplate
-		.replace(ModuleFilenameHelpers.REGEXP_ALL_LOADERS_RESOURCE, identifier)
-		.replace(ModuleFilenameHelpers.REGEXP_LOADERS_RESOURCE, shortIdentifier)
-		.replace(ModuleFilenameHelpers.REGEXP_RESOURCE, resource)
-		.replace(ModuleFilenameHelpers.REGEXP_RESOURCE_PATH, resourcePath)
+
+	// TODO webpack 6: consider removing alternatives without dashes
+	/** @type {Map string>} */
+	const replacements = new Map([
+		["identifier", identifier],
+		["short-identifier", shortIdentifier],
+		["resource", resource],
+		["resource-path", resourcePath],
+		// cSpell:words resourcepath
+		["resourcepath", resourcePath],
+		["absolute-resource-path", absoluteResourcePath],
+		["abs-resource-path", absoluteResourcePath],
+		// cSpell:words absoluteresource
+		["absoluteresource-path", absoluteResourcePath],
+		// cSpell:words absresource
+		["absresource-path", absoluteResourcePath],
+		// cSpell:words resourcepath
+		["absolute-resourcepath", absoluteResourcePath],
+		// cSpell:words resourcepath
+		["abs-resourcepath", absoluteResourcePath],
+		// cSpell:words absoluteresourcepath
+		["absoluteresourcepath", absoluteResourcePath],
+		// cSpell:words absresourcepath
+		["absresourcepath", absoluteResourcePath],
+		["all-loaders", allLoaders],
+		// cSpell:words allloaders
+		["allloaders", allLoaders],
+		["loaders", loaders],
+		["query", query],
+		["id", moduleId],
+		["hash", hash],
+		["namespace", () => opts.namespace]
+	]);
+
+	// TODO webpack 6: consider removing weird double placeholders
+	return /** @type {string} */ (opts.moduleFilenameTemplate)
+		.replace(ModuleFilenameHelpers.REGEXP_ALL_LOADERS_RESOURCE, "[identifier]")
 		.replace(
-			ModuleFilenameHelpers.REGEXP_ABSOLUTE_RESOURCE_PATH,
-			absoluteResourcePath
+			ModuleFilenameHelpers.REGEXP_LOADERS_RESOURCE,
+			"[short-identifier]"
 		)
-		.replace(ModuleFilenameHelpers.REGEXP_ALL_LOADERS, allLoaders)
-		.replace(ModuleFilenameHelpers.REGEXP_LOADERS, loaders)
-		.replace(ModuleFilenameHelpers.REGEXP_QUERY, query)
-		.replace(ModuleFilenameHelpers.REGEXP_ID, moduleId)
-		.replace(ModuleFilenameHelpers.REGEXP_HASH, hash)
-		.replace(ModuleFilenameHelpers.REGEXP_NAMESPACE, opts.namespace);
+		.replace(SQUARE_BRACKET_TAG_REGEXP, (match, content) => {
+			if (content.length + 2 === match.length) {
+				const replacement = replacements.get(content.toLowerCase());
+				if (replacement !== undefined) {
+					return replacement();
+				}
+			} else if (match.startsWith("[\\") && match.endsWith("\\]")) {
+				return `[${match.slice(2, -2)}]`;
+			}
+			return match;
+		});
 };
 
+/**
+ * Replaces duplicate items in an array with new values generated by a callback function.
+ * The callback function is called with the duplicate item, the index of the duplicate item, and the number of times the item has been replaced.
+ * The callback function should return the new value for the duplicate item.
+ * @template T
+ * @param {T[]} array the array with duplicates to be replaced
+ * @param {(duplicateItem: T, duplicateItemIndex: number, numberOfTimesReplaced: number) => T} fn callback function to generate new values for the duplicate items
+ * @param {(firstElement: T, nextElement: T) => -1 | 0 | 1=} comparator optional comparator function to sort the duplicate items
+ * @returns {T[]} the array with duplicates replaced
+ * @example
+ * ```js
+ * const array = ["a", "b", "c", "a", "b", "a"];
+ * const result = ModuleFilenameHelpers.replaceDuplicates(array, (item, index, count) => `${item}-${count}`);
+ * // result: ["a-1", "b-1", "c", "a-2", "b-2", "a-3"]
+ * ```
+ */
 ModuleFilenameHelpers.replaceDuplicates = (array, fn, comparator) => {
 	const countMap = Object.create(null);
 	const posMap = Object.create(null);
-	array.forEach((item, idx) => {
+
+	for (const [idx, item] of array.entries()) {
 		countMap[item] = countMap[item] || [];
 		countMap[item].push(idx);
 		posMap[item] = 0;
-	});
+	}
 	if (comparator) {
-		Object.keys(countMap).forEach(item => {
+		for (const item of Object.keys(countMap)) {
 			countMap[item].sort(comparator);
-		});
+		}
 	}
 	return array.map((item, i) => {
 		if (countMap[item].length > 1) {
 			if (comparator && countMap[item][0] === i) return item;
 			return fn(item, i, posMap[item]++);
-		} else {
-			return item;
 		}
+		return item;
 	});
 };
 
-ModuleFilenameHelpers.matchPart = (str, test) => {
+/**
+ * Tests if a string matches a RegExp or an array of RegExp.
+ * @param {string} str string to test
+ * @param {Matcher} test value which will be used to match against the string
+ * @returns {boolean} true, when the RegExp matches
+ * @example
+ * ```js
+ * ModuleFilenameHelpers.matchPart("foo.js", "foo"); // true
+ * ModuleFilenameHelpers.matchPart("foo.js", "foo.js"); // true
+ * ModuleFilenameHelpers.matchPart("foo.js", "foo."); // true
+ * ModuleFilenameHelpers.matchPart("foo.js", "foo*"); // false
+ * ModuleFilenameHelpers.matchPart("foo.js", "foo.*"); // false
+ * ModuleFilenameHelpers.matchPart("foo.js", /^foo/); // true
+ * ModuleFilenameHelpers.matchPart("foo.js", [/^foo/, "bar"]); // true
+ * ModuleFilenameHelpers.matchPart("foo.js", [/^foo/, "bar"]); // true
+ * ModuleFilenameHelpers.matchPart("foo.js", [/^foo/, /^bar/]); // true
+ * ModuleFilenameHelpers.matchPart("foo.js", [/^baz/, /^bar/]); // false
+ * ```
+ */
+const matchPart = (str, test) => {
 	if (!test) return true;
-	test = asRegExp(test);
-	if (Array.isArray(test)) {
-		return test.map(asRegExp).some(regExp => regExp.test(str));
-	} else {
+	if (test instanceof RegExp) {
 		return test.test(str);
+	} else if (typeof test === "string") {
+		return str.startsWith(test);
+	} else if (typeof test === "function") {
+		return test(str);
 	}
+
+	return test.some((test) => matchPart(str, test));
 };
 
+ModuleFilenameHelpers.matchPart = matchPart;
+
+/**
+ * Tests if a string matches a match object. The match object can have the following properties:
+ * - `test`: a RegExp or an array of RegExp
+ * - `include`: a RegExp or an array of RegExp
+ * - `exclude`: a RegExp or an array of RegExp
+ *
+ * The `test` property is tested first, then `include` and then `exclude`.
+ * @param {MatchObject} obj a match object to test against the string
+ * @param {string} str string to test against the matching object
+ * @returns {boolean} true, when the object matches
+ * @example
+ * ```js
+ * ModuleFilenameHelpers.matchObject({ test: "foo.js" }, "foo.js"); // true
+ * ModuleFilenameHelpers.matchObject({ test: /^foo/ }, "foo.js"); // true
+ * ModuleFilenameHelpers.matchObject({ test: [/^foo/, "bar"] }, "foo.js"); // true
+ * ModuleFilenameHelpers.matchObject({ test: [/^foo/, "bar"] }, "baz.js"); // false
+ * ModuleFilenameHelpers.matchObject({ include: "foo.js" }, "foo.js"); // true
+ * ModuleFilenameHelpers.matchObject({ include: "foo.js" }, "bar.js"); // false
+ * ModuleFilenameHelpers.matchObject({ include: /^foo/ }, "foo.js"); // true
+ * ModuleFilenameHelpers.matchObject({ include: [/^foo/, "bar"] }, "foo.js"); // true
+ * ModuleFilenameHelpers.matchObject({ include: [/^foo/, "bar"] }, "baz.js"); // false
+ * ModuleFilenameHelpers.matchObject({ exclude: "foo.js" }, "foo.js"); // false
+ * ModuleFilenameHelpers.matchObject({ exclude: [/^foo/, "bar"] }, "foo.js"); // false
+ * ```
+ */
 ModuleFilenameHelpers.matchObject = (obj, str) => {
-	if (obj.test) {
-		if (!ModuleFilenameHelpers.matchPart(str, obj.test)) {
-			return false;
-		}
+	if (obj.test && !ModuleFilenameHelpers.matchPart(str, obj.test)) {
+		return false;
 	}
-	if (obj.include) {
-		if (!ModuleFilenameHelpers.matchPart(str, obj.include)) {
-			return false;
-		}
+	if (obj.include && !ModuleFilenameHelpers.matchPart(str, obj.include)) {
+		return false;
 	}
-	if (obj.exclude) {
-		if (ModuleFilenameHelpers.matchPart(str, obj.exclude)) {
-			return false;
-		}
+	if (obj.exclude && ModuleFilenameHelpers.matchPart(str, obj.exclude)) {
+		return false;
 	}
 	return true;
 };
diff --git a/lib/ModuleGraph.js b/lib/ModuleGraph.js
new file mode 100644
index 00000000000..1758f9cc932
--- /dev/null
+++ b/lib/ModuleGraph.js
@@ -0,0 +1,1257 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const util = require("util");
+const ExportsInfo = require("./ExportsInfo");
+const ModuleGraphConnection = require("./ModuleGraphConnection");
+const HarmonyImportDependency = require("./dependencies/HarmonyImportDependency");
+const { ImportPhaseUtils } = require("./dependencies/ImportPhase");
+const SortableSet = require("./util/SortableSet");
+const WeakTupleMap = require("./util/WeakTupleMap");
+const { sortWithSourceOrder } = require("./util/comparators");
+
+/** @typedef {import("./Compilation").ModuleMemCaches} ModuleMemCaches */
+/** @typedef {import("./DependenciesBlock")} DependenciesBlock */
+/** @typedef {import("./Dependency")} Dependency */
+/** @typedef {import("./ExportsInfo").ExportInfo} ExportInfo */
+/** @typedef {import("./ExportsInfo").ExportInfoName} ExportInfoName */
+/** @typedef {import("./Module")} Module */
+/** @typedef {import("./ModuleProfile")} ModuleProfile */
+/** @typedef {import("./RequestShortener")} RequestShortener */
+/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */
+/** @typedef {import("./dependencies/HarmonyImportSideEffectDependency")} HarmonyImportSideEffectDependency */
+/** @typedef {import("./dependencies/HarmonyImportSpecifierDependency")} HarmonyImportSpecifierDependency */
+/** @typedef {import("./util/comparators").DependencySourceOrder} DependencySourceOrder */
+
+/**
+ * Defines the optimization bailout function callback.
+ * @callback OptimizationBailoutFunction
+ * @param {RequestShortener} requestShortener
+ * @returns {string}
+ */
+
+/** @type {Iterable} */
+const EMPTY_SET = new Set();
+
+/**
+ * Gets connections by key.
+ * @template {Module | null | undefined} T
+ * @param {SortableSet} set input
+ * @param {(connection: ModuleGraphConnection) => T} getKey function to extract key from connection
+ * @returns {ReadonlyMap>} mapped by key
+ */
+const getConnectionsByKey = (set, getKey) => {
+	/** @type {Map} */
+	const map = new Map();
+	/** @type {T | 0} */
+	let lastKey = 0;
+	/** @type {ModuleGraphConnection[] | undefined} */
+	let lastList;
+	for (const connection of set) {
+		const key = getKey(connection);
+		if (lastKey === key) {
+			/** @type {ModuleGraphConnection[]} */
+			(lastList).push(connection);
+		} else {
+			lastKey = key;
+			const list = map.get(key);
+			if (list !== undefined) {
+				lastList = list;
+				list.push(connection);
+			} else {
+				const list = [connection];
+				lastList = list;
+				map.set(key, list);
+			}
+		}
+	}
+	return map;
+};
+
+/**
+ * Gets connections by origin module.
+ * @param {SortableSet} set input
+ * @returns {ReadonlyMap>} mapped by origin module
+ */
+const getConnectionsByOriginModule = (set) =>
+	getConnectionsByKey(set, (connection) => connection.originModule);
+
+/**
+ * Gets connections by module.
+ * @param {SortableSet} set input
+ * @returns {ReadonlyMap>} mapped by module
+ */
+const getConnectionsByModule = (set) =>
+	getConnectionsByKey(set, (connection) => connection.module);
+
+/** @typedef {SortableSet} IncomingConnections */
+/** @typedef {SortableSet} OutgoingConnections */
+/** @typedef {Module | null | undefined} Issuer */
+/** @typedef {(string | OptimizationBailoutFunction)[]} OptimizationBailouts */
+
+class ModuleGraphModule {
+	constructor() {
+		/** @type {IncomingConnections} */
+		this.incomingConnections = new SortableSet();
+		/** @type {OutgoingConnections | undefined} */
+		this.outgoingConnections = undefined;
+		/** @type {Issuer} */
+		this.issuer = undefined;
+		/** @type {OptimizationBailouts} */
+		this.optimizationBailout = [];
+		/** @type {ExportsInfo} */
+		this.exports = new ExportsInfo();
+		/** @type {number | null} */
+		this.preOrderIndex = null;
+		/** @type {number | null} */
+		this.postOrderIndex = null;
+		/** @type {number | null} */
+		this.depth = null;
+		/** @type {ModuleProfile | undefined} */
+		this.profile = undefined;
+		/** @type {boolean} */
+		this.async = false;
+		/** @type {ModuleGraphConnection[] | undefined} */
+		this._unassignedConnections = undefined;
+	}
+}
+
+/** @typedef {(moduleGraphConnection: ModuleGraphConnection) => boolean} FilterConnection */
+
+/** @typedef {EXPECTED_OBJECT} MetaKey */
+
+/** @typedef {import("./dependencies/CommonJsExportRequireDependency").idsSymbol} CommonJsExportRequireDependencyIDsSymbol */
+/** @typedef {import("./dependencies/HarmonyImportSpecifierDependency").idsSymbol} HarmonyImportSpecifierDependencyIDsSymbol */
+/** @typedef {import("./dependencies/HarmonyExportImportedSpecifierDependency").idsSymbol} HarmonyExportImportedSpecifierDependencyIDsSymbol */
+
+/**
+ * Defines the known meta type used by this module.
+ * @typedef {object} KnownMeta
+ * @property {Map=} importVarMap
+ * @property {Map=} deferredImportVarMap
+ */
+
+/** @typedef {KnownMeta & Record & Record} Meta */
+
+class ModuleGraph {
+	constructor() {
+		/**
+		 * @type {WeakMap}
+		 * @private
+		 */
+		this._dependencyMap = new WeakMap();
+		/**
+		 * @type {Map}
+		 * @private
+		 */
+		this._moduleMap = new Map();
+		/**
+		 * @type {WeakMap}
+		 * @private
+		 */
+		this._metaMap = new WeakMap();
+		/**
+		 * @type {WeakTupleMap | undefined}
+		 * @private
+		 */
+		this._cache = undefined;
+		/**
+		 * @type {ModuleMemCaches | undefined}
+		 * @private
+		 */
+		this._moduleMemCaches = undefined;
+
+		/**
+		 * @type {string | undefined}
+		 * @private
+		 */
+		this._cacheStage = undefined;
+
+		// incremental rebuilds: when set, move/copyOutgoingModuleConnections record
+		// their seal-time graph mutations (concatenation) here so they can be reversed,
+		// keeping this durable graph reusable across rebuilds
+		/**
+		 * @type {{ oldModule?: Module, newModule: Module, movedOut?: ModuleGraphConnection[] | undefined, movedIn?: ModuleGraphConnection[] | undefined, copied?: ModuleGraphConnection[] }[] | undefined}
+		 * @private
+		 */
+		this._mutationJournal = undefined;
+
+		/**
+		 * @type {WeakMap}
+		 * @private
+		 */
+		this._dependencySourceOrderMap = new WeakMap();
+
+		/**
+		 * @type {Set}
+		 * @private
+		 */
+		this._modulesNeedingSort = new Set();
+	}
+
+	/**
+	 * Get module graph module.
+	 * @param {Module} module the module
+	 * @returns {ModuleGraphModule} the internal module
+	 */
+	_getModuleGraphModule(module) {
+		let mgm = this._moduleMap.get(module);
+		if (mgm === undefined) {
+			mgm = new ModuleGraphModule();
+			this._moduleMap.set(module, mgm);
+		}
+		return mgm;
+	}
+
+	/**
+	 * Updates parents using the provided dependency.
+	 * @param {Dependency} dependency the dependency
+	 * @param {DependenciesBlock} block parent block
+	 * @param {Module} module parent module
+	 * @param {number=} indexInBlock position in block
+	 * @returns {void}
+	 */
+	setParents(dependency, block, module, indexInBlock = -1) {
+		dependency._parentDependenciesBlockIndex = indexInBlock;
+		dependency._parentDependenciesBlock = block;
+		dependency._parentModule = module;
+	}
+
+	/**
+	 * Sets parent dependencies block index.
+	 * @param {Dependency} dependency the dependency
+	 * @param {number} index the index
+	 * @returns {void}
+	 */
+	setParentDependenciesBlockIndex(dependency, index) {
+		dependency._parentDependenciesBlockIndex = index;
+	}
+
+	/**
+	 * Gets parent module.
+	 * @param {Dependency} dependency the dependency
+	 * @returns {Module | undefined} parent module
+	 */
+	getParentModule(dependency) {
+		return dependency._parentModule;
+	}
+
+	/**
+	 * Returns parent block.
+	 * @param {Dependency} dependency the dependency
+	 * @returns {DependenciesBlock | undefined} parent block
+	 */
+	getParentBlock(dependency) {
+		return dependency._parentDependenciesBlock;
+	}
+
+	/**
+	 * Gets parent block index.
+	 * @param {Dependency} dependency the dependency
+	 * @returns {number} index
+	 */
+	getParentBlockIndex(dependency) {
+		return dependency._parentDependenciesBlockIndex;
+	}
+
+	/**
+	 * Sets resolved module.
+	 * @param {Module | null} originModule the referencing module
+	 * @param {Dependency} dependency the referencing dependency
+	 * @param {Module} module the referenced module
+	 * @returns {void}
+	 */
+	setResolvedModule(originModule, dependency, module) {
+		const connection = new ModuleGraphConnection(
+			originModule,
+			dependency,
+			module,
+			undefined,
+			// weak is defined on ModuleDependency; undefined for other dependency kinds
+			/** @type {{ weak?: boolean }} */ (dependency).weak,
+			dependency.getCondition(this)
+		);
+		const connections = this._getModuleGraphModule(module).incomingConnections;
+		connections.add(connection);
+		if (originModule) {
+			const mgm = this._getModuleGraphModule(originModule);
+			if (mgm._unassignedConnections === undefined) {
+				mgm._unassignedConnections = [];
+			}
+			mgm._unassignedConnections.push(connection);
+			if (mgm.outgoingConnections === undefined) {
+				mgm.outgoingConnections = new SortableSet();
+			}
+			mgm.outgoingConnections.add(connection);
+		} else {
+			this._dependencyMap.set(dependency, connection);
+		}
+	}
+
+	/**
+	 * Updates module using the provided dependency.
+	 * @param {Dependency} dependency the referencing dependency
+	 * @param {Module} module the referenced module
+	 * @returns {void}
+	 */
+	updateModule(dependency, module) {
+		const connection =
+			/** @type {ModuleGraphConnection} */
+			(this.getConnection(dependency));
+		if (connection.module === module) return;
+		const newConnection = connection.clone();
+		newConnection.module = module;
+		this._dependencyMap.set(dependency, newConnection);
+		connection.setActive(false);
+		const originMgm = this._getModuleGraphModule(
+			/** @type {Module} */ (connection.originModule)
+		);
+		/** @type {OutgoingConnections} */
+		(originMgm.outgoingConnections).add(newConnection);
+		const targetMgm = this._getModuleGraphModule(module);
+		targetMgm.incomingConnections.add(newConnection);
+	}
+
+	/**
+	 * Updates parent using the provided dependency.
+	 * @param {Dependency} dependency the need update dependency
+	 * @param {ModuleGraphConnection=} connection the target connection
+	 * @param {Module=} parentModule the parent module
+	 * @returns {void}
+	 */
+	updateParent(dependency, connection, parentModule) {
+		if (this._dependencySourceOrderMap.has(dependency)) {
+			return;
+		}
+		if (!connection || !parentModule) {
+			return;
+		}
+		const originDependency = connection.dependency;
+
+		// src/index.js
+		// import { c } from "lib/c" -> c = 0
+		// import { a, b } from "lib" -> a and b have the same source order -> a = b = 1
+		// import { d } from "lib/d" -> d = 2
+		const currentSourceOrder =
+			/** @type {HarmonyImportSideEffectDependency | HarmonyImportSpecifierDependency} */
+			(dependency).sourceOrder;
+
+		// lib/index.js (reexport)
+		// import { a } from "lib/a" -> a = 0
+		// import { b } from "lib/b" -> b = 1
+		const originSourceOrder =
+			/** @type {HarmonyImportSideEffectDependency | HarmonyImportSpecifierDependency} */
+			(originDependency).sourceOrder;
+		if (
+			typeof currentSourceOrder === "number" &&
+			typeof originSourceOrder === "number"
+		) {
+			// src/index.js
+			// import { c } from "lib/c" -> c = 0
+			// import { a } from "lib/a" -> a = 1.0 = 1(main) + 0.0(sub)
+			// import { b } from "lib/b" -> b = 1.1 = 1(main) + 0.1(sub)
+			// import { d } from "lib/d" -> d = 2
+			this._dependencySourceOrderMap.set(dependency, {
+				main: currentSourceOrder,
+				sub: originSourceOrder
+			});
+
+			// Save for later batch sorting
+			this._modulesNeedingSort.add(parentModule);
+		}
+	}
+
+	/**
+	 * Finish update parent.
+	 * @returns {void}
+	 */
+	finishUpdateParent() {
+		if (this._modulesNeedingSort.size === 0) {
+			return;
+		}
+		for (const mod of this._modulesNeedingSort) {
+			// If dependencies like HarmonyImportSideEffectDependency and HarmonyImportSpecifierDependency have a SourceOrder,
+			// we sort based on it; otherwise, we preserve the original order.
+			sortWithSourceOrder(
+				mod.dependencies,
+				this._dependencySourceOrderMap,
+				(dep, index) => this.setParentDependenciesBlockIndex(dep, index)
+			);
+		}
+		this._modulesNeedingSort.clear();
+	}
+
+	/**
+	 * Removes connection.
+	 * @param {Dependency} dependency the referencing dependency
+	 * @returns {void}
+	 */
+	removeConnection(dependency) {
+		const connection =
+			/** @type {ModuleGraphConnection} */
+			(this.getConnection(dependency));
+		const targetMgm = this._getModuleGraphModule(connection.module);
+		targetMgm.incomingConnections.delete(connection);
+		const originMgm = this._getModuleGraphModule(
+			/** @type {Module} */ (connection.originModule)
+		);
+		/** @type {OutgoingConnections} */
+		(originMgm.outgoingConnections).delete(connection);
+		this._dependencyMap.set(dependency, null);
+	}
+
+	/**
+	 * Removes every outgoing connection of `module` whose dependency is in
+	 * `staleDependencies`, including inactive ones left by `updateModule`
+	 * re-targeting (which the dependency-keyed `removeConnection` misses). Needed
+	 * when a module is rebuilt against a persisted graph so stale connections don't
+	 * linger in the old target's incoming set and crash seal-time plugins.
+	 * @param {Module} module the rebuilt origin module
+	 * @param {Set} staleDependencies dependencies replaced by the rebuild
+	 * @returns {void}
+	 */
+	_removeStaleOutgoingConnections(module, staleDependencies) {
+		const mgm = this._getModuleGraphModule(module);
+		const outgoing = mgm.outgoingConnections;
+		if (outgoing === undefined) return;
+		for (const connection of outgoing) {
+			if (
+				!staleDependencies.has(
+					/** @type {Dependency} */ (connection.dependency)
+				)
+			) {
+				continue;
+			}
+			outgoing.delete(connection);
+			if (connection.module !== null) {
+				this._getModuleGraphModule(
+					connection.module
+				).incomingConnections.delete(connection);
+			}
+		}
+	}
+
+	/**
+	 * Assigns `module`'s pending outgoing connections into the dependency map by
+	 * origin. `getConnection` normally does this lazily keyed by a dependency's
+	 * PARENT module; an incremental barrel un-lazy wires a re-export target from
+	 * the importer's walk, so the export dep's parent differs from its origin
+	 * barrel and the lazy flush never reaches it. Flush by origin to fix that.
+	 * @param {Module} module the origin module
+	 * @returns {void}
+	 */
+	_flushUnassignedConnections(module) {
+		const mgm = this._getModuleGraphModule(module);
+		const pending = mgm._unassignedConnections;
+		if (pending === undefined || pending.length === 0) return;
+		for (const connection of pending) {
+			this._dependencyMap.set(
+				/** @type {Dependency} */ (connection.dependency),
+				connection
+			);
+		}
+		pending.length = 0;
+	}
+
+	/**
+	 * Adds the provided dependency to the module graph.
+	 * @param {Dependency} dependency the referencing dependency
+	 * @param {string} explanation an explanation
+	 * @returns {void}
+	 */
+	addExplanation(dependency, explanation) {
+		const connection =
+			/** @type {ModuleGraphConnection} */
+			(this.getConnection(dependency));
+		connection.addExplanation(explanation);
+	}
+
+	/**
+	 * Clones module attributes.
+	 * @param {Module} sourceModule the source module
+	 * @param {Module} targetModule the target module
+	 * @returns {void}
+	 */
+	cloneModuleAttributes(sourceModule, targetModule) {
+		const oldMgm = this._getModuleGraphModule(sourceModule);
+		const newMgm = this._getModuleGraphModule(targetModule);
+		newMgm.postOrderIndex = oldMgm.postOrderIndex;
+		newMgm.preOrderIndex = oldMgm.preOrderIndex;
+		newMgm.depth = oldMgm.depth;
+		newMgm.exports = oldMgm.exports;
+		newMgm.async = oldMgm.async;
+	}
+
+	/**
+	 * Removes module attributes.
+	 * @param {Module} module the module
+	 * @returns {void}
+	 */
+	removeModuleAttributes(module) {
+		const mgm = this._getModuleGraphModule(module);
+		mgm.postOrderIndex = null;
+		mgm.preOrderIndex = null;
+		mgm.depth = null;
+		mgm.async = false;
+	}
+
+	/**
+	 * Removes a module from the graph entirely, detaching its connections. Used to
+	 * drop stale runtime modules on an incremental rebuild so the persisted graph
+	 * doesn't accumulate them.
+	 * @param {Module} module the module to remove
+	 * @returns {void}
+	 */
+	_removeModule(module) {
+		const mgm = this._moduleMap.get(module);
+		if (mgm === undefined) return;
+		if (mgm.outgoingConnections !== undefined) {
+			for (const connection of mgm.outgoingConnections) {
+				if (connection.module && connection.module !== module) {
+					const other = this._moduleMap.get(connection.module);
+					if (other !== undefined) other.incomingConnections.delete(connection);
+				}
+			}
+		}
+		for (const connection of mgm.incomingConnections) {
+			if (connection.originModule && connection.originModule !== module) {
+				const other = this._moduleMap.get(connection.originModule);
+				if (other !== undefined && other.outgoingConnections !== undefined) {
+					other.outgoingConnections.delete(connection);
+				}
+			}
+		}
+		this._moduleMap.delete(module);
+	}
+
+	/**
+	 * Removes all module attributes.
+	 * @returns {void}
+	 */
+	removeAllModuleAttributes() {
+		for (const mgm of this._moduleMap.values()) {
+			mgm.postOrderIndex = null;
+			mgm.preOrderIndex = null;
+			mgm.depth = null;
+			mgm.async = false;
+		}
+	}
+
+	/**
+	 * Move module connections.
+	 * @param {Module} oldModule the old referencing module
+	 * @param {Module} newModule the new referencing module
+	 * @param {FilterConnection} filterConnection filter predicate for replacement
+	 * @returns {void}
+	 */
+	moveModuleConnections(oldModule, newModule, filterConnection) {
+		if (oldModule === newModule) return;
+		const oldMgm = this._getModuleGraphModule(oldModule);
+		const newMgm = this._getModuleGraphModule(newModule);
+		const journal = this._mutationJournal;
+		/** @type {ModuleGraphConnection[] | undefined} */
+		const movedOut = journal ? [] : undefined;
+		/** @type {ModuleGraphConnection[] | undefined} */
+		const movedIn = journal ? [] : undefined;
+		// Outgoing connections
+		const oldConnections = oldMgm.outgoingConnections;
+		if (oldConnections !== undefined) {
+			if (newMgm.outgoingConnections === undefined) {
+				newMgm.outgoingConnections = new SortableSet();
+			}
+			const newConnections = newMgm.outgoingConnections;
+			for (const connection of oldConnections) {
+				if (filterConnection(connection)) {
+					connection.originModule = newModule;
+					newConnections.add(connection);
+					oldConnections.delete(connection);
+					if (movedOut) movedOut.push(connection);
+				}
+			}
+		}
+		// Incoming connections
+		const oldConnections2 = oldMgm.incomingConnections;
+		const newConnections2 = newMgm.incomingConnections;
+		for (const connection of oldConnections2) {
+			if (filterConnection(connection)) {
+				connection.module = newModule;
+				newConnections2.add(connection);
+				oldConnections2.delete(connection);
+				if (movedIn) movedIn.push(connection);
+			}
+		}
+		if (journal) journal.push({ oldModule, newModule, movedOut, movedIn });
+	}
+
+	/**
+	 * Start recording connection moves so they can later be reversed for incremental rebuilds.
+	 * @returns {void}
+	 */
+	_startMutationJournal() {
+		this._mutationJournal = [];
+	}
+
+	/**
+	 * Reverse all recorded connection moves, restoring the graph to its pre-seal
+	 * (end-of-make) state, then stop recording (incremental rebuilds).
+	 * @returns {void}
+	 */
+	_restoreFromMutationJournal() {
+		const journal = this._mutationJournal;
+		if (journal === undefined) return;
+		for (let i = journal.length - 1; i >= 0; i--) {
+			const { oldModule, newModule, movedOut, movedIn, copied } = journal[i];
+			const newMgm = this._getModuleGraphModule(newModule);
+			// copyOutgoingModuleConnections clones connections onto newModule and the
+			// external targets; undo by deleting the clones (there is no oldModule)
+			if (copied !== undefined) {
+				for (const connection of copied) {
+					if (newMgm.outgoingConnections !== undefined) {
+						newMgm.outgoingConnections.delete(connection);
+					}
+					if (connection.module !== null && connection.module !== undefined) {
+						this._getModuleGraphModule(
+							connection.module
+						).incomingConnections.delete(connection);
+					}
+				}
+				continue;
+			}
+			const oldModuleT = /** @type {Module} */ (oldModule);
+			const oldMgm = this._getModuleGraphModule(oldModuleT);
+			if (movedOut !== undefined) {
+				if (oldMgm.outgoingConnections === undefined) {
+					oldMgm.outgoingConnections = new SortableSet();
+				}
+				for (const connection of movedOut) {
+					connection.originModule = oldModuleT;
+					oldMgm.outgoingConnections.add(connection);
+					if (newMgm.outgoingConnections !== undefined) {
+						newMgm.outgoingConnections.delete(connection);
+					}
+				}
+			}
+			if (movedIn !== undefined) {
+				for (const connection of movedIn) {
+					connection.module = oldModuleT;
+					oldMgm.incomingConnections.add(connection);
+					newMgm.incomingConnections.delete(connection);
+				}
+			}
+		}
+		this._mutationJournal = undefined;
+	}
+
+	/**
+	 * Copies outgoing module connections.
+	 * @param {Module} oldModule the old referencing module
+	 * @param {Module} newModule the new referencing module
+	 * @param {FilterConnection} filterConnection filter predicate for replacement
+	 * @returns {void}
+	 */
+	copyOutgoingModuleConnections(oldModule, newModule, filterConnection) {
+		if (oldModule === newModule) return;
+		const oldMgm = this._getModuleGraphModule(oldModule);
+		const newMgm = this._getModuleGraphModule(newModule);
+		const journal = this._mutationJournal;
+		/** @type {ModuleGraphConnection[] | undefined} */
+		const copied = journal ? [] : undefined;
+		// Outgoing connections
+		const oldConnections = oldMgm.outgoingConnections;
+		if (oldConnections !== undefined) {
+			if (newMgm.outgoingConnections === undefined) {
+				newMgm.outgoingConnections = new SortableSet();
+			}
+			const newConnections = newMgm.outgoingConnections;
+			for (const connection of oldConnections) {
+				if (filterConnection(connection)) {
+					const newConnection = connection.clone();
+					newConnection.originModule = newModule;
+					newConnections.add(newConnection);
+					if (newConnection.module !== undefined) {
+						const otherMgm = this._getModuleGraphModule(newConnection.module);
+						otherMgm.incomingConnections.add(newConnection);
+					}
+					if (copied) copied.push(newConnection);
+				}
+			}
+		}
+		if (journal && copied !== undefined && copied.length > 0) {
+			journal.push({ newModule, copied });
+		}
+	}
+
+	/**
+	 * Adds the provided module to the module graph.
+	 * @param {Module} module the referenced module
+	 * @param {string} explanation an explanation why it's referenced
+	 * @returns {void}
+	 */
+	addExtraReason(module, explanation) {
+		const connections = this._getModuleGraphModule(module).incomingConnections;
+		connections.add(new ModuleGraphConnection(null, null, module, explanation));
+	}
+
+	/**
+	 * Gets resolved module.
+	 * @param {Dependency} dependency the dependency to look for a referenced module
+	 * @returns {Module | null} the referenced module
+	 */
+	getResolvedModule(dependency) {
+		const connection = this.getConnection(dependency);
+		return connection !== undefined ? connection.resolvedModule : null;
+	}
+
+	/**
+	 * Returns the connection.
+	 * @param {Dependency} dependency the dependency to look for a referenced module
+	 * @returns {ModuleGraphConnection | undefined} the connection
+	 */
+	getConnection(dependency) {
+		const connection = this._dependencyMap.get(dependency);
+		if (connection === undefined) {
+			const module = this.getParentModule(dependency);
+			if (module !== undefined) {
+				const mgm = this._getModuleGraphModule(module);
+				if (
+					mgm._unassignedConnections &&
+					mgm._unassignedConnections.length !== 0
+				) {
+					/** @type {undefined | ModuleGraphConnection} */
+					let foundConnection;
+					for (const connection of mgm._unassignedConnections) {
+						this._dependencyMap.set(
+							/** @type {Dependency} */ (connection.dependency),
+							connection
+						);
+						if (connection.dependency === dependency) {
+							foundConnection = connection;
+						}
+					}
+					mgm._unassignedConnections.length = 0;
+					if (foundConnection !== undefined) {
+						return foundConnection;
+					}
+				}
+			}
+			this._dependencyMap.set(dependency, null);
+			return;
+		}
+		return connection === null ? undefined : connection;
+	}
+
+	/**
+	 * Returns the referenced module.
+	 * @param {Dependency} dependency the dependency to look for a referenced module
+	 * @returns {Module | null} the referenced module
+	 */
+	getModule(dependency) {
+		const connection = this.getConnection(dependency);
+		return connection !== undefined ? connection.module : null;
+	}
+
+	/**
+	 * Returns the referencing module.
+	 * @param {Dependency} dependency the dependency to look for a referencing module
+	 * @returns {Module | null} the referencing module
+	 */
+	getOrigin(dependency) {
+		const connection = this.getConnection(dependency);
+		return connection !== undefined ? connection.originModule : null;
+	}
+
+	/**
+	 * Gets resolved origin.
+	 * @param {Dependency} dependency the dependency to look for a referencing module
+	 * @returns {Module | null} the original referencing module
+	 */
+	getResolvedOrigin(dependency) {
+		const connection = this.getConnection(dependency);
+		return connection !== undefined ? connection.resolvedOriginModule : null;
+	}
+
+	/**
+	 * Gets incoming connections.
+	 * @param {Module} module the module
+	 * @returns {Iterable} reasons why a module is included
+	 */
+	getIncomingConnections(module) {
+		const connections = this._getModuleGraphModule(module).incomingConnections;
+		return connections;
+	}
+
+	/**
+	 * Gets outgoing connections.
+	 * @param {Module} module the module
+	 * @returns {Iterable} list of outgoing connections
+	 */
+	getOutgoingConnections(module) {
+		const connections = this._getModuleGraphModule(module).outgoingConnections;
+		return connections === undefined ? EMPTY_SET : connections;
+	}
+
+	/**
+	 * Gets incoming connections by origin module.
+	 * @param {Module} module the module
+	 * @returns {ReadonlyMap>} reasons why a module is included, in a map by source module
+	 */
+	getIncomingConnectionsByOriginModule(module) {
+		const connections = this._getModuleGraphModule(module).incomingConnections;
+		return connections.getFromUnorderedCache(getConnectionsByOriginModule);
+	}
+
+	/**
+	 * Gets outgoing connections by module.
+	 * @param {Module} module the module
+	 * @returns {ReadonlyMap> | undefined} connections to modules, in a map by module
+	 */
+	getOutgoingConnectionsByModule(module) {
+		const connections = this._getModuleGraphModule(module).outgoingConnections;
+		return connections === undefined
+			? undefined
+			: connections.getFromUnorderedCache(getConnectionsByModule);
+	}
+
+	/**
+	 * Returns the module profile.
+	 * @param {Module} module the module
+	 * @returns {ModuleProfile | undefined} the module profile
+	 */
+	getProfile(module) {
+		const mgm = this._getModuleGraphModule(module);
+		return mgm.profile;
+	}
+
+	/**
+	 * Updates profile using the provided module.
+	 * @param {Module} module the module
+	 * @param {ModuleProfile | undefined} profile the module profile
+	 * @returns {void}
+	 */
+	setProfile(module, profile) {
+		const mgm = this._getModuleGraphModule(module);
+		mgm.profile = profile;
+	}
+
+	/**
+	 * Returns the issuer module.
+	 * @param {Module} module the module
+	 * @returns {Issuer} the issuer module
+	 */
+	getIssuer(module) {
+		const mgm = this._getModuleGraphModule(module);
+		return mgm.issuer;
+	}
+
+	/**
+	 * Updates issuer using the provided module.
+	 * @param {Module} module the module
+	 * @param {Module | null} issuer the issuer module
+	 * @returns {void}
+	 */
+	setIssuer(module, issuer) {
+		const mgm = this._getModuleGraphModule(module);
+		mgm.issuer = issuer;
+	}
+
+	/**
+	 * Sets issuer if unset.
+	 * @param {Module} module the module
+	 * @param {Module | null} issuer the issuer module
+	 * @returns {void}
+	 */
+	setIssuerIfUnset(module, issuer) {
+		const mgm = this._getModuleGraphModule(module);
+		if (mgm.issuer === undefined) mgm.issuer = issuer;
+	}
+
+	/**
+	 * Gets optimization bailout.
+	 * @param {Module} module the module
+	 * @returns {OptimizationBailouts} optimization bailouts
+	 */
+	getOptimizationBailout(module) {
+		const mgm = this._getModuleGraphModule(module);
+		return mgm.optimizationBailout;
+	}
+
+	/**
+	 * Gets provided exports.
+	 * @param {Module} module the module
+	 * @returns {null | true | ExportInfoName[]} the provided exports
+	 */
+	getProvidedExports(module) {
+		const mgm = this._getModuleGraphModule(module);
+		return mgm.exports.getProvidedExports();
+	}
+
+	/**
+	 * Checks whether this module graph is export provided.
+	 * @param {Module} module the module
+	 * @param {ExportInfoName | ExportInfoName[]} exportName a name of an export
+	 * @returns {boolean | null} true, if the export is provided by the module.
+	 * null, if it's unknown.
+	 * false, if it's not provided.
+	 */
+	isExportProvided(module, exportName) {
+		const mgm = this._getModuleGraphModule(module);
+		const result = mgm.exports.isExportProvided(exportName);
+		return result === undefined ? null : result;
+	}
+
+	/**
+	 * Returns info about the exports.
+	 * @param {Module} module the module
+	 * @returns {ExportsInfo} info about the exports
+	 */
+	getExportsInfo(module) {
+		const mgm = this._getModuleGraphModule(module);
+		return mgm.exports;
+	}
+
+	/**
+	 * Returns info about the export.
+	 * @param {Module} module the module
+	 * @param {string} exportName the export
+	 * @returns {ExportInfo} info about the export
+	 */
+	getExportInfo(module, exportName) {
+		const mgm = this._getModuleGraphModule(module);
+		return mgm.exports.getExportInfo(exportName);
+	}
+
+	/**
+	 * Gets read only export info.
+	 * @param {Module} module the module
+	 * @param {string} exportName the export
+	 * @returns {ExportInfo} info about the export (do not modify)
+	 */
+	getReadOnlyExportInfo(module, exportName) {
+		const mgm = this._getModuleGraphModule(module);
+		return mgm.exports.getReadOnlyExportInfo(exportName);
+	}
+
+	/**
+	 * Returns the used exports.
+	 * @param {Module} module the module
+	 * @param {RuntimeSpec} runtime the runtime
+	 * @returns {false | true | SortableSet | null} the used exports
+	 * false: module is not used at all.
+	 * true: the module namespace/object export is used.
+	 * SortableSet: these export names are used.
+	 * empty SortableSet: module is used but no export.
+	 * null: unknown, worst case should be assumed.
+	 */
+	getUsedExports(module, runtime) {
+		const mgm = this._getModuleGraphModule(module);
+		return mgm.exports.getUsedExports(runtime);
+	}
+
+	/**
+	 * Gets pre order index.
+	 * @param {Module} module the module
+	 * @returns {number | null} the index of the module
+	 */
+	getPreOrderIndex(module) {
+		const mgm = this._getModuleGraphModule(module);
+		return mgm.preOrderIndex;
+	}
+
+	/**
+	 * Gets post order index.
+	 * @param {Module} module the module
+	 * @returns {number | null} the index of the module
+	 */
+	getPostOrderIndex(module) {
+		const mgm = this._getModuleGraphModule(module);
+		return mgm.postOrderIndex;
+	}
+
+	/**
+	 * Sets pre order index.
+	 * @param {Module} module the module
+	 * @param {number} index the index of the module
+	 * @returns {void}
+	 */
+	setPreOrderIndex(module, index) {
+		const mgm = this._getModuleGraphModule(module);
+		mgm.preOrderIndex = index;
+	}
+
+	/**
+	 * Sets pre order index if unset.
+	 * @param {Module} module the module
+	 * @param {number} index the index of the module
+	 * @returns {boolean} true, if the index was set
+	 */
+	setPreOrderIndexIfUnset(module, index) {
+		const mgm = this._getModuleGraphModule(module);
+		if (mgm.preOrderIndex === null) {
+			mgm.preOrderIndex = index;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Sets post order index.
+	 * @param {Module} module the module
+	 * @param {number} index the index of the module
+	 * @returns {void}
+	 */
+	setPostOrderIndex(module, index) {
+		const mgm = this._getModuleGraphModule(module);
+		mgm.postOrderIndex = index;
+	}
+
+	/**
+	 * Sets post order index if unset.
+	 * @param {Module} module the module
+	 * @param {number} index the index of the module
+	 * @returns {boolean} true, if the index was set
+	 */
+	setPostOrderIndexIfUnset(module, index) {
+		const mgm = this._getModuleGraphModule(module);
+		if (mgm.postOrderIndex === null) {
+			mgm.postOrderIndex = index;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Returns the depth of the module.
+	 * @param {Module} module the module
+	 * @returns {number | null} the depth of the module
+	 */
+	getDepth(module) {
+		const mgm = this._getModuleGraphModule(module);
+		return mgm.depth;
+	}
+
+	/**
+	 * Updates depth using the provided module.
+	 * @param {Module} module the module
+	 * @param {number} depth the depth of the module
+	 * @returns {void}
+	 */
+	setDepth(module, depth) {
+		const mgm = this._getModuleGraphModule(module);
+		mgm.depth = depth;
+	}
+
+	/**
+	 * Sets depth if lower.
+	 * @param {Module} module the module
+	 * @param {number} depth the depth of the module
+	 * @returns {boolean} true, if the depth was set
+	 */
+	setDepthIfLower(module, depth) {
+		const mgm = this._getModuleGraphModule(module);
+		if (mgm.depth === null || mgm.depth > depth) {
+			mgm.depth = depth;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Checks whether this module graph is async.
+	 * @param {Module} module the module
+	 * @returns {boolean} true, if the module is async
+	 */
+	isAsync(module) {
+		const mgm = this._getModuleGraphModule(module);
+		return mgm.async;
+	}
+
+	/**
+	 * Checks whether this module graph is deferred.
+	 * @param {Module} module the module
+	 * @returns {boolean} true, if the module is used as a deferred module at least once
+	 */
+	isDeferred(module) {
+		if (this.isAsync(module)) return false;
+		const connections = this.getIncomingConnections(module);
+		for (const connection of connections) {
+			if (
+				!connection.dependency ||
+				!(connection.dependency instanceof HarmonyImportDependency)
+			) {
+				continue;
+			}
+			if (ImportPhaseUtils.isDefer(connection.dependency.phase)) return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Updates async using the provided module.
+	 * @param {Module} module the module
+	 * @returns {void}
+	 */
+	setAsync(module) {
+		const mgm = this._getModuleGraphModule(module);
+		mgm.async = true;
+	}
+
+	/**
+	 * Returns metadata.
+	 * @param {MetaKey} thing any thing
+	 * @returns {Meta} metadata
+	 */
+	getMeta(thing) {
+		let meta = this._metaMap.get(thing);
+		if (meta === undefined) {
+			meta = /** @type {Meta} */ (Object.create(null));
+			this._metaMap.set(thing, meta);
+		}
+		return meta;
+	}
+
+	/**
+	 * Gets meta if existing.
+	 * @param {MetaKey} thing any thing
+	 * @returns {Meta | undefined} metadata
+	 */
+	getMetaIfExisting(thing) {
+		return this._metaMap.get(thing);
+	}
+
+	/**
+	 * Processes the provided cache stage.
+	 * @param {string=} cacheStage a persistent stage name for caching
+	 */
+	freeze(cacheStage) {
+		this._cache = new WeakTupleMap();
+		this._cacheStage = cacheStage;
+	}
+
+	unfreeze() {
+		this._cache = undefined;
+		this._cacheStage = undefined;
+	}
+
+	/**
+	 * Returns computed value or cached.
+	 * @template {EXPECTED_ANY[]} T
+	 * @template R
+	 * @param {(moduleGraph: ModuleGraph, ...args: T) => R} fn computer
+	 * @param {T} args arguments
+	 * @returns {R} computed value or cached
+	 */
+	cached(fn, ...args) {
+		if (this._cache === undefined) return fn(this, ...args);
+		return this._cache.cachedProvide(fn, this, args);
+	}
+
+	/**
+	 * Sets module mem caches.
+	 * @param {ModuleMemCaches} moduleMemCaches mem caches for modules for better caching
+	 */
+	setModuleMemCaches(moduleMemCaches) {
+		this._moduleMemCaches = moduleMemCaches;
+	}
+
+	/**
+	 * Dependency cache provide.
+	 * @template {Dependency} D
+	 * @template {EXPECTED_ANY[]} ARGS
+	 * @template R
+	 * @param {D} dependency dependency
+	 * @param {[...ARGS, (moduleGraph: ModuleGraph, dependency: D, ...args: ARGS) => R]} args arguments, last argument is a function called with moduleGraph, dependency, ...args
+	 * @returns {R} computed value or cached
+	 */
+	dependencyCacheProvide(dependency, ...args) {
+		const fn =
+			/** @type {(moduleGraph: ModuleGraph, dependency: D, ...args: EXPECTED_ANY[]) => R} */
+			(args.pop());
+		if (this._moduleMemCaches && this._cacheStage) {
+			const memCache = this._moduleMemCaches.get(
+				/** @type {Module} */
+				(this.getParentModule(dependency))
+			);
+			if (memCache !== undefined) {
+				return memCache.provide(dependency, this._cacheStage, ...args, () =>
+					fn(this, dependency, ...args)
+				);
+			}
+		}
+		if (this._cache === undefined) return fn(this, dependency, ...args);
+		return this._cache.provide(dependency, ...args, () =>
+			fn(this, dependency, ...args)
+		);
+	}
+
+	// TODO remove in webpack 6
+	/**
+	 * Gets module graph for module.
+	 * @deprecated
+	 * @param {Module} module the module
+	 * @param {string} deprecateMessage message for the deprecation message
+	 * @param {string} deprecationCode code for the deprecation
+	 * @returns {ModuleGraph} the module graph
+	 */
+	static getModuleGraphForModule(module, deprecateMessage, deprecationCode) {
+		const fn = deprecateMap.get(deprecateMessage);
+		if (fn) return fn(module);
+		const newFn = util.deprecate(
+			/**
+			 * Handles the callback logic for this hook.
+			 * @param {Module} module the module
+			 * @returns {ModuleGraph} the module graph
+			 */
+			(module) => {
+				const moduleGraph = moduleGraphForModuleMap.get(module);
+				if (!moduleGraph) {
+					throw new Error(
+						`${deprecateMessage}There was no ModuleGraph assigned to the Module for backward-compat (Use the new API)`
+					);
+				}
+				return moduleGraph;
+			},
+			`${deprecateMessage}: Use new ModuleGraph API`,
+			deprecationCode
+		);
+		deprecateMap.set(deprecateMessage, newFn);
+		return newFn(module);
+	}
+
+	// TODO remove in webpack 6
+	/**
+	 * Sets module graph for module.
+	 * @deprecated
+	 * @param {Module} module the module
+	 * @param {ModuleGraph} moduleGraph the module graph
+	 * @returns {void}
+	 */
+	static setModuleGraphForModule(module, moduleGraph) {
+		moduleGraphForModuleMap.set(module, moduleGraph);
+	}
+
+	// TODO remove in webpack 6
+	/**
+	 * Clear module graph for module.
+	 * @deprecated
+	 * @param {Module} module the module
+	 * @returns {void}
+	 */
+	static clearModuleGraphForModule(module) {
+		moduleGraphForModuleMap.delete(module);
+	}
+}
+
+// TODO remove in webpack 6
+/** @type {WeakMap} */
+const moduleGraphForModuleMap = new WeakMap();
+
+// TODO remove in webpack 6
+/** @type {Map ModuleGraph>} */
+const deprecateMap = new Map();
+
+module.exports = ModuleGraph;
+module.exports.ModuleGraphConnection = ModuleGraphConnection;
diff --git a/lib/ModuleGraphConnection.js b/lib/ModuleGraphConnection.js
new file mode 100644
index 00000000000..d6a7e95b8f0
--- /dev/null
+++ b/lib/ModuleGraphConnection.js
@@ -0,0 +1,208 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+/** @typedef {import("./Dependency")} Dependency */
+/** @typedef {import("./Dependency").GetConditionFn} GetConditionFn */
+/** @typedef {import("./Module")} Module */
+/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */
+
+/**
+ * Module itself is not connected, but transitive modules are connected transitively.
+ */
+const TRANSITIVE_ONLY = Symbol("transitive only");
+
+/**
+ * While determining the active state, this flag is used to signal a circular connection.
+ */
+const CIRCULAR_CONNECTION = Symbol("circular connection");
+
+/** @typedef {boolean | typeof TRANSITIVE_ONLY | typeof CIRCULAR_CONNECTION} ConnectionState */
+
+/**
+ * Adds connection states.
+ * @param {ConnectionState} a first
+ * @param {ConnectionState} b second
+ * @returns {ConnectionState} merged
+ */
+const addConnectionStates = (a, b) => {
+	if (a === true || b === true) return true;
+	if (a === false) return b;
+	if (b === false) return a;
+	if (a === TRANSITIVE_ONLY) return b;
+	if (b === TRANSITIVE_ONLY) return a;
+	return a;
+};
+
+/**
+ * Intersect connection states.
+ * @param {ConnectionState} a first
+ * @param {ConnectionState} b second
+ * @returns {ConnectionState} intersected
+ */
+const intersectConnectionStates = (a, b) => {
+	if (a === false || b === false) return false;
+	if (a === true) return b;
+	if (b === true) return a;
+	if (a === CIRCULAR_CONNECTION) return b;
+	if (b === CIRCULAR_CONNECTION) return a;
+	return a;
+};
+
+class ModuleGraphConnection {
+	/**
+	 * Creates an instance of ModuleGraphConnection.
+	 * @param {Module | null} originModule the referencing module
+	 * @param {Dependency | null} dependency the referencing dependency
+	 * @param {Module} module the referenced module
+	 * @param {string=} explanation some extra detail
+	 * @param {boolean=} weak the reference is weak
+	 * @param {false | null | GetConditionFn=} condition condition for the connection
+	 */
+	constructor(
+		originModule,
+		dependency,
+		module,
+		explanation,
+		weak = false,
+		condition = undefined
+	) {
+		/** @type {Module | null} */
+		this.originModule = originModule;
+		/** @type {Module | null} */
+		this.resolvedOriginModule = originModule;
+		/** @type {Dependency | null} */
+		this.dependency = dependency;
+		/** @type {Module} */
+		this.resolvedModule = module;
+		/** @type {Module} */
+		this.module = module;
+		/** @type {boolean | undefined} */
+		this.weak = weak;
+		/** @type {boolean} */
+		this.conditional = Boolean(condition);
+		/** @type {boolean} */
+		this._active = condition !== false;
+		/** @type {false | null | GetConditionFn | undefined} */
+		this.condition = condition || undefined;
+		/** @type {Set | undefined} */
+		this.explanations = undefined;
+		if (explanation) {
+			this.explanations = new Set();
+			this.explanations.add(explanation);
+		}
+	}
+
+	clone() {
+		const clone = new ModuleGraphConnection(
+			this.resolvedOriginModule,
+			this.dependency,
+			this.resolvedModule,
+			undefined,
+			this.weak,
+			this.condition
+		);
+		clone.originModule = this.originModule;
+		clone.module = this.module;
+		clone.conditional = this.conditional;
+		clone._active = this._active;
+		if (this.explanations) clone.explanations = new Set(this.explanations);
+		return clone;
+	}
+
+	/**
+	 * Adds the provided condition to the module graph connection.
+	 * @param {GetConditionFn} condition condition for the connection
+	 * @returns {void}
+	 */
+	addCondition(condition) {
+		if (this.conditional) {
+			const old =
+				/** @type {GetConditionFn} */
+				(this.condition);
+			/** @type {GetConditionFn} */
+			(this.condition) = (c, r) =>
+				intersectConnectionStates(old(c, r), condition(c, r));
+		} else if (this._active) {
+			this.conditional = true;
+			this.condition = condition;
+		}
+	}
+
+	/**
+	 * Adds the provided explanation to the module graph connection.
+	 * @param {string} explanation the explanation to add
+	 * @returns {void}
+	 */
+	addExplanation(explanation) {
+		if (this.explanations === undefined) {
+			this.explanations = new Set();
+		}
+		this.explanations.add(explanation);
+	}
+
+	get explanation() {
+		if (this.explanations === undefined) return "";
+		return [...this.explanations].join(" ");
+	}
+
+	/**
+	 * Checks whether this module graph connection is active.
+	 * @param {RuntimeSpec} runtime the runtime
+	 * @returns {boolean} true, if the connection is active
+	 */
+	isActive(runtime) {
+		if (!this.conditional) return this._active;
+
+		return (
+			/** @type {GetConditionFn} */ (this.condition)(this, runtime) !== false
+		);
+	}
+
+	/**
+	 * Checks whether this module graph connection is target active.
+	 * @param {RuntimeSpec} runtime the runtime
+	 * @returns {boolean} true, if the connection is active
+	 */
+	isTargetActive(runtime) {
+		if (!this.conditional) return this._active;
+		return (
+			/** @type {GetConditionFn} */ (this.condition)(this, runtime) === true
+		);
+	}
+
+	/**
+	 * Returns true: fully active, false: inactive, TRANSITIVE: direct module inactive, but transitive connection maybe active.
+	 * @param {RuntimeSpec} runtime the runtime
+	 * @returns {ConnectionState} true: fully active, false: inactive, TRANSITIVE: direct module inactive, but transitive connection maybe active
+	 */
+	getActiveState(runtime) {
+		if (!this.conditional) return this._active;
+		return /** @type {GetConditionFn} */ (this.condition)(this, runtime);
+	}
+
+	/**
+	 * Updates active using the provided value.
+	 * @param {boolean} value active or not
+	 * @returns {void}
+	 */
+	setActive(value) {
+		this.conditional = false;
+		this._active = value;
+	}
+}
+
+/** @typedef {typeof TRANSITIVE_ONLY} TRANSITIVE_ONLY */
+/** @typedef {typeof CIRCULAR_CONNECTION} CIRCULAR_CONNECTION */
+
+module.exports = ModuleGraphConnection;
+module.exports.CIRCULAR_CONNECTION = /** @type {typeof CIRCULAR_CONNECTION} */ (
+	CIRCULAR_CONNECTION
+);
+module.exports.TRANSITIVE_ONLY = /** @type {typeof TRANSITIVE_ONLY} */ (
+	TRANSITIVE_ONLY
+);
+module.exports.addConnectionStates = addConnectionStates;
diff --git a/lib/ModuleInfoHeaderPlugin.js b/lib/ModuleInfoHeaderPlugin.js
new file mode 100644
index 00000000000..9e338eeb4d4
--- /dev/null
+++ b/lib/ModuleInfoHeaderPlugin.js
@@ -0,0 +1,323 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const { CachedSource, ConcatSource, RawSource } = require("webpack-sources");
+const { UsageState } = require("./ExportsInfo");
+const Template = require("./Template");
+const CssModulesPlugin = require("./css/CssModulesPlugin");
+const JavascriptModulesPlugin = require("./javascript/JavascriptModulesPlugin");
+
+/** @typedef {import("webpack-sources").Source} Source */
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./ExportsInfo")} ExportsInfo */
+/** @typedef {import("./ExportsInfo").ExportInfo} ExportInfo */
+/** @typedef {import("./Module")} Module */
+/** @typedef {import("./Module").BuildMeta} BuildMeta */
+/** @typedef {import("./ModuleGraph")} ModuleGraph */
+/** @typedef {import("./RequestShortener")} RequestShortener */
+
+/**
+ * Join iterable with comma.
+ * @template T
+ * @param {Iterable} iterable iterable
+ * @returns {string} joined with comma
+ */
+const joinIterableWithComma = (iterable) => {
+	// This is more performant than Array.from().join(", ")
+	// as it doesn't create an array
+	let str = "";
+	let first = true;
+	for (const item of iterable) {
+		if (first) {
+			first = false;
+		} else {
+			str += ", ";
+		}
+		str += item;
+	}
+	return str;
+};
+
+/**
+ * Print exports info to source.
+ * @param {ConcatSource} source output
+ * @param {string} indent spacing
+ * @param {ExportsInfo} exportsInfo data
+ * @param {ModuleGraph} moduleGraph moduleGraph
+ * @param {RequestShortener} requestShortener requestShortener
+ * @param {Set} alreadyPrinted deduplication set
+ * @returns {void}
+ */
+const printExportsInfoToSource = (
+	source,
+	indent,
+	exportsInfo,
+	moduleGraph,
+	requestShortener,
+	alreadyPrinted = new Set()
+) => {
+	const otherExportsInfo = exportsInfo.otherExportsInfo;
+
+	let alreadyPrintedExports = 0;
+
+	// determine exports to print
+	/** @type {ExportInfo[]} */
+	const printedExports = [];
+	for (const exportInfo of exportsInfo.orderedExports) {
+		if (!alreadyPrinted.has(exportInfo)) {
+			alreadyPrinted.add(exportInfo);
+			printedExports.push(exportInfo);
+		} else {
+			alreadyPrintedExports++;
+		}
+	}
+	let showOtherExports = false;
+	if (!alreadyPrinted.has(otherExportsInfo)) {
+		alreadyPrinted.add(otherExportsInfo);
+		showOtherExports = true;
+	} else {
+		alreadyPrintedExports++;
+	}
+
+	// print the exports
+	for (const exportInfo of printedExports) {
+		const target = exportInfo.getTarget(moduleGraph);
+		source.add(
+			`${Template.toComment(
+				`${indent}export ${JSON.stringify(exportInfo.name).slice(
+					1,
+					-1
+				)} [${exportInfo.getProvidedInfo()}] [${exportInfo.getUsedInfo()}] [${exportInfo.getRenameInfo()}]${
+					target
+						? ` -> ${target.module.readableIdentifier(requestShortener)}${
+								target.export
+									? ` .${target.export
+											.map((e) => JSON.stringify(e).slice(1, -1))
+											.join(".")}`
+									: ""
+							}`
+						: ""
+				}`
+			)}\n`
+		);
+		if (exportInfo.exportsInfo) {
+			printExportsInfoToSource(
+				source,
+				`${indent}  `,
+				exportInfo.exportsInfo,
+				moduleGraph,
+				requestShortener,
+				alreadyPrinted
+			);
+		}
+	}
+
+	if (alreadyPrintedExports) {
+		source.add(
+			`${Template.toComment(
+				`${indent}... (${alreadyPrintedExports} already listed exports)`
+			)}\n`
+		);
+	}
+
+	if (showOtherExports) {
+		const target = otherExportsInfo.getTarget(moduleGraph);
+		if (
+			target ||
+			otherExportsInfo.provided !== false ||
+			otherExportsInfo.getUsed(undefined) !== UsageState.Unused
+		) {
+			const title =
+				printedExports.length > 0 || alreadyPrintedExports > 0
+					? "other exports"
+					: "exports";
+			source.add(
+				`${Template.toComment(
+					`${indent}${title} [${otherExportsInfo.getProvidedInfo()}] [${otherExportsInfo.getUsedInfo()}]${
+						target
+							? ` -> ${target.module.readableIdentifier(requestShortener)}`
+							: ""
+					}`
+				)}\n`
+			);
+		}
+	}
+};
+
+/** @typedef {{ header: RawSource | undefined, full: WeakMap }} CacheEntry */
+/** @type {WeakMap>} */
+const caches = new WeakMap();
+
+const PLUGIN_NAME = "ModuleInfoHeaderPlugin";
+
+class ModuleInfoHeaderPlugin {
+	/**
+	 * Creates an instance of ModuleInfoHeaderPlugin.
+	 * @param {boolean=} verbose add more information like exports, runtime requirements and bailouts
+	 */
+	constructor(verbose = true) {
+		/** @type {boolean} */
+		this._verbose = verbose;
+	}
+
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler
+	 * @returns {void}
+	 */
+	apply(compiler) {
+		const { _verbose: verbose } = this;
+		compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
+			const javascriptHooks =
+				JavascriptModulesPlugin.getCompilationHooks(compilation);
+			javascriptHooks.renderModulePackage.tap(
+				PLUGIN_NAME,
+				(
+					moduleSource,
+					module,
+					{ chunk, chunkGraph, moduleGraph, runtimeTemplate }
+				) => {
+					const { requestShortener } = runtimeTemplate;
+					/** @type {undefined | CacheEntry} */
+					let cacheEntry;
+					let cache = caches.get(requestShortener);
+					if (cache === undefined) {
+						caches.set(requestShortener, (cache = new WeakMap()));
+						cache.set(
+							module,
+							(cacheEntry = { header: undefined, full: new WeakMap() })
+						);
+					} else {
+						cacheEntry = cache.get(module);
+						if (cacheEntry === undefined) {
+							cache.set(
+								module,
+								(cacheEntry = { header: undefined, full: new WeakMap() })
+							);
+						} else if (!verbose) {
+							const cachedSource = cacheEntry.full.get(moduleSource);
+							if (cachedSource !== undefined) return cachedSource;
+						}
+					}
+					const source = new ConcatSource();
+					let header = cacheEntry.header;
+					if (header === undefined) {
+						header = this.generateHeader(module, requestShortener);
+						cacheEntry.header = header;
+					}
+					source.add(header);
+					if (verbose) {
+						const exportsType = /** @type {BuildMeta} */ (module.buildMeta)
+							.exportsType;
+						source.add(
+							`${Template.toComment(
+								exportsType
+									? `${exportsType} exports`
+									: "unknown exports (runtime-defined)"
+							)}\n`
+						);
+						if (exportsType) {
+							const exportsInfo = moduleGraph.getExportsInfo(module);
+							printExportsInfoToSource(
+								source,
+								"",
+								exportsInfo,
+								moduleGraph,
+								requestShortener
+							);
+						}
+						source.add(
+							`${Template.toComment(
+								`runtime requirements: ${joinIterableWithComma(
+									chunkGraph.getModuleRuntimeRequirements(module, chunk.runtime)
+								)}`
+							)}\n`
+						);
+						const optimizationBailout =
+							moduleGraph.getOptimizationBailout(module);
+						if (optimizationBailout) {
+							for (const text of optimizationBailout) {
+								const code =
+									typeof text === "function" ? text(requestShortener) : text;
+								source.add(`${Template.toComment(`${code}`)}\n`);
+							}
+						}
+						source.add(moduleSource);
+						return source;
+					}
+					source.add(moduleSource);
+					const cachedSource = new CachedSource(source);
+					cacheEntry.full.set(moduleSource, cachedSource);
+					return cachedSource;
+				}
+			);
+			javascriptHooks.chunkHash.tap(PLUGIN_NAME, (_chunk, hash) => {
+				hash.update(PLUGIN_NAME);
+				hash.update("1");
+			});
+			const cssHooks = CssModulesPlugin.getCompilationHooks(compilation);
+			cssHooks.renderModulePackage.tap(
+				PLUGIN_NAME,
+				(moduleSource, module, { runtimeTemplate }) => {
+					const { requestShortener } = runtimeTemplate;
+					/** @type {undefined | CacheEntry} */
+					let cacheEntry;
+					let cache = caches.get(requestShortener);
+					if (cache === undefined) {
+						caches.set(requestShortener, (cache = new WeakMap()));
+						cache.set(
+							module,
+							(cacheEntry = { header: undefined, full: new WeakMap() })
+						);
+					} else {
+						cacheEntry = cache.get(module);
+						if (cacheEntry === undefined) {
+							cache.set(
+								module,
+								(cacheEntry = { header: undefined, full: new WeakMap() })
+							);
+						} else if (!verbose) {
+							const cachedSource = cacheEntry.full.get(moduleSource);
+							if (cachedSource !== undefined) return cachedSource;
+						}
+					}
+					const source = new ConcatSource();
+					let header = cacheEntry.header;
+					if (header === undefined) {
+						header = this.generateHeader(module, requestShortener);
+						cacheEntry.header = header;
+					}
+					source.add(header);
+					source.add(moduleSource);
+					const cachedSource = new CachedSource(source);
+					cacheEntry.full.set(moduleSource, cachedSource);
+					return cachedSource;
+				}
+			);
+			cssHooks.chunkHash.tap(PLUGIN_NAME, (_chunk, hash) => {
+				hash.update(PLUGIN_NAME);
+				hash.update("1");
+			});
+		});
+	}
+
+	/**
+	 * Returns the header.
+	 * @param {Module} module the module
+	 * @param {RequestShortener} requestShortener request shortener
+	 * @returns {RawSource} the header
+	 */
+	generateHeader(module, requestShortener) {
+		const req = module.readableIdentifier(requestShortener);
+		const reqStr = req.replace(/\*\//g, "*_/");
+		const reqStrStar = "*".repeat(reqStr.length);
+		const headerStr = `/*!****${reqStrStar}****!*\\\n  !*** ${reqStr} ***!\n  \\****${reqStrStar}****/\n`;
+		return new RawSource(headerStr);
+	}
+}
+
+module.exports = ModuleInfoHeaderPlugin;
diff --git a/lib/ModuleNotFoundError.js b/lib/ModuleNotFoundError.js
index cdfc3147b16..0403f1cd029 100644
--- a/lib/ModuleNotFoundError.js
+++ b/lib/ModuleNotFoundError.js
@@ -2,22 +2,9 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
-"use strict";
-
-const WebpackError = require("./WebpackError");
-
-class ModuleNotFoundError extends WebpackError {
-	constructor(module, err) {
-		super("Module not found: " + err);
 
-		this.name = "ModuleNotFoundError";
-		this.details = err.details;
-		this.missing = err.missing;
-		this.module = module;
-		this.error = err;
-
-		Error.captureStackTrace(this, this.constructor);
-	}
-}
+"use strict";
 
-module.exports = ModuleNotFoundError;
+// TODO remove in webpack 6
+// Some old plugins use `require("webpack/lib/ModuleNotFoundError")`, in webpack@6 developer should migrate to `compiler.webpack.ModuleNotFoundError`
+module.exports = require("./errors/ModuleNotFoundError");
diff --git a/lib/ModuleParseError.js b/lib/ModuleParseError.js
deleted file mode 100644
index 32c7a69c116..00000000000
--- a/lib/ModuleParseError.js
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const WebpackError = require("./WebpackError");
-
-/** @typedef {import("./Module")} Module */
-
-class ModuleParseError extends WebpackError {
-	/**
-	 * @param {Module} module the errored module
-	 * @param {string} source source code
-	 * @param {Error&any} err the parse error
-	 */
-	constructor(module, source, err) {
-		let message = "Module parse failed: " + err.message;
-		let loc = undefined;
-		message += "\nYou may need an appropriate loader to handle this file type.";
-		if (
-			err.loc &&
-			typeof err.loc === "object" &&
-			typeof err.loc.line === "number"
-		) {
-			var lineNumber = err.loc.line;
-			if (/[\0\u0001\u0002\u0003\u0004\u0005\u0006\u0007]/.test(source)) {
-				// binary file
-				message += "\n(Source code omitted for this binary file)";
-			} else {
-				const sourceLines = source.split("\n");
-				const start = Math.max(0, lineNumber - 3);
-				const linesBefore = sourceLines.slice(start, lineNumber - 1);
-				const theLine = sourceLines[lineNumber - 1];
-				const linesAfter = sourceLines.slice(lineNumber, lineNumber + 2);
-				message +=
-					linesBefore.map(l => `\n| ${l}`).join("") +
-					`\n> ${theLine}` +
-					linesAfter.map(l => `\n| ${l}`).join("");
-			}
-			loc = err.loc;
-		} else {
-			message += "\n" + err.stack;
-		}
-
-		super(message);
-
-		this.name = "ModuleParseError";
-		this.module = module;
-		this.loc = loc;
-		this.error = err;
-
-		Error.captureStackTrace(this, this.constructor);
-	}
-}
-
-module.exports = ModuleParseError;
diff --git a/lib/ModuleProfile.js b/lib/ModuleProfile.js
new file mode 100644
index 00000000000..93dc4426809
--- /dev/null
+++ b/lib/ModuleProfile.js
@@ -0,0 +1,134 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+class ModuleProfile {
+	constructor() {
+		/** @type {number} */
+		this.startTime = Date.now();
+
+		/** @type {number} */
+		this.factoryStartTime = 0;
+		/** @type {number} */
+		this.factoryEndTime = 0;
+		/** @type {number} */
+		this.factory = 0;
+		/** @type {number} */
+		this.factoryParallelismFactor = 0;
+
+		/** @type {number} */
+		this.restoringStartTime = 0;
+		/** @type {number} */
+		this.restoringEndTime = 0;
+		/** @type {number} */
+		this.restoring = 0;
+		/** @type {number} */
+		this.restoringParallelismFactor = 0;
+
+		/** @type {number} */
+		this.integrationStartTime = 0;
+		/** @type {number} */
+		this.integrationEndTime = 0;
+		/** @type {number} */
+		this.integration = 0;
+		/** @type {number} */
+		this.integrationParallelismFactor = 0;
+
+		/** @type {number} */
+		this.buildingStartTime = 0;
+		/** @type {number} */
+		this.buildingEndTime = 0;
+		/** @type {number} */
+		this.building = 0;
+		/** @type {number} */
+		this.buildingParallelismFactor = 0;
+
+		/** @type {number} */
+		this.storingStartTime = 0;
+		/** @type {number} */
+		this.storingEndTime = 0;
+		/** @type {number} */
+		this.storing = 0;
+		/** @type {number} */
+		this.storingParallelismFactor = 0;
+
+		/** @type {{ start: number, end: number }[] | undefined} */
+		this.additionalFactoryTimes = undefined;
+		/** @type {number} */
+		this.additionalFactories = 0;
+		/** @type {number} */
+		this.additionalFactoriesParallelismFactor = 0;
+
+		/**
+		 * @deprecated
+		 * @type {number}
+		 */
+		this.additionalIntegration = 0;
+	}
+
+	markFactoryStart() {
+		this.factoryStartTime = Date.now();
+	}
+
+	markFactoryEnd() {
+		this.factoryEndTime = Date.now();
+		this.factory = this.factoryEndTime - this.factoryStartTime;
+	}
+
+	markRestoringStart() {
+		this.restoringStartTime = Date.now();
+	}
+
+	markRestoringEnd() {
+		this.restoringEndTime = Date.now();
+		this.restoring = this.restoringEndTime - this.restoringStartTime;
+	}
+
+	markIntegrationStart() {
+		this.integrationStartTime = Date.now();
+	}
+
+	markIntegrationEnd() {
+		this.integrationEndTime = Date.now();
+		this.integration = this.integrationEndTime - this.integrationStartTime;
+	}
+
+	markBuildingStart() {
+		this.buildingStartTime = Date.now();
+	}
+
+	markBuildingEnd() {
+		this.buildingEndTime = Date.now();
+		this.building = this.buildingEndTime - this.buildingStartTime;
+	}
+
+	markStoringStart() {
+		this.storingStartTime = Date.now();
+	}
+
+	markStoringEnd() {
+		this.storingEndTime = Date.now();
+		this.storing = this.storingEndTime - this.storingStartTime;
+	}
+
+	// This depends on timing so we ignore it for coverage
+	/* istanbul ignore next */
+	/**
+	 * Merge this profile into another one
+	 * @param {ModuleProfile} realProfile the profile to merge into
+	 * @returns {void}
+	 */
+	mergeInto(realProfile) {
+		realProfile.additionalFactories = this.factory;
+		(realProfile.additionalFactoryTimes =
+			realProfile.additionalFactoryTimes || []).push({
+			start: this.factoryStartTime,
+			end: this.factoryEndTime
+		});
+	}
+}
+
+module.exports = ModuleProfile;
diff --git a/lib/ModuleReason.js b/lib/ModuleReason.js
deleted file mode 100644
index 239584b6c1e..00000000000
--- a/lib/ModuleReason.js
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-class ModuleReason {
-	constructor(module, dependency, explanation) {
-		this.module = module;
-		this.dependency = dependency;
-		this.explanation = explanation;
-		this._chunks = null;
-	}
-
-	hasChunk(chunk) {
-		if (this._chunks) {
-			if (this._chunks.has(chunk)) return true;
-		} else if (this.module && this.module._chunks.has(chunk)) return true;
-		return false;
-	}
-
-	rewriteChunks(oldChunk, newChunks) {
-		if (!this._chunks) {
-			if (this.module) {
-				if (!this.module._chunks.has(oldChunk)) return;
-				this._chunks = new Set(this.module._chunks);
-			} else {
-				this._chunks = new Set();
-			}
-		}
-		if (this._chunks.has(oldChunk)) {
-			this._chunks.delete(oldChunk);
-			for (let i = 0; i < newChunks.length; i++) {
-				this._chunks.add(newChunks[i]);
-			}
-		}
-	}
-}
-
-module.exports = ModuleReason;
diff --git a/lib/ModuleSourceTypeConstants.js b/lib/ModuleSourceTypeConstants.js
new file mode 100644
index 00000000000..4b842d04d42
--- /dev/null
+++ b/lib/ModuleSourceTypeConstants.js
@@ -0,0 +1,212 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Alexander Akait @alexander-akait
+*/
+
+"use strict";
+
+/**
+ * @type {Readonly<"javascript">}
+ */
+const JAVASCRIPT_TYPE = "javascript";
+
+/**
+ * @type {Readonly<"runtime">}
+ */
+const RUNTIME_TYPE = "runtime";
+
+/**
+ * @type {Readonly<"webassembly">}
+ */
+const WEBASSEMBLY_TYPE = "webassembly";
+
+/**
+ * @type {Readonly<"asset">}
+ */
+const ASSET_TYPE = "asset";
+
+/**
+ * @type {Readonly<"asset-url">}
+ */
+const ASSET_URL_TYPE = "asset-url";
+
+/**
+ * @type {Readonly<"css">}
+ */
+const CSS_TYPE = "css";
+
+/**
+ * @type {Readonly<"css-import">}
+ */
+const CSS_IMPORT_TYPE = "css-import";
+
+/**
+ * @type {Readonly<"css-text">}
+ */
+const CSS_TEXT_TYPE = "css-text";
+
+/**
+ * @type {Readonly<"html">}
+ */
+const HTML_TYPE = "html";
+
+/**
+ * @type {Readonly<"share-init">}
+ */
+const SHARED_INIT_TYPE = "share-init";
+
+/**
+ * @type {Readonly<"remote">}
+ */
+const REMOTE_GENERATOR_TYPE = "remote";
+
+/**
+ * @type {Readonly<"consume-shared">}
+ */
+const CONSUME_SHARED_GENERATOR_TYPE = "consume-shared";
+
+/**
+ * @type {Readonly<"unknown">}
+ */
+const UNKNOWN_TYPE = "unknown";
+
+/**
+ * Defines the all types type used by this module.
+ * @typedef {JAVASCRIPT_TYPE | RUNTIME_TYPE | WEBASSEMBLY_TYPE | ASSET_TYPE | ASSET_URL_TYPE | CSS_TYPE | CSS_IMPORT_TYPE | CSS_TEXT_TYPE | HTML_TYPE | SHARED_INIT_TYPE | REMOTE_GENERATOR_TYPE | CONSUME_SHARED_GENERATOR_TYPE | UNKNOWN_TYPE} AllTypes
+ */
+
+/**
+ * @type {ReadonlySet}
+ */
+const NO_TYPES = new Set();
+
+/**
+ * @type {ReadonlySet<"asset">}
+ */
+const ASSET_TYPES = new Set([ASSET_TYPE]);
+
+/**
+ * @type {ReadonlySet<"asset" | "javascript" | "asset">}
+ */
+const ASSET_AND_JAVASCRIPT_TYPES = new Set([ASSET_TYPE, JAVASCRIPT_TYPE]);
+
+/**
+ * @type {ReadonlySet<"asset-url" | "asset">}
+ */
+const ASSET_AND_ASSET_URL_TYPES = new Set([ASSET_TYPE, ASSET_URL_TYPE]);
+
+/**
+ * @type {ReadonlySet<"javascript" | "asset-url" | "asset">}
+ */
+const ASSET_AND_JAVASCRIPT_AND_ASSET_URL_TYPES = new Set([
+	ASSET_TYPE,
+	JAVASCRIPT_TYPE,
+	ASSET_URL_TYPE
+]);
+
+/**
+ * @type {ReadonlySet<"javascript">}
+ */
+const JAVASCRIPT_TYPES = new Set([JAVASCRIPT_TYPE]);
+
+/**
+ * @type {ReadonlySet<"javascript" | "asset-url">}
+ */
+const JAVASCRIPT_AND_ASSET_URL_TYPES = new Set([
+	JAVASCRIPT_TYPE,
+	ASSET_URL_TYPE
+]);
+
+/**
+ * @type {ReadonlySet<"javascript" | "css">}
+ */
+const JAVASCRIPT_AND_CSS_TYPES = new Set([JAVASCRIPT_TYPE, CSS_TYPE]);
+
+/**
+ * @type {ReadonlySet<"css">}
+ */
+const CSS_TYPES = new Set([CSS_TYPE]);
+
+/**
+ * @type {ReadonlySet<"asset-url">}
+ */
+const ASSET_URL_TYPES = new Set([ASSET_URL_TYPE]);
+
+/**
+ * @type {ReadonlySet<"css-text">}
+ */
+const CSS_TEXT_TYPES = new Set([CSS_TEXT_TYPE]);
+
+/**
+ * @type {ReadonlySet<"javascript" | "css-text">}
+ */
+const JAVASCRIPT_AND_CSS_TEXT_TYPES = new Set([JAVASCRIPT_TYPE, CSS_TEXT_TYPE]);
+/**
+ * @type {ReadonlySet<"css-import">}
+ */
+const CSS_IMPORT_TYPES = new Set([CSS_IMPORT_TYPE]);
+
+/**
+ * @type {ReadonlySet<"html">}
+ */
+const HTML_TYPES = new Set([HTML_TYPE]);
+
+/**
+ * @type {ReadonlySet<"webassembly">}
+ */
+const WEBASSEMBLY_TYPES = new Set([WEBASSEMBLY_TYPE]);
+
+/**
+ * @type {ReadonlySet<"runtime">}
+ */
+const RUNTIME_TYPES = new Set([RUNTIME_TYPE]);
+
+/**
+ * @type {ReadonlySet<"remote" | "share-init">}
+ */
+const REMOTE_AND_SHARE_INIT_TYPES = new Set([
+	REMOTE_GENERATOR_TYPE,
+	SHARED_INIT_TYPE
+]);
+
+/**
+ * @type {ReadonlySet<"consume-shared">}
+ */
+const CONSUME_SHARED_TYPES = new Set([CONSUME_SHARED_GENERATOR_TYPE]);
+
+/**
+ * @type {ReadonlySet<"share-init">}
+ */
+const SHARED_INIT_TYPES = new Set([SHARED_INIT_TYPE]);
+
+module.exports.ASSET_AND_ASSET_URL_TYPES = ASSET_AND_ASSET_URL_TYPES;
+module.exports.ASSET_AND_JAVASCRIPT_AND_ASSET_URL_TYPES =
+	ASSET_AND_JAVASCRIPT_AND_ASSET_URL_TYPES;
+module.exports.ASSET_AND_JAVASCRIPT_TYPES = ASSET_AND_JAVASCRIPT_TYPES;
+module.exports.ASSET_TYPE = ASSET_TYPE;
+module.exports.ASSET_TYPES = ASSET_TYPES;
+module.exports.ASSET_URL_TYPE = ASSET_URL_TYPE;
+module.exports.ASSET_URL_TYPES = ASSET_URL_TYPES;
+module.exports.CONSUME_SHARED_TYPES = CONSUME_SHARED_TYPES;
+module.exports.CSS_IMPORT_TYPE = CSS_IMPORT_TYPE;
+module.exports.CSS_IMPORT_TYPES = CSS_IMPORT_TYPES;
+module.exports.CSS_TEXT_TYPE = CSS_TEXT_TYPE;
+module.exports.CSS_TEXT_TYPES = CSS_TEXT_TYPES;
+module.exports.CSS_TYPE = CSS_TYPE;
+module.exports.CSS_TYPES = CSS_TYPES;
+module.exports.HTML_TYPE = HTML_TYPE;
+module.exports.HTML_TYPES = HTML_TYPES;
+module.exports.JAVASCRIPT_AND_ASSET_URL_TYPES = JAVASCRIPT_AND_ASSET_URL_TYPES;
+module.exports.JAVASCRIPT_AND_CSS_TEXT_TYPES = JAVASCRIPT_AND_CSS_TEXT_TYPES;
+module.exports.JAVASCRIPT_AND_CSS_TYPES = JAVASCRIPT_AND_CSS_TYPES;
+module.exports.JAVASCRIPT_TYPE = JAVASCRIPT_TYPE;
+module.exports.JAVASCRIPT_TYPES = JAVASCRIPT_TYPES;
+module.exports.NO_TYPES = NO_TYPES;
+module.exports.REMOTE_AND_SHARE_INIT_TYPES = REMOTE_AND_SHARE_INIT_TYPES;
+module.exports.RUNTIME_TYPE = RUNTIME_TYPE;
+module.exports.RUNTIME_TYPES = RUNTIME_TYPES;
+module.exports.SHARED_INIT_TYPE = SHARED_INIT_TYPE;
+module.exports.SHARED_INIT_TYPES = SHARED_INIT_TYPES;
+module.exports.UNKNOWN_TYPE = UNKNOWN_TYPE;
+module.exports.WEBASSEMBLY_TYPE = WEBASSEMBLY_TYPE;
+module.exports.WEBASSEMBLY_TYPES = WEBASSEMBLY_TYPES;
diff --git a/lib/ModuleTemplate.js b/lib/ModuleTemplate.js
index 06e787ed930..3b717131e2f 100644
--- a/lib/ModuleTemplate.js
+++ b/lib/ModuleTemplate.js
@@ -2,92 +2,178 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
-const { Tapable, SyncWaterfallHook, SyncHook } = require("tapable");
+const util = require("util");
+const JavascriptModulesPlugin = require("./javascript/JavascriptModulesPlugin");
 
+/** @typedef {import("tapable").Tap} Tap */
 /** @typedef {import("webpack-sources").Source} Source */
+/** @typedef {import("./Chunk")} Chunk */
+/** @typedef {import("./Compilation")} Compilation */
+/** @typedef {import("./DependencyTemplates")} DependencyTemplates */
 /** @typedef {import("./Module")} Module */
+/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */
+/** @typedef {import("./javascript/JavascriptModulesPlugin").ChunkRenderContext} ChunkRenderContext */
+/** @typedef {import("./javascript/JavascriptModulesPlugin").ModuleRenderContext}  ModuleRenderContext */
+/** @typedef {import("./util/Hash")} Hash */
 
-module.exports = class ModuleTemplate extends Tapable {
-	constructor(runtimeTemplate, type) {
-		super();
-		this.runtimeTemplate = runtimeTemplate;
-		this.type = type;
-		this.hooks = {
-			content: new SyncWaterfallHook([
-				"source",
-				"module",
-				"options",
-				"dependencyTemplates"
-			]),
-			module: new SyncWaterfallHook([
-				"source",
-				"module",
-				"options",
-				"dependencyTemplates"
-			]),
-			render: new SyncWaterfallHook([
-				"source",
-				"module",
-				"options",
-				"dependencyTemplates"
-			]),
-			package: new SyncWaterfallHook([
-				"source",
-				"module",
-				"options",
-				"dependencyTemplates"
-			]),
-			hash: new SyncHook(["hash"])
-		};
-	}
+/**
+ * Defines the if set type used by this module.
+ * @template T
+ * @typedef {import("tapable").IfSet} IfSet
+ */
 
+// TODO webpack 6: remove this class
+class ModuleTemplate {
 	/**
-	 * @param {Module} module the module
-	 * @param {TODO} dependencyTemplates templates for dependencies
-	 * @param {TODO} options render options
-	 * @returns {Source} the source
+	 * Creates an instance of ModuleTemplate.
+	 * @param {RuntimeTemplate} runtimeTemplate the runtime template
+	 * @param {Compilation} compilation the compilation
 	 */
-	render(module, dependencyTemplates, options) {
-		try {
-			const moduleSource = module.source(
-				dependencyTemplates,
-				this.runtimeTemplate,
-				this.type
-			);
-			const moduleSourcePostContent = this.hooks.content.call(
-				moduleSource,
-				module,
-				options,
-				dependencyTemplates
-			);
-			const moduleSourcePostModule = this.hooks.module.call(
-				moduleSourcePostContent,
-				module,
-				options,
-				dependencyTemplates
-			);
-			const moduleSourcePostRender = this.hooks.render.call(
-				moduleSourcePostModule,
-				module,
-				options,
-				dependencyTemplates
-			);
-			return this.hooks.package.call(
-				moduleSourcePostRender,
-				module,
-				options,
-				dependencyTemplates
-			);
-		} catch (e) {
-			e.message = `${module.identifier()}\n${e.message}`;
-			throw e;
-		}
+	constructor(runtimeTemplate, compilation) {
+		/** @type {RuntimeTemplate} */
+		this._runtimeTemplate = runtimeTemplate;
+		/** @type {string} */
+		this.type = "javascript";
+		this.hooks = Object.freeze({
+			content: {
+				tap: util.deprecate(
+					/**
+					 * Handles the callback logic for this hook.
+					 * @template AdditionalOptions
+					 * @param {string | Tap & IfSet} options options
+					 * @param {(source: Source, module: Module, moduleRenderContext: ModuleRenderContext, dependencyTemplates: DependencyTemplates) => Source} fn fn
+					 */
+					(options, fn) => {
+						JavascriptModulesPlugin.getCompilationHooks(
+							compilation
+						).renderModuleContent.tap(
+							options,
+							(source, module, renderContext) =>
+								fn(
+									source,
+									module,
+									renderContext,
+									renderContext.dependencyTemplates
+								)
+						);
+					},
+					"ModuleTemplate.hooks.content is deprecated (use JavascriptModulesPlugin.getCompilationHooks().renderModuleContent instead)",
+					"DEP_MODULE_TEMPLATE_CONTENT"
+				)
+			},
+			module: {
+				tap: util.deprecate(
+					/**
+					 * Handles the callback logic for this hook.
+					 * @template AdditionalOptions
+					 * @param {string | Tap & IfSet} options options
+					 * @param {(source: Source, module: Module, moduleRenderContext: ModuleRenderContext, dependencyTemplates: DependencyTemplates) => Source} fn fn
+					 */
+					(options, fn) => {
+						JavascriptModulesPlugin.getCompilationHooks(
+							compilation
+						).renderModuleContent.tap(
+							options,
+							(source, module, renderContext) =>
+								fn(
+									source,
+									module,
+									renderContext,
+									renderContext.dependencyTemplates
+								)
+						);
+					},
+					"ModuleTemplate.hooks.module is deprecated (use JavascriptModulesPlugin.getCompilationHooks().renderModuleContent instead)",
+					"DEP_MODULE_TEMPLATE_MODULE"
+				)
+			},
+			render: {
+				tap: util.deprecate(
+					/**
+					 * Handles the callback logic for this hook.
+					 * @template AdditionalOptions
+					 * @param {string | Tap & IfSet} options options
+					 * @param {(source: Source, module: Module, chunkRenderContext: ChunkRenderContext, dependencyTemplates: DependencyTemplates) => Source} fn fn
+					 */
+					(options, fn) => {
+						JavascriptModulesPlugin.getCompilationHooks(
+							compilation
+						).renderModuleContainer.tap(
+							options,
+							(source, module, renderContext) =>
+								fn(
+									source,
+									module,
+									renderContext,
+									renderContext.dependencyTemplates
+								)
+						);
+					},
+					"ModuleTemplate.hooks.render is deprecated (use JavascriptModulesPlugin.getCompilationHooks().renderModuleContainer instead)",
+					"DEP_MODULE_TEMPLATE_RENDER"
+				)
+			},
+			package: {
+				tap: util.deprecate(
+					/**
+					 * Handles the callback logic for this hook.
+					 * @template AdditionalOptions
+					 * @param {string | Tap & IfSet} options options
+					 * @param {(source: Source, module: Module, chunkRenderContext: ChunkRenderContext, dependencyTemplates: DependencyTemplates) => Source} fn fn
+					 */
+					(options, fn) => {
+						JavascriptModulesPlugin.getCompilationHooks(
+							compilation
+						).renderModulePackage.tap(
+							options,
+							(source, module, renderContext) =>
+								fn(
+									source,
+									module,
+									renderContext,
+									renderContext.dependencyTemplates
+								)
+						);
+					},
+					"ModuleTemplate.hooks.package is deprecated (use JavascriptModulesPlugin.getCompilationHooks().renderModulePackage instead)",
+					"DEP_MODULE_TEMPLATE_PACKAGE"
+				)
+			},
+			hash: {
+				tap: util.deprecate(
+					/**
+					 * Handles the callback logic for this hook.
+					 * @template AdditionalOptions
+					 * @param {string | Tap & IfSet} options options
+					 * @param {(hash: Hash) => void} fn fn
+					 */
+					(options, fn) => {
+						compilation.hooks.fullHash.tap(options, fn);
+					},
+					"ModuleTemplate.hooks.hash is deprecated (use Compilation.hooks.fullHash instead)",
+					"DEP_MODULE_TEMPLATE_HASH"
+				)
+			}
+		});
 	}
+}
 
-	updateHash(hash) {
-		hash.update("1");
-		this.hooks.hash.call(hash);
-	}
-};
+Object.defineProperty(ModuleTemplate.prototype, "runtimeTemplate", {
+	get: util.deprecate(
+		/**
+		 * Returns output options.
+		 * @this {ModuleTemplate}
+		 * @returns {RuntimeTemplate} output options
+		 */
+		function runtimeTemplate() {
+			return this._runtimeTemplate;
+		},
+		"ModuleTemplate.runtimeTemplate is deprecated (use Compilation.runtimeTemplate instead)",
+		"DEP_WEBPACK_CHUNK_TEMPLATE_OUTPUT_OPTIONS"
+	)
+});
+
+module.exports = ModuleTemplate;
diff --git a/lib/ModuleTypeConstants.js b/lib/ModuleTypeConstants.js
new file mode 100644
index 00000000000..135d15cbf63
--- /dev/null
+++ b/lib/ModuleTypeConstants.js
@@ -0,0 +1,206 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Sean Larkin @TheLarkInn
+*/
+
+"use strict";
+
+/**
+ * @type {Readonly<"javascript/auto">}
+ */
+const JAVASCRIPT_MODULE_TYPE_AUTO = "javascript/auto";
+
+/**
+ * @type {Readonly<"javascript/dynamic">}
+ */
+const JAVASCRIPT_MODULE_TYPE_DYNAMIC = "javascript/dynamic";
+
+/**
+ * @type {Readonly<"javascript/esm">}
+ * This is the module type used for _strict_ ES Module syntax. This means that all legacy formats
+ * that webpack supports (CommonJS, AMD, SystemJS) are not supported.
+ */
+const JAVASCRIPT_MODULE_TYPE_ESM = "javascript/esm";
+
+/**
+ * @type {Readonly<"json">}
+ * This is the module type used for JSON files. JSON files are always parsed as ES Module.
+ */
+const JSON_MODULE_TYPE = "json";
+
+/**
+ * @type {Readonly<"webassembly/async">}
+ * This is the module type used for WebAssembly modules. In webpack 5 they are always treated as async modules.
+ */
+const WEBASSEMBLY_MODULE_TYPE_ASYNC = "webassembly/async";
+
+/**
+ * @type {Readonly<"webassembly/sync">}
+ * This is the module type used for WebAssembly modules. In webpack 4 they are always treated as sync modules.
+ * There is a legacy option to support this usage in webpack 5 and up.
+ */
+const WEBASSEMBLY_MODULE_TYPE_SYNC = "webassembly/sync";
+
+/**
+ * @type {Readonly<"css">}
+ * This is the module type used for CSS files.
+ */
+const CSS_MODULE_TYPE = "css";
+
+/**
+ * @type {Readonly<"css/global">}
+ * This is the module type used for CSS modules files where you need to use `:local` in selector list to hash classes.
+ */
+const CSS_MODULE_TYPE_GLOBAL = "css/global";
+
+/**
+ * @type {Readonly<"css/module">}
+ * This is the module type used for CSS modules files, by default all classes are hashed.
+ */
+const CSS_MODULE_TYPE_MODULE = "css/module";
+
+/**
+ * @type {Readonly<"css/auto">}
+ * This is the module type used for CSS files, the module will be parsed as CSS modules if it's filename contains `.module.` or `.modules.`.
+ */
+const CSS_MODULE_TYPE_AUTO = "css/auto";
+
+/**
+ * @type {Readonly<"html">}
+ * This is the module type used for HTML files when `experiments.html` is enabled.
+ * HTML modules are emitted as HTML assets and can be used as entry points.
+ */
+const HTML_MODULE_TYPE = "html";
+
+/**
+ * @type {Readonly<"asset">}
+ * This is the module type used for automatically choosing between `asset/inline`, `asset/resource` based on asset size limit (8096).
+ */
+const ASSET_MODULE_TYPE = "asset";
+
+/**
+ * @type {Readonly<"asset/inline">}
+ * This is the module type used for assets that are inlined as a data URI. This is the equivalent of `url-loader`.
+ */
+const ASSET_MODULE_TYPE_INLINE = "asset/inline";
+
+/**
+ * @type {Readonly<"asset/resource">}
+ * This is the module type used for assets that are copied to the output directory. This is the equivalent of `file-loader`.
+ */
+const ASSET_MODULE_TYPE_RESOURCE = "asset/resource";
+
+/**
+ * @type {Readonly<"asset/source">}
+ * This is the module type used for assets that are imported as source code. This is the equivalent of `raw-loader`.
+ */
+const ASSET_MODULE_TYPE_SOURCE = "asset/source";
+
+/**
+ * @type {Readonly<"asset/bytes">}
+ * This is the module type used for assets that are imported as Uint8Array.
+ */
+const ASSET_MODULE_TYPE_BYTES = "asset/bytes";
+
+/**
+ * @type {Readonly<"asset/raw-data-url">}
+ * This is the module type used for the ignored asset module.
+ */
+const ASSET_MODULE_TYPE_RAW_DATA_URL = "asset/raw-data-url";
+
+/**
+ * @type {Readonly<"asset/webmanifest">}
+ * This is the module type used for a Web App Manifest whose icon URLs are bundled as assets.
+ */
+const ASSET_MODULE_TYPE_WEBMANIFEST = "asset/webmanifest";
+
+/**
+ * @type {Readonly<"runtime">}
+ * This is the module type used for the webpack runtime abstractions.
+ */
+const WEBPACK_MODULE_TYPE_RUNTIME = "runtime";
+
+/**
+ * @type {Readonly<"fallback-module">}
+ * This is the module type used for the ModuleFederation feature's FallbackModule class.
+ */
+const WEBPACK_MODULE_TYPE_FALLBACK = "fallback-module";
+
+/**
+ * @type {Readonly<"remote-module">}
+ * This is the module type used for the ModuleFederation feature's RemoteModule class.
+ */
+const WEBPACK_MODULE_TYPE_REMOTE = "remote-module";
+
+/**
+ * @type {Readonly<"provide-module">}
+ * This is the module type used for the ModuleFederation feature's ProvideModule class.
+ */
+const WEBPACK_MODULE_TYPE_PROVIDE = "provide-module";
+
+/**
+ * @type {Readonly<"consume-shared-module">}
+ * This is the module type used for the ModuleFederation feature's ConsumeSharedModule class.
+ */
+const WEBPACK_MODULE_TYPE_CONSUME_SHARED_MODULE = "consume-shared-module";
+
+/**
+ * @type {Readonly<"lazy-compilation-proxy">}
+ * Module type used for `experiments.lazyCompilation` feature. See `LazyCompilationPlugin` for more information.
+ */
+const WEBPACK_MODULE_TYPE_LAZY_COMPILATION_PROXY = "lazy-compilation-proxy";
+
+/** @typedef {"javascript/auto" | "javascript/dynamic" | "javascript/esm"} JavaScriptModuleTypes */
+/** @typedef {"json"} JSONModuleType */
+/** @typedef {"webassembly/async" | "webassembly/sync"} WebAssemblyModuleTypes */
+/** @typedef {"css" | "css/global" | "css/module" | "css/auto"} CssModuleTypes */
+/** @typedef {"html"} HTMLModuleType */
+/** @typedef {"asset" | "asset/inline" | "asset/resource" | "asset/source" | "asset/raw-data-url" | "asset/webmanifest"} AssetModuleTypes */
+/** @typedef {"runtime" | "fallback-module" | "remote-module" | "provide-module" | "consume-shared-module" | "lazy-compilation-proxy"} WebpackModuleTypes */
+/** @typedef {string} UnknownModuleTypes */
+/** @typedef {JavaScriptModuleTypes | JSONModuleType | WebAssemblyModuleTypes | CssModuleTypes | HTMLModuleType | AssetModuleTypes | WebpackModuleTypes | UnknownModuleTypes} ModuleTypes */
+
+module.exports.ASSET_MODULE_TYPE = ASSET_MODULE_TYPE;
+module.exports.ASSET_MODULE_TYPE_BYTES = ASSET_MODULE_TYPE_BYTES;
+module.exports.ASSET_MODULE_TYPE_INLINE = ASSET_MODULE_TYPE_INLINE;
+module.exports.ASSET_MODULE_TYPE_RAW_DATA_URL = ASSET_MODULE_TYPE_RAW_DATA_URL;
+module.exports.ASSET_MODULE_TYPE_RESOURCE = ASSET_MODULE_TYPE_RESOURCE;
+module.exports.ASSET_MODULE_TYPE_SOURCE = ASSET_MODULE_TYPE_SOURCE;
+module.exports.ASSET_MODULE_TYPE_WEBMANIFEST = ASSET_MODULE_TYPE_WEBMANIFEST;
+/** @type {CssModuleTypes[]} */
+module.exports.CSS_MODULES = [
+	CSS_MODULE_TYPE,
+	CSS_MODULE_TYPE_GLOBAL,
+	CSS_MODULE_TYPE_MODULE,
+	CSS_MODULE_TYPE_AUTO
+];
+module.exports.CSS_MODULE_TYPE = CSS_MODULE_TYPE;
+module.exports.CSS_MODULE_TYPE_AUTO = CSS_MODULE_TYPE_AUTO;
+module.exports.CSS_MODULE_TYPE_GLOBAL = CSS_MODULE_TYPE_GLOBAL;
+module.exports.CSS_MODULE_TYPE_MODULE = CSS_MODULE_TYPE_MODULE;
+module.exports.HTML_MODULE_TYPE = HTML_MODULE_TYPE;
+/** @type {JavaScriptModuleTypes[]} */
+module.exports.JAVASCRIPT_MODULES = [
+	JAVASCRIPT_MODULE_TYPE_AUTO,
+	JAVASCRIPT_MODULE_TYPE_DYNAMIC,
+	JAVASCRIPT_MODULE_TYPE_ESM
+];
+module.exports.JAVASCRIPT_MODULE_TYPE_AUTO = JAVASCRIPT_MODULE_TYPE_AUTO;
+module.exports.JAVASCRIPT_MODULE_TYPE_DYNAMIC = JAVASCRIPT_MODULE_TYPE_DYNAMIC;
+module.exports.JAVASCRIPT_MODULE_TYPE_ESM = JAVASCRIPT_MODULE_TYPE_ESM;
+module.exports.JSON_MODULE_TYPE = JSON_MODULE_TYPE;
+/** @type {WebAssemblyModuleTypes[]} */
+module.exports.WEBASSEMBLY_MODULES = [
+	WEBASSEMBLY_MODULE_TYPE_ASYNC,
+	WEBASSEMBLY_MODULE_TYPE_SYNC
+];
+module.exports.WEBASSEMBLY_MODULE_TYPE_ASYNC = WEBASSEMBLY_MODULE_TYPE_ASYNC;
+module.exports.WEBASSEMBLY_MODULE_TYPE_SYNC = WEBASSEMBLY_MODULE_TYPE_SYNC;
+module.exports.WEBPACK_MODULE_TYPE_CONSUME_SHARED_MODULE =
+	WEBPACK_MODULE_TYPE_CONSUME_SHARED_MODULE;
+module.exports.WEBPACK_MODULE_TYPE_FALLBACK = WEBPACK_MODULE_TYPE_FALLBACK;
+module.exports.WEBPACK_MODULE_TYPE_LAZY_COMPILATION_PROXY =
+	WEBPACK_MODULE_TYPE_LAZY_COMPILATION_PROXY;
+module.exports.WEBPACK_MODULE_TYPE_PROVIDE = WEBPACK_MODULE_TYPE_PROVIDE;
+module.exports.WEBPACK_MODULE_TYPE_REMOTE = WEBPACK_MODULE_TYPE_REMOTE;
+module.exports.WEBPACK_MODULE_TYPE_RUNTIME = WEBPACK_MODULE_TYPE_RUNTIME;
diff --git a/lib/ModuleWarning.js b/lib/ModuleWarning.js
deleted file mode 100644
index 13068192c16..00000000000
--- a/lib/ModuleWarning.js
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const WebpackError = require("./WebpackError");
-const { cleanUp } = require("./ErrorHelpers");
-
-class ModuleWarning extends WebpackError {
-	constructor(module, warning, { from = null } = {}) {
-		let message = "Module Warning";
-		if (from) {
-			message += ` (from ${from}):\n`;
-		} else {
-			message += ": ";
-		}
-		if (warning && typeof warning === "object" && warning.message) {
-			message += warning.message;
-		} else if (warning) {
-			message += warning;
-		}
-		super(message);
-		this.name = "ModuleWarning";
-		this.module = module;
-		this.warning = warning;
-		this.details =
-			warning && typeof warning === "object" && warning.stack
-				? cleanUp(warning.stack, this.message)
-				: undefined;
-
-		Error.captureStackTrace(this, this.constructor);
-	}
-}
-
-module.exports = ModuleWarning;
diff --git a/lib/MultiCompiler.js b/lib/MultiCompiler.js
index cdc7fb5089e..664c0007a40 100644
--- a/lib/MultiCompiler.js
+++ b/lib/MultiCompiler.js
@@ -2,64 +2,231 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
-const { Tapable, SyncHook, MultiHook } = require("tapable");
 const asyncLib = require("neo-async");
-const MultiWatching = require("./MultiWatching");
+const { MultiHook, SyncHook } = require("tapable");
+
 const MultiStats = require("./MultiStats");
-const ConcurrentCompilationError = require("./ConcurrentCompilationError");
-
-module.exports = class MultiCompiler extends Tapable {
-	constructor(compilers) {
-		super();
-		this.hooks = {
-			done: new SyncHook(["stats"]),
-			invalid: new MultiHook(compilers.map(c => c.hooks.invalid)),
-			run: new MultiHook(compilers.map(c => c.hooks.run)),
-			watchClose: new SyncHook([]),
-			watchRun: new MultiHook(compilers.map(c => c.hooks.watchRun))
-		};
+const MultiWatching = require("./MultiWatching");
+const WebpackError = require("./errors/WebpackError");
+const ArrayQueue = require("./util/ArrayQueue");
+const memoize = require("./util/memoize");
+
+const getConcurrentCompilationError = memoize(() =>
+	require("./errors/ConcurrentCompilationError")
+);
+
+/**
+ * Defines the shared type used by this module.
+ * @template T
+ * @typedef {import("tapable").AsyncSeriesHook} AsyncSeriesHook
+ */
+/**
+ * Defines the shared type used by this module.
+ * @template T
+ * @template R
+ * @typedef {import("tapable").SyncBailHook} SyncBailHook
+ */
+
+/** @typedef {import("../declarations/WebpackOptions").WebpackOptions} WebpackOptions */
+/** @typedef {import("../declarations/WebpackOptions").WatchOptions} WatchOptions */
+/** @typedef {import("./Compiler")} Compiler */
+/**
+ * Defines the callback type used by this module.
+ * @template T
+ * @template [R=void]
+ * @typedef {import("./webpack").Callback} Callback
+ */
+/** @typedef {import("./webpack").ErrorCallback} ErrorCallback */
+/** @typedef {import("./Stats")} Stats */
+/** @typedef {import("./logging/Logger").Logger} Logger */
+/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
+/** @typedef {import("./util/fs").IntermediateFileSystem} IntermediateFileSystem */
+/** @typedef {import("./util/fs").OutputFileSystem} OutputFileSystem */
+/** @typedef {import("./util/fs").WatchFileSystem} WatchFileSystem */
+
+/**
+ * Defines the run with dependencies handler callback.
+ * @callback RunWithDependenciesHandler
+ * @param {Compiler} compiler
+ * @param {Callback} callback
+ * @returns {void}
+ */
+
+/**
+ * Defines the multi compiler options type used by this module.
+ * @typedef {object} MultiCompilerOptions
+ * @property {number=} parallelism how many Compilers are allows to run at the same time in parallel
+ */
+
+/** @typedef {ReadonlyArray & MultiCompilerOptions} MultiWebpackOptions */
+
+const CLASS_NAME = "MultiCompiler";
+
+module.exports = class MultiCompiler {
+	/**
+	 * Creates an instance of MultiCompiler.
+	 * @param {Compiler[] | Record} compilers child compilers
+	 * @param {MultiCompilerOptions} options options
+	 */
+	constructor(compilers, options) {
 		if (!Array.isArray(compilers)) {
-			compilers = Object.keys(compilers).map(name => {
-				compilers[name].name = name;
-				return compilers[name];
+			/** @type {Compiler[]} */
+			compilers = Object.keys(compilers).map((name) => {
+				/** @type {Record} */
+				(compilers)[name].name = name;
+				return /** @type {Record} */ (compilers)[name];
 			});
 		}
+
+		this.hooks = Object.freeze({
+			/** @type {SyncHook<[MultiStats, Compiler[]]>} */
+			done: new SyncHook(["stats", "changedCompilers"]),
+			/** @type {MultiHook>} */
+			invalid: new MultiHook(compilers.map((c) => c.hooks.invalid)),
+			/** @type {MultiHook>} */
+			run: new MultiHook(compilers.map((c) => c.hooks.run)),
+			/** @type {SyncHook<[]>} */
+			watchClose: new SyncHook([]),
+			/** @type {MultiHook>} */
+			shutdown: new MultiHook(compilers.map((c) => c.hooks.shutdown)),
+			/** @type {MultiHook>} */
+			watchRun: new MultiHook(compilers.map((c) => c.hooks.watchRun)),
+			/** @type {MultiHook>} */
+			infrastructureLog: new MultiHook(
+				compilers.map((c) => c.hooks.infrastructureLog)
+			)
+		});
+		/** @type {Compiler[]} */
 		this.compilers = compilers;
+		/** @type {MultiCompilerOptions} */
+		this._options = {
+			parallelism: options.parallelism || Infinity
+		};
+		/** @type {WeakMap} */
+		this.dependencies = new WeakMap();
+		/** @type {boolean} */
+		this.running = false;
+		/** @type {MultiWatching | undefined} */
+		this.watching = undefined;
+
+		/** @type {(Stats | null)[]} */
+		const compilerStats = this.compilers.map(() => null);
+		/** @type {Set} */
+		const changedCompilers = new Set();
 		let doneCompilers = 0;
-		let compilerStats = [];
-		let index = 0;
-		for (const compiler of this.compilers) {
+		for (let index = 0; index < this.compilers.length; index++) {
+			const compiler = this.compilers[index];
+			const compilerIndex = index;
 			let compilerDone = false;
-			const compilerIndex = index++;
 			// eslint-disable-next-line no-loop-func
-			compiler.hooks.done.tap("MultiCompiler", stats => {
+			compiler.hooks.done.tap(CLASS_NAME, (stats) => {
+				changedCompilers.add(compiler);
 				if (!compilerDone) {
 					compilerDone = true;
 					doneCompilers++;
 				}
 				compilerStats[compilerIndex] = stats;
 				if (doneCompilers === this.compilers.length) {
-					this.hooks.done.call(new MultiStats(compilerStats));
+					const changed = this.compilers.filter((c) => changedCompilers.has(c));
+					changedCompilers.clear();
+					this.hooks.done.call(
+						// Copy: the live array is cleared per child on shutdown.
+						new MultiStats(/** @type {Stats[]} */ ([...compilerStats])),
+						changed
+					);
 				}
 			});
+			// Each entry holds a Stats -> Compilation; don't outlive the child's close().
+			compiler.hooks.shutdown.tap(CLASS_NAME, () => {
+				compilerStats[compilerIndex] = null;
+			});
 			// eslint-disable-next-line no-loop-func
-			compiler.hooks.invalid.tap("MultiCompiler", () => {
+			compiler.hooks.invalid.tap(CLASS_NAME, () => {
 				if (compilerDone) {
 					compilerDone = false;
 					doneCompilers--;
 				}
 			});
+			// Release fields on this child's Compilation once it's done. The
+			// stage: Infinity tap runs after every afterDone tap at a lower
+			// stage, so plugins observing compilation state in afterDone still
+			// see it intact. Stats remains usable; only fields Stats never reads
+			// (and that the persistent cache never serializes) are dropped.
+
+			compiler.hooks.afterDone.tap(
+				{ name: CLASS_NAME, stage: Infinity },
+				(stats) => {
+					if (stats !== undefined) {
+						compiler._releaseUnusedCompilationData(stats.compilation);
+					}
+				}
+			);
+		}
+		this._validateCompilersOptions();
+	}
+
+	_validateCompilersOptions() {
+		if (this.compilers.length < 2) return;
+		/**
+		 * Adds the provided compiler to the multi compiler.
+		 * @param {Compiler} compiler compiler
+		 * @param {WebpackError} warning warning
+		 */
+		const addWarning = (compiler, warning) => {
+			compiler.hooks.thisCompilation.tap(CLASS_NAME, (compilation) => {
+				compilation.warnings.push(warning);
+			});
+		};
+		/** @type {Set} */
+		const cacheNames = new Set();
+		for (const compiler of this.compilers) {
+			if (compiler.options.cache && "name" in compiler.options.cache) {
+				const name = /** @type {string} */ (compiler.options.cache.name);
+				if (cacheNames.has(name)) {
+					addWarning(
+						compiler,
+						new WebpackError(
+							`${
+								compiler.name
+									? `Compiler with name "${compiler.name}" doesn't use unique cache name. `
+									: ""
+							}Please set unique "cache.name" option. Name "${name}" already used.`
+						)
+					);
+				} else {
+					cacheNames.add(name);
+				}
+			}
 		}
-		this.running = false;
+	}
+
+	get options() {
+		return Object.assign(
+			this.compilers.map((c) => c.options),
+			this._options
+		);
 	}
 
 	get outputPath() {
+		// Match whole path segments: "/dist-app" must not count as inside "/dist"
+		/**
+		 * @param {string} parent candidate ancestor path
+		 * @param {string} path path to test
+		 * @returns {boolean} true when path is parent or inside it
+		 */
+		const isSubPath = (parent, path) => {
+			if (path === parent) return true;
+			if (!path.startsWith(parent)) return false;
+			const next = path.charCodeAt(parent.length);
+			return next === 47 || next === 92 || /[/\\]$/.test(parent);
+		};
 		let commonPath = this.compilers[0].outputPath;
 		for (const compiler of this.compilers) {
 			while (
-				compiler.outputPath.indexOf(commonPath) !== 0 &&
+				!isSubPath(commonPath, compiler.outputPath) &&
 				/[/\\]/.test(commonPath)
 			) {
 				commonPath = commonPath.replace(/[/\\][^/\\]*$/, "");
@@ -74,26 +241,93 @@ module.exports = class MultiCompiler extends Tapable {
 		throw new Error("Cannot read inputFileSystem of a MultiCompiler");
 	}
 
-	get outputFileSystem() {
-		throw new Error("Cannot read outputFileSystem of a MultiCompiler");
-	}
-
+	/**
+	 * Sets input file system.
+	 * @param {InputFileSystem} value the new input file system
+	 */
 	set inputFileSystem(value) {
 		for (const compiler of this.compilers) {
 			compiler.inputFileSystem = value;
 		}
 	}
 
+	get outputFileSystem() {
+		throw new Error("Cannot read outputFileSystem of a MultiCompiler");
+	}
+
+	/**
+	 * Sets output file system.
+	 * @param {OutputFileSystem} value the new output file system
+	 */
 	set outputFileSystem(value) {
 		for (const compiler of this.compilers) {
 			compiler.outputFileSystem = value;
 		}
 	}
 
+	get watchFileSystem() {
+		throw new Error("Cannot read watchFileSystem of a MultiCompiler");
+	}
+
+	/**
+	 * Sets watch file system.
+	 * @param {WatchFileSystem} value the new watch file system
+	 */
+	set watchFileSystem(value) {
+		for (const compiler of this.compilers) {
+			compiler.watchFileSystem = value;
+		}
+	}
+
+	/**
+	 * Sets intermediate file system.
+	 * @param {IntermediateFileSystem} value the new intermediate file system
+	 */
+	set intermediateFileSystem(value) {
+		for (const compiler of this.compilers) {
+			compiler.intermediateFileSystem = value;
+		}
+	}
+
+	get intermediateFileSystem() {
+		throw new Error("Cannot read outputFileSystem of a MultiCompiler");
+	}
+
+	/**
+	 * Gets infrastructure logger.
+	 * @param {string | (() => string)} name name of the logger, or function called once to get the logger name
+	 * @returns {Logger} a logger with that name
+	 */
+	getInfrastructureLogger(name) {
+		return this.compilers[0].getInfrastructureLogger(name);
+	}
+
+	/**
+	 * Updates dependencies using the provided compiler.
+	 * @param {Compiler} compiler the child compiler
+	 * @param {string[]} dependencies its dependencies
+	 * @returns {void}
+	 */
+	setDependencies(compiler, dependencies) {
+		this.dependencies.set(compiler, dependencies);
+	}
+
+	/**
+	 * Validate dependencies.
+	 * @param {Callback} callback signals when the validation is complete
+	 * @returns {boolean} true if the dependencies are valid
+	 */
 	validateDependencies(callback) {
+		/** @type {Set<{ source: Compiler, target: Compiler }>} */
 		const edges = new Set();
+		/** @type {string[]} */
 		const missing = [];
-		const targetFound = compiler => {
+		/**
+		 * Returns target was found.
+		 * @param {Compiler} compiler compiler
+		 * @returns {boolean} target was found
+		 */
+		const targetFound = (compiler) => {
 			for (const edge of edges) {
 				if (edge.target === compiler) {
 					return true;
@@ -101,16 +335,22 @@ module.exports = class MultiCompiler extends Tapable {
 			}
 			return false;
 		};
-		const sortEdges = (e1, e2) => {
-			return (
-				e1.source.name.localeCompare(e2.source.name) ||
-				e1.target.name.localeCompare(e2.target.name)
-			);
-		};
+		/**
+		 * Returns result.
+		 * @param {{ source: Compiler, target: Compiler }} e1 edge 1
+		 * @param {{ source: Compiler, target: Compiler }} e2 edge 2
+		 * @returns {number} result
+		 */
+		const sortEdges = (e1, e2) =>
+			/** @type {string} */
+			(e1.source.name).localeCompare(/** @type {string} */ (e2.source.name)) ||
+			/** @type {string} */
+			(e1.target.name).localeCompare(/** @type {string} */ (e2.target.name));
 		for (const source of this.compilers) {
-			if (source.dependencies) {
-				for (const dep of source.dependencies) {
-					const target = this.compilers.find(c => c.name === dep);
+			const dependencies = this.dependencies.get(source);
+			if (dependencies) {
+				for (const dep of dependencies) {
+					const target = this.compilers.find((c) => c.name === dep);
 					if (!target) {
 						missing.push(dep);
 					} else {
@@ -122,8 +362,11 @@ module.exports = class MultiCompiler extends Tapable {
 				}
 			}
 		}
-		const errors = missing.map(m => `Compiler dependency \`${m}\` not found.`);
-		const stack = this.compilers.filter(c => !targetFound(c));
+		/** @type {string[]} */
+		const errors = missing.map(
+			(m) => `Compiler dependency \`${m}\` not found.`
+		);
+		const stack = this.compilers.filter((c) => !targetFound(c));
 		while (stack.length > 0) {
 			const current = stack.pop();
 			for (const edge of edges) {
@@ -137,9 +380,10 @@ module.exports = class MultiCompiler extends Tapable {
 			}
 		}
 		if (edges.size > 0) {
-			const lines = Array.from(edges)
+			/** @type {string[]} */
+			const lines = [...edges]
 				.sort(sortEdges)
-				.map(edge => `${edge.source.name} -> ${edge.target.name}`);
+				.map((edge) => `${edge.source.name} -> ${edge.target.name}`);
 			lines.unshift("Circular dependency found in compiler dependencies.");
 			errors.unshift(lines.join("\n"));
 		}
@@ -151,17 +395,38 @@ module.exports = class MultiCompiler extends Tapable {
 		return true;
 	}
 
+	// TODO webpack 6 remove
+	/**
+	 * Run with dependencies.
+	 * @deprecated This method should have been private
+	 * @param {Compiler[]} compilers the child compilers
+	 * @param {RunWithDependenciesHandler} fn a handler to run for each compiler
+	 * @param {Callback} callback the compiler's handler
+	 * @returns {void}
+	 */
 	runWithDependencies(compilers, fn, callback) {
+		/** @type {Set} */
 		const fulfilledNames = new Set();
 		let remainingCompilers = compilers;
-		const isDependencyFulfilled = d => fulfilledNames.has(d);
+		/**
+		 * Checks whether this multi compiler is dependency fulfilled.
+		 * @param {string} d dependency
+		 * @returns {boolean} when dependency was fulfilled
+		 */
+		const isDependencyFulfilled = (d) => fulfilledNames.has(d);
+		/**
+		 * Gets ready compilers.
+		 * @returns {Compiler[]} compilers
+		 */
 		const getReadyCompilers = () => {
-			let readyCompilers = [];
-			let list = remainingCompilers;
+			/** @type {Compiler[]} */
+			const readyCompilers = [];
+			const list = remainingCompilers;
 			remainingCompilers = [];
 			for (const c of list) {
+				const dependencies = this.dependencies.get(c);
 				const ready =
-					!c.dependencies || c.dependencies.every(isDependencyFulfilled);
+					!dependencies || dependencies.every(isDependencyFulfilled);
 				if (ready) {
 					readyCompilers.push(c);
 				} else {
@@ -170,107 +435,326 @@ module.exports = class MultiCompiler extends Tapable {
 			}
 			return readyCompilers;
 		};
-		const runCompilers = callback => {
-			if (remainingCompilers.length === 0) return callback();
+		/**
+		 * Processes the provided stat.
+		 * @param {Callback} callback callback
+		 * @returns {void}
+		 */
+		const runCompilers = (callback) => {
+			if (remainingCompilers.length === 0) return callback(null);
 			asyncLib.map(
 				getReadyCompilers(),
 				(compiler, callback) => {
-					fn(compiler, err => {
+					fn(compiler, (err) => {
 						if (err) return callback(err);
-						fulfilledNames.add(compiler.name);
+						fulfilledNames.add(/** @type {string} */ (compiler.name));
 						runCompilers(callback);
 					});
 				},
-				callback
+				(err, results) => {
+					callback(/** @type {Error | null} */ (err), results);
+				}
 			);
 		};
 		runCompilers(callback);
 	}
 
-	watch(watchOptions, handler) {
-		if (this.running) return handler(new ConcurrentCompilationError());
-
-		let watchings = [];
-		let allStats = this.compilers.map(() => null);
-		let compilerStatus = this.compilers.map(() => false);
-		if (this.validateDependencies(handler)) {
-			this.running = true;
-			this.runWithDependencies(
-				this.compilers,
-				(compiler, callback) => {
-					const compilerIdx = this.compilers.indexOf(compiler);
-					let firstRun = true;
-					let watching = compiler.watch(
-						Array.isArray(watchOptions)
-							? watchOptions[compilerIdx]
-							: watchOptions,
-						(err, stats) => {
-							if (err) handler(err);
-							if (stats) {
-								allStats[compilerIdx] = stats;
-								compilerStatus[compilerIdx] = "new";
-								if (compilerStatus.every(Boolean)) {
-									const freshStats = allStats.filter((s, idx) => {
-										return compilerStatus[idx] === "new";
-									});
-									compilerStatus.fill(true);
-									const multiStats = new MultiStats(freshStats);
-									handler(null, multiStats);
-								}
-							}
-							if (firstRun && !err) {
-								firstRun = false;
-								callback();
-							}
-						}
-					);
-					watchings.push(watching);
+	/**
+	 * Returns result of setup.
+	 * @template SetupResult
+	 * @param {(compiler: Compiler, index: number, doneCallback: Callback, isBlocked: () => boolean, setChanged: () => void, setInvalid: () => void) => SetupResult} setup setup a single compiler
+	 * @param {(compiler: Compiler, setupResult: SetupResult, callback: Callback) => void} run run/continue a single compiler
+	 * @param {Callback} callback callback when all compilers are done, result includes Stats of all changed compilers
+	 * @returns {SetupResult[]} result of setup
+	 */
+	_runGraph(setup, run, callback) {
+		/** @typedef {{ compiler: Compiler, setupResult: undefined | SetupResult, result: undefined | Stats, state: "pending" | "blocked" | "queued" | "starting" | "running" | "running-outdated" | "done", children: Node[], parents: Node[] }} Node */
+
+		// State transitions for nodes:
+		// -> blocked (initial)
+		// blocked -> starting [running++] (when all parents done)
+		// queued -> starting [running++] (when processing the queue)
+		// starting -> running (when run has been called)
+		// running -> done [running--] (when compilation is done)
+		// done -> pending (when invalidated from file change)
+		// pending -> blocked [add to queue] (when invalidated from aggregated changes)
+		// done -> blocked [add to queue] (when invalidated, from parent invalidation)
+		// running -> running-outdated (when invalidated, either from change or parent invalidation)
+		// running-outdated -> blocked [running--] (when compilation is done)
+
+		/** @type {Node[]} */
+		const nodes = this.compilers.map((compiler) => ({
+			compiler,
+			setupResult: undefined,
+			result: undefined,
+			state: "blocked",
+			children: [],
+			parents: []
+		}));
+		/** @type {Map} */
+		const compilerToNode = new Map();
+		for (const node of nodes) {
+			compilerToNode.set(/** @type {string} */ (node.compiler.name), node);
+		}
+		for (const node of nodes) {
+			const dependencies = this.dependencies.get(node.compiler);
+			if (!dependencies) continue;
+			for (const dep of dependencies) {
+				const parent = /** @type {Node} */ (compilerToNode.get(dep));
+				node.parents.push(parent);
+				parent.children.push(node);
+			}
+		}
+		/** @type {ArrayQueue} */
+		const queue = new ArrayQueue();
+		for (const node of nodes) {
+			if (node.parents.length === 0) {
+				node.state = "queued";
+				queue.enqueue(node);
+			}
+		}
+		let errored = false;
+		let running = 0;
+		let duringSetup = true;
+		/** @type {Error | null} */
+		let setupError = null;
+		const parallelism = /** @type {number} */ (this._options.parallelism);
+		/**
+		 * Closes all node watchings.
+		 * @param {() => void} onClosed called when every watching is closed
+		 * @returns {void}
+		 */
+		const closeWatchings = (onClosed) => {
+			asyncLib.each(
+				nodes,
+				(node, callback) => {
+					if (node.compiler.watching) {
+						node.compiler.watching.close(callback);
+					} else {
+						callback();
+					}
 				},
-				() => {
-					// ignore
+				onClosed
+			);
+		};
+		/**
+		 * Processes the provided node.
+		 * @param {Node} node node
+		 * @param {(Error | null)=} err error
+		 * @param {Stats=} stats result
+		 * @returns {void}
+		 */
+		const nodeDone = (node, err, stats) => {
+			if (errored) return;
+			if (err) {
+				errored = true;
+				// During setup later watchings do not exist yet — defer the close
+				// sweep until the setup loop created them all, or they would leak
+				if (duringSetup) {
+					setupError = err;
+					return;
 				}
+				return closeWatchings(() => callback(err));
+			}
+			node.result = stats;
+			running--;
+			if (node.state === "running") {
+				node.state = "done";
+				for (const child of node.children) {
+					if (child.state === "blocked") queue.enqueue(child);
+				}
+			} else if (node.state === "running-outdated") {
+				node.state = "blocked";
+				queue.enqueue(node);
+			}
+			processQueue();
+		};
+		/**
+		 * Node invalid from parent.
+		 * @param {Node} node node
+		 * @returns {void}
+		 */
+		const nodeInvalidFromParent = (node) => {
+			if (node.state === "done") {
+				node.state = "blocked";
+			} else if (node.state === "running") {
+				node.state = "running-outdated";
+			}
+			for (const child of node.children) {
+				nodeInvalidFromParent(child);
+			}
+		};
+		/**
+		 * Processes the provided node.
+		 * @param {Node} node node
+		 * @returns {void}
+		 */
+		const nodeInvalid = (node) => {
+			if (node.state === "done") {
+				node.state = "pending";
+			} else if (node.state === "running") {
+				node.state = "running-outdated";
+			}
+			for (const child of node.children) {
+				nodeInvalidFromParent(child);
+			}
+		};
+		/**
+		 * Processes the provided node.
+		 * @param {Node} node node
+		 * @returns {void}
+		 */
+		const nodeChange = (node) => {
+			nodeInvalid(node);
+			if (node.state === "pending") {
+				node.state = "blocked";
+			}
+			if (node.state === "blocked") {
+				queue.enqueue(node);
+				processQueue();
+			}
+		};
+
+		/** @type {SetupResult[]} */
+		const setupResults = [];
+		for (const [i, node] of nodes.entries()) {
+			setupResults.push(
+				(node.setupResult = setup(
+					node.compiler,
+					i,
+					nodeDone.bind(null, node),
+					() => node.state !== "starting" && node.state !== "running",
+					() => nodeChange(node),
+					() => nodeInvalid(node)
+				))
 			);
 		}
+		let processing = true;
+		const processQueue = () => {
+			if (processing) return;
+			processing = true;
+			process.nextTick(processQueueWorker);
+		};
+		const processQueueWorker = () => {
+			// eslint-disable-next-line no-unmodified-loop-condition
+			while (running < parallelism && queue.length > 0 && !errored) {
+				const node = /** @type {Node} */ (queue.dequeue());
+				if (
+					node.state === "queued" ||
+					(node.state === "blocked" &&
+						node.parents.every((p) => p.state === "done"))
+				) {
+					running++;
+					node.state = "starting";
+					run(
+						node.compiler,
+						/** @type {SetupResult} */ (node.setupResult),
+						nodeDone.bind(null, node)
+					);
+					node.state = "running";
+				}
+			}
+			processing = false;
+			if (
+				!errored &&
+				running === 0 &&
+				nodes.every((node) => node.state === "done")
+			) {
+				/** @type {Stats[]} */
+				const stats = [];
+				for (const node of nodes) {
+					const result = node.result;
+					if (result) {
+						node.result = undefined;
+						stats.push(result);
+					}
+				}
+				if (stats.length > 0) {
+					callback(null, new MultiStats(stats));
+				}
+			}
+		};
+		duringSetup = false;
+		if (setupError) {
+			// The setup loop finished, every watching exists now — safe to sweep
+			const err = setupError;
+			closeWatchings(() => callback(err));
+		} else {
+			processQueueWorker();
+		}
+		return setupResults;
+	}
+
+	/**
+	 * Returns a compiler watcher.
+	 * @param {WatchOptions | WatchOptions[]} watchOptions the watcher's options
+	 * @param {Callback} handler signals when the call finishes
+	 * @returns {MultiWatching | undefined} a compiler watcher
+	 */
+	watch(watchOptions, handler) {
+		if (this.running) {
+			handler(new (getConcurrentCompilationError())());
+			return;
+		}
+		// Only mark as running after validation, so a retry reports the real error
+		if (!this.validateDependencies(handler)) {
+			this.watching = new MultiWatching([], this);
+			return this.watching;
+		}
+		this.running = true;
 
-		return new MultiWatching(watchings, this);
+		const watchings = this._runGraph(
+			(compiler, idx, callback, isBlocked, setChanged, setInvalid) => {
+				const watching = compiler.watch(
+					Array.isArray(watchOptions) ? watchOptions[idx] : watchOptions,
+					callback
+				);
+				if (watching) {
+					watching._onInvalid = setInvalid;
+					watching._onChange = setChanged;
+					watching._isBlocked = isBlocked;
+				}
+				return watching;
+			},
+			(compiler, watching, _callback) => {
+				if (compiler.watching !== watching) return;
+				if (!watching.running) watching.invalidate();
+			},
+			(err, stats) => {
+				// A fatal error tears the whole graph down — allow watching again
+				if (err) this.running = false;
+				handler(err, stats);
+			}
+		);
+		this.watching = new MultiWatching(watchings, this);
+		return this.watching;
 	}
 
+	/**
+	 * Processes the provided multi stat.
+	 * @param {Callback} callback signals when the call finishes
+	 * @returns {void}
+	 */
 	run(callback) {
 		if (this.running) {
-			return callback(new ConcurrentCompilationError());
+			callback(new (getConcurrentCompilationError())());
+			return;
 		}
+		// Only mark as running after validation, so a retry reports the real error
+		if (!this.validateDependencies(callback)) return;
+		this.running = true;
 
-		const finalCallback = (err, stats) => {
-			this.running = false;
+		this._runGraph(
+			() => {},
+			(compiler, setupResult, callback) => compiler.run(callback),
+			(err, stats) => {
+				this.running = false;
 
-			if (callback !== undefined) {
-				return callback(err, stats);
-			}
-		};
-
-		const allStats = this.compilers.map(() => null);
-		if (this.validateDependencies(callback)) {
-			this.running = true;
-			this.runWithDependencies(
-				this.compilers,
-				(compiler, callback) => {
-					const compilerIdx = this.compilers.indexOf(compiler);
-					compiler.run((err, stats) => {
-						if (err) {
-							return callback(err);
-						}
-						allStats[compilerIdx] = stats;
-						callback();
-					});
-				},
-				err => {
-					if (err) {
-						return finalCallback(err);
-					}
-					finalCallback(null, new MultiStats(allStats));
+				if (callback !== undefined) {
+					return callback(err, stats);
 				}
-			);
-		}
+			}
+		);
 	}
 
 	purgeInputFileSystem() {
@@ -280,4 +764,28 @@ module.exports = class MultiCompiler extends Tapable {
 			}
 		}
 	}
+
+	/**
+	 * Processes the provided error callback.
+	 * @param {ErrorCallback} callback signals when the compiler closes
+	 * @returns {void}
+	 */
+	close(callback) {
+		if (this.watching) {
+			// When there is still an active watching, close this first
+			this.watching.close((_err) => {
+				this.close(callback);
+			});
+			return;
+		}
+		asyncLib.each(
+			this.compilers,
+			(compiler, callback) => {
+				compiler.close(callback);
+			},
+			(error) => {
+				callback(/** @type {Error | null} */ (error));
+			}
+		);
+	}
 };
diff --git a/lib/MultiEntryPlugin.js b/lib/MultiEntryPlugin.js
deleted file mode 100644
index 29e8064298c..00000000000
--- a/lib/MultiEntryPlugin.js
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const MultiEntryDependency = require("./dependencies/MultiEntryDependency");
-const SingleEntryDependency = require("./dependencies/SingleEntryDependency");
-const MultiModuleFactory = require("./MultiModuleFactory");
-
-module.exports = class MultiEntryPlugin {
-	constructor(context, entries, name) {
-		this.context = context;
-		this.entries = entries;
-		this.name = name;
-	}
-
-	apply(compiler) {
-		compiler.hooks.compilation.tap(
-			"MultiEntryPlugin",
-			(compilation, { normalModuleFactory }) => {
-				const multiModuleFactory = new MultiModuleFactory();
-
-				compilation.dependencyFactories.set(
-					MultiEntryDependency,
-					multiModuleFactory
-				);
-				compilation.dependencyFactories.set(
-					SingleEntryDependency,
-					normalModuleFactory
-				);
-			}
-		);
-
-		compiler.hooks.make.tapAsync(
-			"MultiEntryPlugin",
-			(compilation, callback) => {
-				const { context, entries, name } = this;
-
-				const dep = MultiEntryPlugin.createDependency(entries, name);
-				compilation.addEntry(context, dep, name, callback);
-			}
-		);
-	}
-
-	static createDependency(entries, name) {
-		return new MultiEntryDependency(
-			entries.map((e, idx) => {
-				const dep = new SingleEntryDependency(e);
-				// Because entrypoints are not dependencies found in an
-				// existing module, we give it a synthetic id
-				dep.loc = `${name}:${100000 + idx}`;
-				return dep;
-			}),
-			name
-		);
-	}
-};
diff --git a/lib/MultiModule.js b/lib/MultiModule.js
deleted file mode 100644
index 6aee82f6694..00000000000
--- a/lib/MultiModule.js
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const Module = require("./Module");
-const Template = require("./Template");
-const { RawSource } = require("webpack-sources");
-
-class MultiModule extends Module {
-	constructor(context, dependencies, name) {
-		super("javascript/dynamic", context);
-
-		// Info from Factory
-		this.dependencies = dependencies;
-		this.name = name;
-		this._identifier = `multi ${this.dependencies
-			.map(d => d.request)
-			.join(" ")}`;
-	}
-
-	identifier() {
-		return this._identifier;
-	}
-
-	readableIdentifier(requestShortener) {
-		return `multi ${this.dependencies
-			.map(d => requestShortener.shorten(d.request))
-			.join(" ")}`;
-	}
-
-	build(options, compilation, resolver, fs, callback) {
-		this.built = true;
-		this.buildMeta = {};
-		this.buildInfo = {};
-		return callback();
-	}
-
-	needRebuild() {
-		return false;
-	}
-
-	size() {
-		return 16 + this.dependencies.length * 12;
-	}
-
-	updateHash(hash) {
-		hash.update("multi module");
-		hash.update(this.name || "");
-		super.updateHash(hash);
-	}
-
-	source(dependencyTemplates, runtimeTemplate) {
-		const str = [];
-		let idx = 0;
-		for (const dep of this.dependencies) {
-			if (dep.module) {
-				if (idx === this.dependencies.length - 1) {
-					str.push("module.exports = ");
-				}
-				str.push("__webpack_require__(");
-				if (runtimeTemplate.outputOptions.pathinfo) {
-					str.push(Template.toComment(dep.request));
-				}
-				str.push(`${JSON.stringify(dep.module.id)}`);
-				str.push(")");
-			} else {
-				const content = require("./dependencies/WebpackMissingModule").module(
-					dep.request
-				);
-				str.push(content);
-			}
-			str.push(";\n");
-			idx++;
-		}
-		return new RawSource(str.join(""));
-	}
-}
-
-module.exports = MultiModule;
diff --git a/lib/MultiModuleFactory.js b/lib/MultiModuleFactory.js
deleted file mode 100644
index 5d29b2056a5..00000000000
--- a/lib/MultiModuleFactory.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const { Tapable } = require("tapable");
-const MultiModule = require("./MultiModule");
-
-module.exports = class MultiModuleFactory extends Tapable {
-	constructor() {
-		super();
-		this.hooks = {};
-	}
-
-	create(data, callback) {
-		const dependency = data.dependencies[0];
-		callback(
-			null,
-			new MultiModule(data.context, dependency.dependencies, dependency.name)
-		);
-	}
-};
diff --git a/lib/MultiStats.js b/lib/MultiStats.js
index 4713524b8db..9d7c4187efe 100644
--- a/lib/MultiStats.js
+++ b/lib/MultiStats.js
@@ -2,90 +2,239 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
-const Stats = require("./Stats");
+const identifierUtils = require("./util/identifier");
+
+/** @typedef {import("../declarations/WebpackOptions").StatsOptions} StatsOptions */
+/** @typedef {import("../declarations/WebpackOptions").StatsValue} StatsValue */
+/** @typedef {import("./Compilation").CreateStatsOptionsContext} CreateStatsOptionsContext */
+/** @typedef {import("./Compilation").NormalizedStatsOptions} NormalizedStatsOptions */
+/** @typedef {import("./Stats")} Stats */
+/** @typedef {import("./stats/DefaultStatsFactoryPlugin").KnownStatsCompilation} KnownStatsCompilation */
+/** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsCompilation} StatsCompilation */
+/** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsError} StatsError */
+
+/**
+ * Returns indent.
+ * @param {string} str string
+ * @param {string} prefix pref
+ * @returns {string} indent
+ */
+const indent = (str, prefix) => {
+	const rem = str.replace(/\n([^\n])/g, `\n${prefix}$1`);
+	return prefix + rem;
+};
 
-const optionOrFallback = (optionValue, fallbackValue) =>
-	optionValue !== undefined ? optionValue : fallbackValue;
+/** @typedef {StatsOptions} MultiStatsOptions */
+/** @typedef {{ version: boolean, hash: boolean, errorsCount: boolean, warningsCount: boolean, errors: boolean, warnings: boolean, hints: boolean, hintsCount: boolean, children: NormalizedStatsOptions[] }} ChildOptions */
 
 class MultiStats {
+	/**
+	 * Creates an instance of MultiStats.
+	 * @param {Stats[]} stats the child stats
+	 */
 	constructor(stats) {
+		/** @type {Stats[]} */
 		this.stats = stats;
-		this.hash = stats.map(stat => stat.hash).join("");
 	}
 
+	get hash() {
+		return this.stats.map((stat) => stat.hash).join("");
+	}
+
+	/**
+	 * Checks whether this multi stats has errors.
+	 * @returns {boolean} true if a child compilation encountered an error
+	 */
 	hasErrors() {
-		return this.stats
-			.map(stat => stat.hasErrors())
-			.reduce((a, b) => a || b, false);
+		return this.stats.some((stat) => stat.hasErrors());
 	}
 
+	/**
+	 * Checks whether this multi stats has warnings.
+	 * @returns {boolean} true if a child compilation had a warning
+	 */
 	hasWarnings() {
-		return this.stats
-			.map(stat => stat.hasWarnings())
-			.reduce((a, b) => a || b, false);
+		return this.stats.some((stat) => stat.hasWarnings());
 	}
 
-	toJson(options, forToString) {
-		if (typeof options === "boolean" || typeof options === "string") {
-			options = Stats.presetToOptions(options);
-		} else if (!options) {
-			options = {};
-		}
-		const jsons = this.stats.map((stat, idx) => {
-			const childOptions = Stats.getChildOptions(options, idx);
-			const obj = stat.toJson(childOptions, forToString);
-			obj.name = stat.compilation && stat.compilation.name;
+	/**
+	 * Create child options.
+	 * @param {undefined | StatsValue} options stats options
+	 * @param {CreateStatsOptionsContext} context context
+	 * @returns {ChildOptions} context context
+	 */
+	_createChildOptions(options, context) {
+		const getCreateStatsOptions = () => {
+			if (!options) {
+				options = {};
+			}
+
+			const { children: childrenOptions = undefined, ...baseOptions } =
+				typeof options === "string"
+					? { preset: options }
+					: /** @type {StatsOptions} */ (options);
+
+			return { childrenOptions, baseOptions };
+		};
+
+		const children = this.stats.map((stat, idx) => {
+			if (typeof options === "boolean") {
+				return stat.compilation.createStatsOptions(options, context);
+			}
+			const { childrenOptions, baseOptions } = getCreateStatsOptions();
+			const childOptions = Array.isArray(childrenOptions)
+				? childrenOptions[idx]
+				: childrenOptions;
+			if (typeof childOptions === "boolean") {
+				return stat.compilation.createStatsOptions(childOptions, context);
+			}
+			return stat.compilation.createStatsOptions(
+				{
+					...baseOptions,
+					...(typeof childOptions === "string"
+						? { preset: childOptions }
+						: childOptions && typeof childOptions === "object"
+							? childOptions
+							: undefined)
+				},
+				context
+			);
+		});
+		return {
+			version: children.every((o) => o.version),
+			hash: children.every((o) => o.hash),
+			errorsCount: children.every((o) => o.errorsCount),
+			warningsCount: children.every((o) => o.warningsCount),
+			errors: children.every((o) => o.errors),
+			warnings: children.every((o) => o.warnings),
+			hints: children.every((o) => o.hints),
+			hintsCount: children.every((o) => o.hintsCount),
+			children
+		};
+	}
+
+	/**
+	 * Returns json output.
+	 * @param {StatsValue=} options stats options
+	 * @returns {StatsCompilation} json output
+	 */
+	toJson(options) {
+		const childOptions = this._createChildOptions(options, {
+			forToString: false
+		});
+		/** @type {KnownStatsCompilation} */
+		const obj = {};
+		obj.children = this.stats.map((stat, idx) => {
+			const obj = stat.toJson(childOptions.children[idx]);
+			const compilationName = stat.compilation.name;
+			const name =
+				compilationName &&
+				identifierUtils.makePathsRelative(
+					stat.compilation.compiler.context,
+					compilationName,
+					stat.compilation.compiler.root
+				);
+			obj.name = name;
 			return obj;
 		});
-		const showVersion =
-			typeof options.version === "undefined"
-				? jsons.every(j => j.version)
-				: options.version !== false;
-		const showHash =
-			typeof options.hash === "undefined"
-				? jsons.every(j => j.hash)
-				: options.hash !== false;
-		if (showVersion) {
-			for (const j of jsons) {
-				delete j.version;
+		if (childOptions.version) {
+			obj.version = obj.children[0].version;
+		}
+		if (childOptions.hash) {
+			obj.hash = obj.children.map((j) => j.hash).join("");
+		}
+		/**
+		 * Returns result.
+		 * @param {StatsCompilation} j stats error
+		 * @param {StatsError} obj Stats error
+		 * @returns {StatsError} result
+		 */
+		const mapError = (j, obj) => ({
+			...obj,
+			compilerPath: obj.compilerPath ? `${j.name}.${obj.compilerPath}` : j.name
+		});
+		if (childOptions.errors) {
+			obj.errors = [];
+			for (const j of obj.children) {
+				const errors =
+					/** @type {NonNullable} */
+					(j.errors);
+				for (const i of errors) {
+					obj.errors.push(mapError(j, i));
+				}
+			}
+		}
+		if (childOptions.warnings) {
+			obj.warnings = [];
+			for (const j of obj.children) {
+				const warnings =
+					/** @type {NonNullable} */
+					(j.warnings);
+				for (const i of warnings) {
+					obj.warnings.push(mapError(j, i));
+				}
+			}
+		}
+		if (childOptions.hints) {
+			obj.hints = [];
+			for (const j of obj.children) {
+				const hints =
+					/** @type {NonNullable} */
+					(j.hints);
+				for (const i of hints) {
+					obj.hints.push(mapError(j, i));
+				}
+			}
+		}
+		if (childOptions.errorsCount) {
+			obj.errorsCount = 0;
+			for (const j of obj.children) {
+				obj.errorsCount += /** @type {number} */ (j.errorsCount);
+			}
+		}
+		if (childOptions.warningsCount) {
+			obj.warningsCount = 0;
+			for (const j of obj.children) {
+				obj.warningsCount += /** @type {number} */ (j.warningsCount);
+			}
+		}
+		if (childOptions.hintsCount) {
+			obj.hintsCount = 0;
+			for (const j of obj.children) {
+				obj.hintsCount += /** @type {number} */ (j.hintsCount);
 			}
 		}
-		const obj = {
-			errors: jsons.reduce((arr, j) => {
-				return arr.concat(
-					j.errors.map(msg => {
-						return `(${j.name}) ${msg}`;
-					})
-				);
-			}, []),
-			warnings: jsons.reduce((arr, j) => {
-				return arr.concat(
-					j.warnings.map(msg => {
-						return `(${j.name}) ${msg}`;
-					})
-				);
-			}, [])
-		};
-		if (showVersion) obj.version = require("../package.json").version;
-		if (showHash) obj.hash = this.hash;
-		if (options.children !== false) obj.children = jsons;
 		return obj;
 	}
 
+	/**
+	 * Returns a string representation.
+	 * @param {StatsValue=} options stats options
+	 * @returns {string} string output
+	 */
 	toString(options) {
-		if (typeof options === "boolean" || typeof options === "string") {
-			options = Stats.presetToOptions(options);
-		} else if (!options) {
-			options = {};
-		}
-
-		const useColors = optionOrFallback(options.colors, false);
-
-		const obj = this.toJson(options, true);
-
-		return Stats.jsonToString(obj, useColors);
+		const childOptions = this._createChildOptions(options, {
+			forToString: true
+		});
+		const results = this.stats.map((stat, idx) => {
+			const str = stat.toString(childOptions.children[idx]);
+			const compilationName = stat.compilation.name;
+			const name =
+				compilationName &&
+				identifierUtils
+					.makePathsRelative(
+						stat.compilation.compiler.context,
+						compilationName,
+						stat.compilation.compiler.root
+					)
+					.replace(/\|/g, " ");
+			if (!str) return str;
+			return name ? `${name}:\n${indent(str, "  ")}` : str;
+		});
+		return results.filter(Boolean).join("\n\n");
 	}
 }
 
diff --git a/lib/MultiWatching.js b/lib/MultiWatching.js
index 48e012c871c..d1f0ddebaf7 100644
--- a/lib/MultiWatching.js
+++ b/lib/MultiWatching.js
@@ -2,33 +2,78 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
 const asyncLib = require("neo-async");
 
+/** @typedef {import("./MultiCompiler")} MultiCompiler */
+/** @typedef {import("./Watching")} Watching */
+/** @typedef {import("./webpack").ErrorCallback} ErrorCallback */
+
 class MultiWatching {
+	/**
+	 * Creates an instance of MultiWatching.
+	 * @param {Watching[]} watchings child compilers' watchers
+	 * @param {MultiCompiler} compiler the compiler
+	 */
 	constructor(watchings, compiler) {
+		/** @type {Watching[]} */
 		this.watchings = watchings;
+		/** @type {MultiCompiler} */
 		this.compiler = compiler;
 	}
 
-	invalidate() {
+	/**
+	 * Processes the provided error callback.
+	 * @param {ErrorCallback=} callback signals when the build has completed again
+	 * @returns {void}
+	 */
+	invalidate(callback) {
+		if (callback) {
+			asyncLib.each(
+				this.watchings,
+				(watching, callback) => watching.invalidate(callback),
+				(err) => {
+					callback(/** @type {Error | null} */ (err));
+				}
+			);
+		} else {
+			for (const watching of this.watchings) {
+				watching.invalidate();
+			}
+		}
+	}
+
+	suspend() {
+		for (const watching of this.watchings) {
+			watching.suspend();
+		}
+	}
+
+	resume() {
 		for (const watching of this.watchings) {
-			watching.invalidate();
+			watching.resume();
 		}
 	}
 
+	/**
+	 * Processes the provided error callback.
+	 * @param {ErrorCallback} callback signals when the watcher is closed
+	 * @returns {void}
+	 */
 	close(callback) {
-		asyncLib.forEach(
+		asyncLib.each(
 			this.watchings,
 			(watching, finishedCallback) => {
 				watching.close(finishedCallback);
 			},
-			err => {
+			(err) => {
+				if (this.compiler.watching === this) this.compiler.watching = undefined;
 				this.compiler.hooks.watchClose.call();
 				if (typeof callback === "function") {
 					this.compiler.running = false;
-					callback(err);
+					callback(/** @type {Error | null} */ (err));
 				}
 			}
 		);
diff --git a/lib/NamedChunksPlugin.js b/lib/NamedChunksPlugin.js
deleted file mode 100644
index 0cb5b6bf3d1..00000000000
--- a/lib/NamedChunksPlugin.js
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-class NamedChunksPlugin {
-	static defaultNameResolver(chunk) {
-		return chunk.name || null;
-	}
-
-	constructor(nameResolver) {
-		this.nameResolver = nameResolver || NamedChunksPlugin.defaultNameResolver;
-	}
-
-	apply(compiler) {
-		compiler.hooks.compilation.tap("NamedChunksPlugin", compilation => {
-			compilation.hooks.beforeChunkIds.tap("NamedChunksPlugin", chunks => {
-				for (const chunk of chunks) {
-					if (chunk.id === null) {
-						chunk.id = this.nameResolver(chunk);
-					}
-				}
-			});
-		});
-	}
-}
-
-module.exports = NamedChunksPlugin;
diff --git a/lib/NamedModulesPlugin.js b/lib/NamedModulesPlugin.js
deleted file mode 100644
index a3857ac8e90..00000000000
--- a/lib/NamedModulesPlugin.js
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const createHash = require("./util/createHash");
-const RequestShortener = require("./RequestShortener");
-
-const getHash = str => {
-	const hash = createHash("md4");
-	hash.update(str);
-	return hash.digest("hex").substr(0, 4);
-};
-
-class NamedModulesPlugin {
-	constructor(options) {
-		this.options = options || {};
-	}
-
-	apply(compiler) {
-		compiler.hooks.compilation.tap("NamedModulesPlugin", compilation => {
-			compilation.hooks.beforeModuleIds.tap("NamedModulesPlugin", modules => {
-				const namedModules = new Map();
-				const context = this.options.context || compiler.options.context;
-
-				for (const module of modules) {
-					if (module.id === null && module.libIdent) {
-						module.id = module.libIdent({ context });
-					}
-
-					if (module.id !== null) {
-						const namedModule = namedModules.get(module.id);
-						if (namedModule !== undefined) {
-							namedModule.push(module);
-						} else {
-							namedModules.set(module.id, [module]);
-						}
-					}
-				}
-
-				for (const namedModule of namedModules.values()) {
-					if (namedModule.length > 1) {
-						for (const module of namedModule) {
-							const requestShortener = new RequestShortener(context);
-							module.id = `${module.id}?${getHash(
-								requestShortener.shorten(module.identifier())
-							)}`;
-						}
-					}
-				}
-			});
-		});
-	}
-}
-
-module.exports = NamedModulesPlugin;
diff --git a/lib/NoEmitOnErrorsPlugin.js b/lib/NoEmitOnErrorsPlugin.js
index 2c37c497989..5df1dc5b6c2 100644
--- a/lib/NoEmitOnErrorsPlugin.js
+++ b/lib/NoEmitOnErrorsPlugin.js
@@ -2,15 +2,25 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
+/** @typedef {import("./Compiler")} Compiler */
+
+const PLUGIN_NAME = "NoEmitOnErrorsPlugin";
+
 class NoEmitOnErrorsPlugin {
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
 	apply(compiler) {
-		compiler.hooks.shouldEmit.tap("NoEmitOnErrorsPlugin", compilation => {
+		compiler.hooks.shouldEmit.tap(PLUGIN_NAME, (compilation) => {
 			if (compilation.getStats().hasErrors()) return false;
 		});
-		compiler.hooks.compilation.tap("NoEmitOnErrorsPlugin", compilation => {
-			compilation.hooks.shouldRecord.tap("NoEmitOnErrorsPlugin", () => {
+		compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
+			compilation.hooks.shouldRecord.tap(PLUGIN_NAME, () => {
 				if (compilation.getStats().hasErrors()) return false;
 			});
 		});
diff --git a/lib/NoModeWarning.js b/lib/NoModeWarning.js
deleted file mode 100644
index 1fbc3426af9..00000000000
--- a/lib/NoModeWarning.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const WebpackError = require("./WebpackError");
-
-module.exports = class NoModeWarning extends WebpackError {
-	constructor(modules) {
-		super();
-
-		this.name = "NoModeWarning";
-		this.message =
-			"configuration\n" +
-			"The 'mode' option has not been set, webpack will fallback to 'production' for this value. " +
-			"Set 'mode' option to 'development' or 'production' to enable defaults for each environment.\n" +
-			"You can also set it to 'none' to disable any default behavior. " +
-			"Learn more: https://webpack.js.org/concepts/mode/";
-
-		Error.captureStackTrace(this, this.constructor);
-	}
-};
diff --git a/lib/NodeStuffPlugin.js b/lib/NodeStuffPlugin.js
index 9ee407cd6df..9ad10e11690 100644
--- a/lib/NodeStuffPlugin.js
+++ b/lib/NodeStuffPlugin.js
@@ -2,178 +2,626 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
-const path = require("path");
-const ParserHelpers = require("./ParserHelpers");
+const {
+	JAVASCRIPT_MODULE_TYPE_AUTO,
+	JAVASCRIPT_MODULE_TYPE_DYNAMIC,
+	JAVASCRIPT_MODULE_TYPE_ESM
+} = require("./ModuleTypeConstants");
+const RuntimeGlobals = require("./RuntimeGlobals");
+const CachedConstDependency = require("./dependencies/CachedConstDependency");
 const ConstDependency = require("./dependencies/ConstDependency");
+const ExternalModuleDependency = require("./dependencies/ExternalModuleDependency");
+const ExternalModuleInitFragmentDependency = require("./dependencies/ExternalModuleInitFragmentDependency");
+const ImportMetaPlugin = require("./dependencies/ImportMetaPlugin");
+const { evaluateToString } = require("./javascript/JavascriptParserHelpers");
+const { relative } = require("./util/fs");
+const { parseResource } = require("./util/identifier");
+const memoize = require("./util/memoize");
+
+const getNodeStuffInWebError = memoize(() =>
+	require("./errors/NodeStuffInWebError")
+);
+
+/** @typedef {import("../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */
+/** @typedef {import("../declarations/WebpackOptions").NodeOptions} NodeOptions */
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */
+/** @typedef {import("./NormalModule")} NormalModule */
+/** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */
+/** @typedef {import("./javascript/JavascriptParser").Expression} Expression */
+/** @typedef {import("./javascript/JavascriptParser").Range} Range */
+/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
 
-const NullFactory = require("./NullFactory");
+const PLUGIN_NAME = "NodeStuffPlugin";
+const URL_MODULE_CONSTANT_FUNCTION_NAME = "__webpack_fileURLToPath__";
+
+const { IMPORT_META_DIRNAME, IMPORT_META_FILENAME, isImportMetaFieldEnabled } =
+	ImportMetaPlugin;
 
 class NodeStuffPlugin {
+	/**
+	 * Creates an instance of NodeStuffPlugin.
+	 * @param {NodeOptions} options options
+	 */
 	constructor(options) {
+		/** @type {NodeOptions} */
 		this.options = options;
 	}
 
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
 	apply(compiler) {
-		const options = this.options;
+		const { options } = this;
+
 		compiler.hooks.compilation.tap(
-			"NodeStuffPlugin",
+			PLUGIN_NAME,
 			(compilation, { normalModuleFactory }) => {
-				compilation.dependencyFactories.set(ConstDependency, new NullFactory());
 				compilation.dependencyTemplates.set(
-					ConstDependency,
-					new ConstDependency.Template()
+					ExternalModuleDependency,
+					new ExternalModuleDependency.Template()
+				);
+				compilation.dependencyTemplates.set(
+					ExternalModuleInitFragmentDependency,
+					new ExternalModuleInitFragmentDependency.Template()
 				);
 
-				const handler = (parser, parserOptions) => {
-					if (parserOptions.node === false) return;
-
-					let localOptions = options;
-					if (parserOptions.node) {
-						localOptions = Object.assign({}, localOptions, parserOptions.node);
-					}
+				/**
+				 * Processes the provided parser.
+				 * @param {JavascriptParser} parser the parser
+				 * @param {NodeOptions} nodeOptions options
+				 * @returns {void}
+				 */
+				const globalHandler = (parser, nodeOptions) => {
+					/**
+					 * Returns const dependency.
+					 * @param {Expression} expr expression
+					 * @returns {ConstDependency} const dependency
+					 */
+					const getGlobalDep = (expr) => {
+						if (compilation.outputOptions.environment.globalThis) {
+							return new ConstDependency(
+								"globalThis",
+								/** @type {Range} */ (expr.range)
+							);
+						}
 
-					const setConstant = (expressionName, value) => {
-						parser.hooks.expression
-							.for(expressionName)
-							.tap("NodeStuffPlugin", () => {
-								parser.state.current.addVariable(
-									expressionName,
-									JSON.stringify(value)
-								);
-								return true;
-							});
+						return new ConstDependency(
+							RuntimeGlobals.global,
+							/** @type {Range} */ (expr.range),
+							[RuntimeGlobals.global]
+						);
 					};
 
-					const setModuleConstant = (expressionName, fn) => {
-						parser.hooks.expression
-							.for(expressionName)
-							.tap("NodeStuffPlugin", () => {
-								parser.state.current.addVariable(
-									expressionName,
-									JSON.stringify(fn(parser.state.module))
-								);
-								return true;
-							});
-					};
-					const context = compiler.context;
-					if (localOptions.__filename === "mock") {
-						setConstant("__filename", "/index.js");
-					} else if (localOptions.__filename) {
-						setModuleConstant("__filename", module =>
-							path.relative(context, module.resource)
-						);
-					}
-					parser.hooks.evaluateIdentifier
-						.for("__filename")
-						.tap("NodeStuffPlugin", expr => {
-							if (!parser.state.module) return;
-							const resource = parser.state.module.resource;
-							const i = resource.indexOf("?");
-							return ParserHelpers.evaluateToString(
-								i < 0 ? resource : resource.substr(0, i)
-							)(expr);
+					const withWarning = nodeOptions.global === "warn";
+
+					parser.hooks.expression.for("global").tap(PLUGIN_NAME, (expr) => {
+						const dep = getGlobalDep(expr);
+						dep.loc = parser.getLocation(expr);
+						parser.state.module.addPresentationalDependency(dep);
+
+						if (withWarning) {
+							parser.state.module.addWarning(
+								new (getNodeStuffInWebError())(
+									dep.loc,
+									"global",
+									"The global namespace object is a Node.js feature and isn't available in browsers."
+								)
+							);
+						}
+					});
+
+					parser.hooks.rename.for("global").tap(PLUGIN_NAME, (expr) => {
+						const dep = getGlobalDep(expr);
+						dep.loc = parser.getLocation(expr);
+						parser.state.module.addPresentationalDependency(dep);
+						return false;
+					});
+				};
+
+				const hooks = ImportMetaPlugin.getCompilationHooks(compilation);
+
+				/**
+				 * Sets module constant.
+				 * @param {JavascriptParser} parser the parser
+				 * @param {"__filename" | "__dirname" | "import.meta.filename" | "import.meta.dirname"} expressionName expression name
+				 * @param {(module: NormalModule) => string} fn function
+				 * @param {"filename" | "dirname"} property a property
+				 * @returns {void}
+				 */
+				const setModuleConstant = (parser, expressionName, fn, property) => {
+					parser.hooks.expression
+						.for(expressionName)
+						.tap(PLUGIN_NAME, (expr) => {
+							const dep = new ConstDependency(
+								fn(parser.state.module),
+								/** @type {Range} */
+								(expr.range)
+							);
+							dep.loc = parser.getLocation(expr);
+							parser.state.module.addPresentationalDependency(dep);
+							return true;
 						});
-					if (localOptions.__dirname === "mock") {
-						setConstant("__dirname", "/");
-					} else if (localOptions.__dirname) {
-						setModuleConstant("__dirname", module =>
-							path.relative(context, module.context)
-						);
-					}
-					parser.hooks.evaluateIdentifier
-						.for("__dirname")
-						.tap("NodeStuffPlugin", expr => {
-							if (!parser.state.module) return;
-							return ParserHelpers.evaluateToString(
-								parser.state.module.context
-							)(expr);
+
+					if (
+						expressionName === IMPORT_META_FILENAME ||
+						expressionName === IMPORT_META_DIRNAME
+					) {
+						hooks.propertyInDestructuring.tap(PLUGIN_NAME, (usingProperty) => {
+							if (usingProperty.id === property) {
+								return `${property}: ${fn(parser.state.module)},`;
+							}
 						});
+					}
+				};
+
+				/**
+				 * Sets cached module constant.
+				 * @param {JavascriptParser} parser the parser
+				 * @param {"__filename" | "__dirname" | "import.meta.filename" | "import.meta.dirname"} expressionName expression name
+				 * @param {(module: NormalModule) => string} fn function
+				 * @param {"filename" | "dirname"} property a property
+				 * @param {string=} warning warning
+				 * @returns {void}
+				 */
+				const setCachedModuleConstant = (
+					parser,
+					expressionName,
+					fn,
+					property,
+					warning
+				) => {
 					parser.hooks.expression
-						.for("require.main")
-						.tap(
-							"NodeStuffPlugin",
-							ParserHelpers.toConstantDependencyWithWebpackRequire(
-								parser,
-								"__webpack_require__.c[__webpack_require__.s]"
-							)
-						);
-					parser.hooks.expression
-						.for("require.extensions")
-						.tap(
-							"NodeStuffPlugin",
-							ParserHelpers.expressionIsUnsupported(
-								parser,
-								"require.extensions is not supported by webpack. Use a loader instead."
-							)
-						);
-					parser.hooks.expression
-						.for("module.loaded")
-						.tap("NodeStuffPlugin", expr => {
-							parser.state.module.buildMeta.moduleConcatenationBailout =
-								"module.loaded";
-							return ParserHelpers.toConstantDependency(parser, "module.l")(
-								expr
+						.for(expressionName)
+						.tap(PLUGIN_NAME, (expr) => {
+							const dep = new CachedConstDependency(
+								JSON.stringify(fn(parser.state.module)),
+								/** @type {Range} */
+								(expr.range),
+								`__webpack_${property}__`
 							);
+							dep.loc = parser.getLocation(expr);
+							parser.state.module.addPresentationalDependency(dep);
+
+							if (warning) {
+								parser.state.module.addWarning(
+									new (getNodeStuffInWebError())(
+										dep.loc,
+										expressionName,
+										warning
+									)
+								);
+							}
+
+							return true;
+						});
+
+					if (
+						expressionName === IMPORT_META_FILENAME ||
+						expressionName === IMPORT_META_DIRNAME
+					) {
+						hooks.propertyInDestructuring.tap(PLUGIN_NAME, (usingProperty) => {
+							if (property === usingProperty.id) {
+								if (warning) {
+									parser.state.module.addWarning(
+										new (getNodeStuffInWebError())(
+											parser.getLocation(usingProperty),
+											expressionName,
+											warning
+										)
+									);
+								}
+
+								return `${property}: ${JSON.stringify(
+									fn(parser.state.module)
+								)},`;
+							}
 						});
+					}
+				};
+
+				/**
+				 * Updates constant using the provided parser.
+				 * @param {JavascriptParser} parser the parser
+				 * @param {"__filename" | "__dirname" | "import.meta.filename" | "import.meta.dirname"} expressionName expression name
+				 * @param {string} value value
+				 * @param {"filename" | "dirname"} property a property
+				 * @param {string=} warning warning
+				 * @returns {void}
+				 */
+				const setConstant = (
+					parser,
+					expressionName,
+					value,
+					property,
+					warning
+				) =>
+					setCachedModuleConstant(
+						parser,
+						expressionName,
+						() => value,
+						property,
+						warning
+					);
+
+				/**
+				 * Sets url module constant.
+				 * @param {JavascriptParser} parser the parser
+				 * @param {"__filename" | "__dirname" | "import.meta.filename" | "import.meta.dirname"} expressionName expression name
+				 * @param {"dirname" | "filename"} property property
+				 * @param {() => string} value function to get value
+				 * @returns {void}
+				 */
+				const setUrlModuleConstant = (
+					parser,
+					expressionName,
+					property,
+					value
+				) => {
 					parser.hooks.expression
-						.for("module.id")
-						.tap("NodeStuffPlugin", expr => {
-							parser.state.module.buildMeta.moduleConcatenationBailout =
-								"module.id";
-							return ParserHelpers.toConstantDependency(parser, "module.i")(
-								expr
+						.for(expressionName)
+						.tap(PLUGIN_NAME, (expr) => {
+							// We use `CachedConstDependency` because of `eval` devtool, there is no `import.meta` inside `eval()`
+							const { importMetaName, environment, module } =
+								compilation.outputOptions;
+
+							// Generate `import.meta.dirname` and `import.meta.filename` when:
+							// - they are supported by the environment
+							// - it is a universal target, because we can't use `import mod from "node:url"; ` at the top file
+							if (
+								environment.importMetaDirnameAndFilename ||
+								(compiler.platform.web === null &&
+									compiler.platform.node === null &&
+									module)
+							) {
+								const dep = new CachedConstDependency(
+									`${importMetaName}.${property}`,
+									/** @type {Range} */
+									(expr.range),
+									`__webpack_${property}__`,
+									CachedConstDependency.PLACE_CHUNK
+								);
+
+								dep.loc = parser.getLocation(expr);
+								parser.state.module.addPresentationalDependency(dep);
+								return;
+							}
+
+							const dep = new ExternalModuleDependency(
+								"url",
+								[
+									{
+										name: "fileURLToPath",
+										value: URL_MODULE_CONSTANT_FUNCTION_NAME
+									}
+								],
+								undefined,
+								`${URL_MODULE_CONSTANT_FUNCTION_NAME}(${value()})`,
+								/** @type {Range} */ (expr.range),
+								`__webpack_${property}__`,
+								ExternalModuleDependency.PLACE_CHUNK
 							);
+							dep.loc = parser.getLocation(expr);
+							parser.state.module.addPresentationalDependency(dep);
+
+							return true;
 						});
-					parser.hooks.expression
-						.for("module.exports")
-						.tap("NodeStuffPlugin", () => {
-							const module = parser.state.module;
-							const isHarmony =
-								module.buildMeta && module.buildMeta.exportsType;
-							if (!isHarmony) return true;
+
+					if (
+						expressionName === IMPORT_META_FILENAME ||
+						expressionName === IMPORT_META_DIRNAME
+					) {
+						hooks.propertyInDestructuring.tap(PLUGIN_NAME, (usingProperty) => {
+							if (property === usingProperty.id) {
+								const { importMetaName, environment, module } =
+									compilation.outputOptions;
+
+								if (
+									environment.importMetaDirnameAndFilename ||
+									(compiler.platform.web === null &&
+										compiler.platform.node === null &&
+										module)
+								) {
+									const dep = new CachedConstDependency(
+										`${importMetaName}.${property}`,
+										null,
+										`__webpack_${property}__`,
+										CachedConstDependency.PLACE_CHUNK
+									);
+									dep.loc = parser.getLocation(usingProperty);
+									parser.state.module.addPresentationalDependency(dep);
+									return `${property}: __webpack_${property}__,`;
+								}
+
+								const dep = new ExternalModuleDependency(
+									"url",
+									[
+										{
+											name: "fileURLToPath",
+											value: URL_MODULE_CONSTANT_FUNCTION_NAME
+										}
+									],
+									undefined,
+									`${URL_MODULE_CONSTANT_FUNCTION_NAME}(${value()})`,
+									null,
+									`__webpack_${property}__`,
+									ExternalModuleDependency.PLACE_CHUNK
+								);
+
+								dep.loc = parser.getLocation(usingProperty);
+								parser.state.module.addPresentationalDependency(dep);
+
+								return `${property}: __webpack_${property}__,`;
+							}
 						});
-					parser.hooks.evaluateIdentifier
-						.for("module.hot")
-						.tap(
-							"NodeStuffPlugin",
-							ParserHelpers.evaluateToIdentifier("module.hot", false)
-						);
-					parser.hooks.expression.for("module").tap("NodeStuffPlugin", () => {
-						const module = parser.state.module;
-						const isHarmony = module.buildMeta && module.buildMeta.exportsType;
-						let moduleJsPath = path.join(
-							__dirname,
-							"..",
-							"buildin",
-							isHarmony ? "harmony-module.js" : "module.js"
-						);
-						if (module.context) {
-							moduleJsPath = path.relative(
-								parser.state.module.context,
-								moduleJsPath
-							);
-							if (!/^[A-Z]:/i.test(moduleJsPath)) {
-								moduleJsPath = `./${moduleJsPath.replace(/\\/g, "/")}`;
+					}
+				};
+
+				/**
+				 * Dirname and filename handler.
+				 * @param {JavascriptParser} parser the parser
+				 * @param {NodeOptions} nodeOptions options
+				 * @param {{ dirname: "__dirname" | "import.meta.dirname", filename: "__filename" | "import.meta.filename", dirnameEnabled?: boolean, filenameEnabled?: boolean }} identifiers options
+				 * @returns {void}
+				 */
+				const dirnameAndFilenameHandler = (
+					parser,
+					nodeOptions,
+					{ dirname, filename, dirnameEnabled = true, filenameEnabled = true }
+				) => {
+					// Keep `import.meta.filename` in code.
+					if (
+						filenameEnabled &&
+						nodeOptions.__filename === false &&
+						filename === IMPORT_META_FILENAME
+					) {
+						setModuleConstant(parser, filename, () => filename, "filename");
+					}
+
+					if (filenameEnabled && nodeOptions.__filename) {
+						switch (nodeOptions.__filename) {
+							case "mock":
+								setConstant(parser, filename, "/index.js", "filename");
+								break;
+							case "warn-mock":
+								setConstant(
+									parser,
+									filename,
+									"/index.js",
+									"filename",
+									"__filename is a Node.js feature and isn't available in browsers."
+								);
+								break;
+							case "node-module": {
+								const importMetaName = compilation.outputOptions.importMetaName;
+
+								setUrlModuleConstant(
+									parser,
+									filename,
+									"filename",
+									() => `${importMetaName}.url`
+								);
+								break;
 							}
+							case "eval-only":
+								// Keep `import.meta.filename` in the source code for the ES module output, or create a fallback using `import.meta.url` if possible
+								if (compilation.outputOptions.module) {
+									const { importMetaName } = compilation.outputOptions;
+
+									setUrlModuleConstant(
+										parser,
+										filename,
+										"filename",
+										() => `${importMetaName}.url`
+									);
+								}
+								// Replace `import.meta.filename` with `__filename` for the non-ES module output
+								else if (filename === IMPORT_META_FILENAME) {
+									setModuleConstant(
+										parser,
+										filename,
+										() => "__filename",
+										"filename"
+									);
+								}
+								break;
+							case true:
+								setCachedModuleConstant(
+									parser,
+									filename,
+									(module) =>
+										relative(
+											/** @type {InputFileSystem} */ (compiler.inputFileSystem),
+											compiler.context,
+											module.resource
+										),
+									"filename"
+								);
+								break;
 						}
-						return ParserHelpers.addParsedVariableToModule(
-							parser,
-							"module",
-							`require(${JSON.stringify(moduleJsPath)})(module)`
-						);
-					});
+
+						parser.hooks.evaluateIdentifier
+							.for("__filename")
+							.tap(PLUGIN_NAME, (expr) => {
+								if (!parser.state.module) return;
+								const resource = parseResource(parser.state.module.resource);
+								return evaluateToString(resource.path)(expr);
+							});
+					}
+
+					// Keep `import.meta.dirname` in code.
+					if (
+						dirnameEnabled &&
+						nodeOptions.__dirname === false &&
+						dirname === IMPORT_META_DIRNAME
+					) {
+						setModuleConstant(parser, dirname, () => dirname, "dirname");
+					}
+
+					if (dirnameEnabled && nodeOptions.__dirname) {
+						switch (nodeOptions.__dirname) {
+							case "mock":
+								setConstant(parser, dirname, "/", "dirname");
+								break;
+							case "warn-mock":
+								setConstant(
+									parser,
+									dirname,
+									"/",
+									"dirname",
+									"__dirname is a Node.js feature and isn't available in browsers."
+								);
+								break;
+							case "node-module": {
+								const importMetaName = compilation.outputOptions.importMetaName;
+
+								setUrlModuleConstant(
+									parser,
+									dirname,
+									"dirname",
+									() => `${importMetaName}.url.replace(/\\/(?:[^\\/]*)$/, "")`
+								);
+								break;
+							}
+							case "eval-only":
+								// Keep `import.meta.dirname` in the source code for the ES module output and replace `__dirname` on `import.meta.dirname`
+								if (compilation.outputOptions.module) {
+									const { importMetaName } = compilation.outputOptions;
+
+									setUrlModuleConstant(
+										parser,
+										dirname,
+										"dirname",
+										() => `${importMetaName}.url.replace(/\\/(?:[^\\/]*)$/, "")`
+									);
+								}
+								// Replace `import.meta.dirname` with `__dirname` for the non-ES module output
+								else if (dirname === IMPORT_META_DIRNAME) {
+									setModuleConstant(
+										parser,
+										dirname,
+										() => "__dirname",
+										"dirname"
+									);
+								}
+								break;
+							case true:
+								setCachedModuleConstant(
+									parser,
+									dirname,
+									(module) =>
+										relative(
+											/** @type {InputFileSystem} */ (compiler.inputFileSystem),
+											compiler.context,
+											/** @type {string} */ (module.context)
+										),
+									"dirname"
+								);
+								break;
+						}
+
+						parser.hooks.evaluateIdentifier
+							.for(dirname)
+							.tap(PLUGIN_NAME, (expr) => {
+								if (!parser.state.module) return;
+								return evaluateToString(
+									/** @type {string} */
+									(parser.state.module.context)
+								)(expr);
+							});
+					}
 				};
 
+				/**
+				 * Handles the hook callback for this code path.
+				 * @param {JavascriptParser} parser the parser
+				 * @param {JavascriptParserOptions} parserOptions the javascript parser options
+				 * @param {boolean} a true when we need to handle `__filename` and `__dirname`, otherwise false
+				 * @param {boolean} b true when we need to handle `import.meta.filename` and `import.meta.dirname`, otherwise false
+				 */
+				const handler = (parser, parserOptions, a, b) => {
+					if (b && parserOptions.node === false) {
+						if (isImportMetaFieldEnabled(parserOptions.importMeta, "dirname")) {
+							setModuleConstant(
+								parser,
+								IMPORT_META_DIRNAME,
+								() => IMPORT_META_DIRNAME,
+								"dirname"
+							);
+						}
+						if (
+							isImportMetaFieldEnabled(parserOptions.importMeta, "filename")
+						) {
+							setModuleConstant(
+								parser,
+								IMPORT_META_FILENAME,
+								() => IMPORT_META_FILENAME,
+								"filename"
+							);
+						}
+						return;
+					}
+
+					let localOptions = options;
+
+					if (parserOptions.node) {
+						localOptions = { ...localOptions, ...parserOptions.node };
+					}
+
+					if (localOptions.global !== false) {
+						globalHandler(parser, localOptions);
+					}
+
+					if (a) {
+						dirnameAndFilenameHandler(parser, localOptions, {
+							dirname: "__dirname",
+							filename: "__filename"
+						});
+					}
+
+					if (
+						b &&
+						(isImportMetaFieldEnabled(parserOptions.importMeta, "dirname") ||
+							isImportMetaFieldEnabled(parserOptions.importMeta, "filename"))
+					) {
+						dirnameAndFilenameHandler(parser, localOptions, {
+							dirname: IMPORT_META_DIRNAME,
+							filename: IMPORT_META_FILENAME,
+							dirnameEnabled: isImportMetaFieldEnabled(
+								parserOptions.importMeta,
+								"dirname"
+							),
+							filenameEnabled: isImportMetaFieldEnabled(
+								parserOptions.importMeta,
+								"filename"
+							)
+						});
+					}
+				};
+
+				normalModuleFactory.hooks.parser
+					.for(JAVASCRIPT_MODULE_TYPE_AUTO)
+					.tap(PLUGIN_NAME, (parser, parserOptions) => {
+						handler(parser, parserOptions, true, true);
+					});
 				normalModuleFactory.hooks.parser
-					.for("javascript/auto")
-					.tap("NodeStuffPlugin", handler);
+					.for(JAVASCRIPT_MODULE_TYPE_DYNAMIC)
+					.tap(PLUGIN_NAME, (parser, parserOptions) => {
+						handler(parser, parserOptions, true, false);
+					});
 				normalModuleFactory.hooks.parser
-					.for("javascript/dynamic")
-					.tap("NodeStuffPlugin", handler);
+					.for(JAVASCRIPT_MODULE_TYPE_ESM)
+					.tap(PLUGIN_NAME, (parser, parserOptions) => {
+						handler(parser, parserOptions, false, true);
+					});
 			}
 		);
 	}
 }
+
 module.exports = NodeStuffPlugin;
diff --git a/lib/NormalModule.js b/lib/NormalModule.js
index 1e65bce91a1..b0af0699a79 100644
--- a/lib/NormalModule.js
+++ b/lib/NormalModule.js
@@ -2,234 +2,1200 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
-"use strict";
 
-const path = require("path");
-const NativeModule = require("module");
+"use strict";
 
+const querystring = require("querystring");
+const {
+	AsyncSeriesBailHook,
+	HookMap,
+	SyncHook,
+	SyncWaterfallHook
+} = require("tapable");
 const {
 	CachedSource,
-	LineToLineMappedSource,
 	OriginalSource,
 	RawSource,
 	SourceMapSource
 } = require("webpack-sources");
-const { getContext, runLoaders } = require("loader-runner");
-
-const WebpackError = require("./WebpackError");
+const Dependency = require("./Dependency");
 const Module = require("./Module");
-const ModuleParseError = require("./ModuleParseError");
-const ModuleBuildError = require("./ModuleBuildError");
-const ModuleError = require("./ModuleError");
-const ModuleWarning = require("./ModuleWarning");
+const ModuleGraphConnection = require("./ModuleGraphConnection");
+const { JAVASCRIPT_MODULE_TYPE_AUTO } = require("./ModuleTypeConstants");
+const RuntimeGlobals = require("./RuntimeGlobals");
+const HarmonyImportSideEffectDependency = require("./dependencies/HarmonyImportSideEffectDependency");
+const HookWebpackError = require("./errors/HookWebpackError");
+const {
+	createLoaderContext,
+	getContext,
+	runLoaders
+} = require("./loaders/LoaderRunner");
+const LazySet = require("./util/LazySet");
+const { isSubset } = require("./util/SetHelpers");
+const { getScheme } = require("./util/URLAbsoluteSpecifier");
+const {
+	concatComparators,
+	keepOriginalOrder,
+	sortWithSourceOrder
+} = require("./util/comparators");
 const createHash = require("./util/createHash");
+const createHooksRegistry = require("./util/createHooksRegistry");
+const { createFakeHook } = require("./util/deprecation");
+const formatLocation = require("./util/formatLocation");
+const { join } = require("./util/fs");
+const {
+	ABSOLUTE_PATH_REGEXP,
+	absolutify,
+	contextify,
+	contextifySourceUrl
+} = require("./util/identifier");
+const makeSerializable = require("./util/makeSerializable");
+const memoize = require("./util/memoize");
+const parseJson = require("./util/parseJson");
+
+const getValidate = memoize(() => require("schema-utils").validate);
+
+const getExtractSourceMap = memoize(() => require("./util/extractSourceMap"));
+
+const getInvalidDependenciesModuleWarning = memoize(() =>
+	require("./errors/InvalidDependenciesModuleWarning")
+);
+
+const getUnhandledSchemeError = memoize(() =>
+	require("./errors/UnhandledSchemeError")
+);
+
+const getNonErrorEmittedError = memoize(() =>
+	require("./errors/NonErrorEmittedError")
+);
+
+const getModuleWarning = memoize(() => require("./errors/ModuleWarning"));
+
+const getModuleParseError = memoize(() => require("./errors/ModuleParseError"));
+
+const getModuleError = memoize(() => require("./errors/ModuleError"));
+
+const getModuleBuildError = memoize(() => require("./errors/ModuleBuildError"));
+
+/** @typedef {import("enhanced-resolve").ResolveContext} ResolveContext */
+/** @typedef {import("enhanced-resolve").ResolveRequest} ResolveRequest */
+/** @typedef {import("webpack-sources").Source} Source */
+/** @typedef {import("webpack-sources").RawSourceMap} RawSourceMap */
+/** @typedef {import("../declarations/WebpackOptions").ResolveOptions} ResolveOptions */
+/** @typedef {import("../declarations/WebpackOptions").NoParse} NoParse */
+/** @typedef {import("./Compilation")} Compilation */
+/** @typedef {import("./config/defaults").WebpackOptionsNormalizedWithDefaults} WebpackOptions */
+/** @typedef {import("./Dependency").UpdateHashContext} UpdateHashContext */
+/** @typedef {import("./Generator")} Generator */
+/** @typedef {import("./Generator").GenerateErrorFn} GenerateErrorFn */
+/** @typedef {import("./FileSystemInfo").Snapshot} Snapshot */
+/** @typedef {import("./Module").BuildInfo} BuildInfo */
+/** @typedef {import("./Module").FileSystemDependencies} FileSystemDependencies */
+/** @typedef {import("./Module").ValueCacheVersions} ValueCacheVersions */
+/** @typedef {import("./Module").BuildMeta} BuildMeta */
+/** @typedef {import("./Module").CodeGenerationContext} CodeGenerationContext */
+/** @typedef {import("./Module").CodeGenerationResult} CodeGenerationResult */
+/** @typedef {import("./Module").CodeGenerationResultData} CodeGenerationResultData */
+/** @typedef {import("./Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */
+/** @typedef {import("./Module").KnownBuildInfo} KnownBuildInfo */
+/** @typedef {import("./Module").LibIdentOptions} LibIdentOptions */
+/** @typedef {import("./Module").LibIdent} LibIdent */
+/** @typedef {import("./Module").NameForCondition} NameForCondition */
+/** @typedef {import("./Module").NeedBuildContext} NeedBuildContext */
+/** @typedef {import("./Module").NeedBuildCallback} NeedBuildCallback */
+/** @typedef {import("./Module").BuildCallback} BuildCallback */
+/** @typedef {import("./Module").RuntimeRequirements} RuntimeRequirements */
+/** @typedef {import("./Module").Sources} Sources */
+/** @typedef {import("./Module").SourceType} SourceType */
+/** @typedef {import("./Module").SourceTypes} SourceTypes */
+/** @typedef {import("./Module").UnsafeCacheData} UnsafeCacheData */
+/** @typedef {import("./ModuleGraph")} ModuleGraph */
+/** @typedef {import("./ModuleGraphConnection").ConnectionState} ConnectionState */
+/** @typedef {import("./NormalModuleFactory")} NormalModuleFactory */
+/** @typedef {import("./NormalModuleFactory").NormalModuleTypes} NormalModuleTypes */
+/** @typedef {import("./NormalModuleFactory").ParserByType} ParserByType */
+/** @typedef {import("./NormalModuleFactory").ParserOptionsByType} ParserOptionsByType */
+/** @typedef {import("./NormalModuleFactory").GeneratorByType} GeneratorByType */
+/** @typedef {import("./NormalModuleFactory").GeneratorOptionsByType} GeneratorOptionsByType */
+/** @typedef {import("./NormalModuleFactory").ResourceSchemeData} ResourceSchemeData */
+/** @typedef {import("./Parser")} Parser */
+/** @typedef {import("./Parser").PreparsedAst} PreparsedAst */
+/** @typedef {import("./RequestShortener")} RequestShortener */
+/** @typedef {import("./ResolverFactory").ResolverWithOptions} ResolverWithOptions */
+/** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */
+/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */
+/** @typedef {import("./util/Hash")} Hash */
+/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
+/** @typedef {import("../declarations/WebpackOptions").HashFunction} HashFunction */
+/** @typedef {import("./util/identifier").AssociatedObjectForCache} AssociatedObjectForCache */
+/**
+ * @template T
+ * @typedef {import("./util/deprecation").FakeHook} FakeHook
+ */
 
-const asString = buf => {
-	if (Buffer.isBuffer(buf)) {
-		return buf.toString("utf-8");
-	}
-	return buf;
-};
+/** @typedef {{ [k: string]: EXPECTED_ANY }} ParserOptions */
+/** @typedef {{ [k: string]: EXPECTED_ANY }} GeneratorOptions */
 
-const asBuffer = str => {
-	if (!Buffer.isBuffer(str)) {
-		return Buffer.from(str, "utf-8");
+/**
+ * @template T
+ * @typedef {import("../declarations/LoaderContext").LoaderContext} LoaderContext
+ */
+
+/**
+ * @template T
+ * @typedef {import("../declarations/LoaderContext").NormalModuleLoaderContext} NormalModuleLoaderContext
+ */
+
+/** @typedef {(content: string) => boolean} NoParseFn */
+
+/**
+ * @param {NormalModule} mod the module
+ * @param {ModuleGraph} moduleGraph the module graph
+ * @param {Dependency} dep the dep that triggered the bailout
+ */
+const recordSideEffectsBailout = (mod, moduleGraph, dep) => {
+	if (mod._addedSideEffectsBailout === undefined) {
+		mod._addedSideEffectsBailout = new WeakSet();
+	} else if (mod._addedSideEffectsBailout.has(moduleGraph)) {
+		return;
 	}
-	return str;
+	mod._addedSideEffectsBailout.add(moduleGraph);
+	moduleGraph
+		.getOptimizationBailout(mod)
+		.push(
+			() =>
+				`Dependency (${dep.type}) with side effects at ${formatLocation(dep.loc)}`
+		);
 };
 
-const contextify = (context, request) => {
-	return request
-		.split("!")
-		.map(r => {
-			const splitPath = r.split("?");
-			if (/^[a-zA-Z]:\\/.test(splitPath[0])) {
-				splitPath[0] = path.win32.relative(context, splitPath[0]);
-				if (!/^[a-zA-Z]:\\/.test(splitPath[0])) {
-					splitPath[0] = splitPath[0].replace(/\\/g, "/");
+/**
+ * Walks the `HarmonyImportSideEffectDependency` graph to decide how `rootMod`
+ * connects to a referrer when consumed for its side effects only.
+ *
+ * Iterative (explicit work stack) so deep import chains never approach V8's
+ * recursion limit — the overflow behind #20986 — with no separate recursive
+ * form or depth cap. Cycles use Tarjan's `index`/`lowlink`: a module's result
+ * is memoized on `_sideEffectsStateGraph` only when it is context-independent
+ * (cycle-free, or the root of its strongly-connected component), i.e. exactly
+ * when the result cannot depend on which ancestors are mid-walk.
+ *
+ * The previous walker disabled memoization for the whole walk the moment any
+ * cycle appeared (one global `circular` flag). With diamond-shaped graphs that
+ * made the walk exponential, so a single circular import could blow up an
+ * otherwise-linear graph. Per-module lowlink keeps the cycle-free majority
+ * memoized while a cycle exists elsewhere.
+ *
+ * Caching under `lowlink === index` is conservative: an imprecise lowlink can
+ * only skip caching (a later recompute yields the same value), never memoize a
+ * state that depended on stack context. `true` is monotonic and always cached.
+ * A non-`NormalModule` dependency (e.g. `ConcatenatedModule` re-entering this
+ * method) is treated as a taint so its chain is never memoized, and any module
+ * already flagged `_isEvaluatingSideEffects` (this walk or an outer re-entrant
+ * one) counts as circular, matching the previous walker.
+ * @param {NormalModule} rootMod the module being walked
+ * @param {ModuleGraph} moduleGraph the module graph
+ * @param {EXPECTED_ANY} SideEffectDep `HarmonyImportSideEffectDependency` constructor, resolved once at the entry
+ * @returns {ConnectionState} the side-effects connection state
+ */
+const walkSideEffects = (rootMod, moduleGraph, SideEffectDep) => {
+	// Context-independent state without descending; `undefined` => walk deps.
+	/**
+	 * @param {NormalModule} mod module
+	 * @returns {ConnectionState | undefined} resolved state or undefined
+	 */
+	const quick = (mod) => {
+		if (mod._sideEffectsStateGraph === moduleGraph) {
+			return /** @type {ConnectionState} */ (mod._sideEffectsStateValue);
+		}
+		const fm = mod.factoryMeta;
+		if (fm !== undefined) {
+			if (fm.sideEffectFree) return false;
+			if (fm.sideEffectFree === false) return true;
+		}
+		if (!(mod.buildMeta !== undefined && mod.buildMeta.sideEffectFree)) {
+			return true;
+		}
+		return undefined;
+	};
+
+	const q0 = quick(rootMod);
+	if (q0 !== undefined) return q0;
+
+	/** @type {Map} module -> Tarjan index */
+	const indexOf = new Map();
+	/** @type {Map} module -> Tarjan lowlink */
+	const lowOf = new Map();
+	// Parallel frame arrays, to avoid a per-frame object allocation.
+	/** @type {NormalModule[]} */
+	const fMod = [];
+	/** @type {Dependency[][]} */
+	const fDeps = [];
+	/** @type {number[]} */
+	const fIdx = [];
+	/** @type {ConnectionState[]} */
+	const fCur = [];
+	let counter = 0;
+
+	/**
+	 * @param {NormalModule} mod module to push a frame for
+	 */
+	const enter = (mod) => {
+		indexOf.set(mod, counter);
+		lowOf.set(mod, counter);
+		counter++;
+		mod._isEvaluatingSideEffects = true;
+		fMod.push(mod);
+		fDeps.push(mod.dependencies);
+		fIdx.push(0);
+		fCur.push(false);
+	};
+
+	enter(rootMod);
+	/** @type {ConnectionState} the just-popped frame's result */
+	let result = false;
+	/**
+	 * Result of a just-popped child frame, to apply to the new top's current
+	 * dep. `undefined` means "no pending; advance normally".
+	 * @type {ConnectionState | undefined}
+	 */
+	let pending;
+
+	while (fMod.length > 0) {
+		const top = fMod.length - 1;
+		const mod = fMod[top];
+		const deps = fDeps[top];
+		let i = fIdx[top];
+		let current = fCur[top];
+
+		// Apply a popped child's result to the dep that spawned it.
+		if (pending !== undefined) {
+			const state = pending;
+			pending = undefined;
+			const dep = deps[i];
+			if (state === true) {
+				recordSideEffectsBailout(mod, moduleGraph, dep);
+				mod._sideEffectsStateGraph = moduleGraph;
+				mod._sideEffectsStateValue = true;
+				mod._isEvaluatingSideEffects = false;
+				fMod.pop();
+				fDeps.pop();
+				fIdx.pop();
+				fCur.pop();
+				result = true;
+				pending = true;
+				continue;
+			}
+			if (state !== ModuleGraphConnection.CIRCULAR_CONNECTION) {
+				current = ModuleGraphConnection.addConnectionStates(current, state);
+			}
+			i++;
+		}
+
+		let descended = false;
+		while (i < deps.length) {
+			const dep = deps[i];
+			/** @type {ConnectionState} */
+			let state;
+
+			if (dep instanceof SideEffectDep) {
+				const refModule = moduleGraph.getModule(dep);
+				if (!refModule) {
+					state = true;
+				} else if (refModule instanceof NormalModule) {
+					if (refModule._isEvaluatingSideEffects) {
+						// On the current stack (or an outer re-entrant walk) => cycle.
+						state = ModuleGraphConnection.CIRCULAR_CONNECTION;
+						const ri = indexOf.get(refModule);
+						lowOf.set(
+							mod,
+							Math.min(
+								/** @type {number} */ (lowOf.get(mod)),
+								ri === undefined ? -1 : ri
+							)
+						);
+					} else {
+						const q = quick(refModule);
+						if (q !== undefined) {
+							state = q;
+						} else {
+							// Descend: pause this frame, walk the child next.
+							fIdx[top] = i;
+							fCur[top] = current;
+							enter(refModule);
+							descended = true;
+							break;
+						}
+					}
+				} else {
+					// Non-NormalModule (e.g. ConcatenatedModule delegating back
+					// through here) resolves via a re-entrant walk that may see our
+					// in-progress modules as circular, so taint this chain: never
+					// memoize it.
+					state = refModule.getSideEffectsConnectionState(moduleGraph);
+					lowOf.set(mod, -1);
 				}
+			} else {
+				state = dep.getModuleEvaluationSideEffectsState(moduleGraph);
 			}
-			if (/^\//.test(splitPath[0])) {
-				splitPath[0] = path.posix.relative(context, splitPath[0]);
+
+			if (state === true) {
+				recordSideEffectsBailout(mod, moduleGraph, dep);
+				// `true` is monotonic — always safe to memoize.
+				mod._sideEffectsStateGraph = moduleGraph;
+				mod._sideEffectsStateValue = true;
+				mod._isEvaluatingSideEffects = false;
+				fMod.pop();
+				fDeps.pop();
+				fIdx.pop();
+				fCur.pop();
+				result = true;
+				pending = true;
+				descended = true;
+				break;
 			}
-			if (!/^(\.\.\/|\/|[a-zA-Z]:\\)/.test(splitPath[0])) {
-				splitPath[0] = "./" + splitPath[0];
+			if (state !== ModuleGraphConnection.CIRCULAR_CONNECTION) {
+				current = ModuleGraphConnection.addConnectionStates(current, state);
 			}
-			return splitPath.join("?");
-		})
-		.join("!");
+			i++;
+		}
+
+		if (descended) continue;
+
+		// `mod` finished without a `true` bailout.
+		mod._isEvaluatingSideEffects = false;
+		// Memoize only when the result is context-independent: no back-edge in
+		// this subtree reached above `mod` (a cycle-free module or an SCC root).
+		if (lowOf.get(mod) === indexOf.get(mod)) {
+			mod._sideEffectsStateGraph = moduleGraph;
+			mod._sideEffectsStateValue = current;
+		}
+		fMod.pop();
+		fDeps.pop();
+		fIdx.pop();
+		fCur.pop();
+		result = current;
+		pending = current;
+		if (fMod.length > 0) {
+			const parent = fMod[fMod.length - 1];
+			lowOf.set(
+				parent,
+				Math.min(
+					/** @type {number} */ (lowOf.get(parent)),
+					/** @type {number} */ (lowOf.get(mod))
+				)
+			);
+		}
+	}
+
+	return result;
 };
 
-class NonErrorEmittedError extends WebpackError {
-	constructor(error) {
-		super();
+/**
+ * @typedef {object} LoaderItem
+ * @property {string} loader
+ * @property {string | null | undefined | Record} options
+ * @property {string | null=} ident
+ * @property {string | null=} type
+ */
 
-		this.name = "NonErrorEmittedError";
-		this.message = "(Emitted value instead of an instance of Error) " + error;
+/**
+ * @param {string} context absolute context path
+ * @param {string | RawSourceMap} sourceMap a source map
+ * @param {AssociatedObjectForCache=} associatedObjectForCache an object to which the cache will be attached
+ * @returns {string | RawSourceMap} new source map
+ */
+const contextifySourceMap = (context, sourceMap, associatedObjectForCache) => {
+	if (typeof sourceMap === "string" || !Array.isArray(sourceMap.sources)) {
+		return sourceMap;
+	}
+	const { sourceRoot } = sourceMap;
+	/** @type {(source: string) => string} */
+	const mapper = !sourceRoot
+		? (source) => source
+		: sourceRoot.endsWith("/")
+			? (source) =>
+					source.startsWith("/")
+						? `${sourceRoot.slice(0, -1)}${source}`
+						: `${sourceRoot}${source}`
+			: (source) =>
+					source.startsWith("/")
+						? `${sourceRoot}${source}`
+						: `${sourceRoot}/${source}`;
+	const newSources = sourceMap.sources.map((source) =>
+		contextifySourceUrl(context, mapper(source), associatedObjectForCache)
+	);
+	return {
+		...sourceMap,
+		file: "x",
+		sourceRoot: undefined,
+		sources: newSources
+	};
+};
 
-		Error.captureStackTrace(this, this.constructor);
+/**
+ * @param {string | Buffer} input the input
+ * @returns {string} the converted string
+ */
+const asString = (input) => {
+	if (Buffer.isBuffer(input)) {
+		return input.toString("utf8");
 	}
-}
+	return input;
+};
+
+/**
+ * @param {string | Buffer} input the input
+ * @returns {Buffer} the converted buffer
+ */
+const asBuffer = (input) => {
+	if (!Buffer.isBuffer(input)) {
+		return Buffer.from(input, "utf8");
+	}
+	return input;
+};
+
+/** @typedef {[string | Buffer, string | RawSourceMap | undefined, PreparsedAst | undefined]}  Result */
+
+/** @typedef {LoaderContext} AnyLoaderContext */
+
+/**
+ * @deprecated Use the `readResource` hook instead.
+ * @typedef {HookMap>>} DeprecatedReadResourceForScheme
+ */
+
+const createCompilationHooks = () => {
+	/** @type {HookMap>} */
+	const readResource = new HookMap(
+		() => new AsyncSeriesBailHook(["loaderContext"])
+	);
+	return {
+		// TODO webpack 6 deprecate
+		/**
+		 * @deprecated Use the `readResource` hook instead.
+		 * @type {DeprecatedReadResourceForScheme}
+		 */
+		readResourceForScheme: new HookMap((scheme) => {
+			const hook = readResource.for(scheme);
+			return createFakeHook(
+				/** @type {AsyncSeriesBailHook<[string, NormalModule], string | Buffer | null>} */ ({
+					tap: (options, fn) =>
+						hook.tap(options, (loaderContext) =>
+							fn(
+								loaderContext.resource,
+								/** @type {NormalModule} */ (loaderContext._module)
+							)
+						),
+					tapAsync: (options, fn) =>
+						hook.tapAsync(options, (loaderContext, callback) =>
+							fn(
+								loaderContext.resource,
+								/** @type {NormalModule} */ (loaderContext._module),
+								callback
+							)
+						),
+					tapPromise: (options, fn) =>
+						hook.tapPromise(options, (loaderContext) =>
+							fn(
+								loaderContext.resource,
+								/** @type {NormalModule} */ (loaderContext._module)
+							)
+						)
+				})
+			);
+		}),
+		/**
+		 * @since 5.58.0
+		 */
+		readResource,
+		/** @type {SyncHook<[AnyLoaderContext, NormalModule]>} */
+		loader: new SyncHook(["loaderContext", "module"]),
+		/** @type {SyncHook<[LoaderItem[], NormalModule, AnyLoaderContext]>} */
+		beforeLoaders: new SyncHook(["loaders", "module", "loaderContext"]),
+		/**
+		 * @type {SyncHook<[NormalModule]>}
+		 * @since 5.59.0
+		 */
+		beforeParse: new SyncHook(["module"]),
+		/**
+		 * @type {SyncHook<[NormalModule]>}
+		 * @since 5.59.0
+		 */
+		beforeSnapshot: new SyncHook(["module"]),
+		/**
+		 * @type {SyncWaterfallHook<[Result, NormalModule]>}
+		 * @since 5.99.0
+		 */
+		processResult: new SyncWaterfallHook(["result", "module"]),
+		/**
+		 * @type {AsyncSeriesBailHook<[NormalModule, NeedBuildContext], boolean>}
+		 * @since 5.49.0
+		 */
+		needBuild: new AsyncSeriesBailHook(["module", "context"])
+	};
+};
 
 /**
- * @typedef {Object} CachedSourceEntry
- * @property {TODO} source the generated source
- * @property {string} hash the hash value
+ * @typedef {ReturnType} NormalModuleCompilationHooks
  */
 
+/**
+ * @template {NormalModuleTypes | ""} [T=NormalModuleTypes | ""]
+ * @typedef {object} NormalModuleCreateData
+ * @property {string=} layer an optional layer in which the module is
+ * @property {T} type module type. When deserializing, this is set to an empty string "".
+ * @property {string} request request string
+ * @property {string} userRequest request intended by user (without loaders from config)
+ * @property {string} rawRequest request without resolving
+ * @property {LoaderItem[]} loaders list of loaders
+ * @property {string} resource path + query of the real resource
+ * @property {(ResourceSchemeData & Partial)=} resourceResolveData resource resolve data
+ * @property {string} context context directory for resolving
+ * @property {string=} matchResource path + query of the matched resource (virtual)
+ * @property {ParserByType[T]} parser the parser used
+ * @property {ParserOptionsByType[T]=} parserOptions the options of the parser used
+ * @property {GeneratorByType[T]} generator the generator used
+ * @property {GeneratorOptionsByType[T]=} generatorOptions the options of the generator used
+ * @property {ResolveOptions=} resolveOptions options used for resolving requests from this module
+ * @property {boolean} extractSourceMap enable/disable extracting source map
+ */
+
+/**
+ * @typedef {(resourcePath: string, getLoaderContext: (resourcePath: string) => AnyLoaderContext) => Promise>} ReadResource
+ */
+
+/**
+ * Defines the build info properties of normal modules (filesystem-backed, loader-processed).
+ * @typedef {object} KnownNormalModuleBuildInfo
+ * @property {boolean=} parsed
+ * @property {string=} hash
+ * @property {FileSystemDependencies=} fileDependencies
+ * @property {FileSystemDependencies=} contextDependencies
+ * @property {FileSystemDependencies=} missingDependencies
+ * @property {FileSystemDependencies=} buildDependencies
+ * @property {ValueCacheVersions=} valueDependencies
+ * @property {(Snapshot | null)=} snapshot
+ * @property {string=} resourceIntegrity using in HttpUriPlugin
+ */
+
+/** @typedef {BuildInfo & KnownNormalModuleBuildInfo} NormalModuleBuildInfo */
+
+const normalModuleHooksRegistry = createHooksRegistry(createCompilationHooks);
+
 class NormalModule extends Module {
+	/**
+	 * @param {Compilation} compilation the compilation
+	 * @returns {NormalModuleCompilationHooks} the attached hooks
+	 */
+	static getCompilationHooks(compilation) {
+		return normalModuleHooksRegistry(compilation);
+	}
+
+	/**
+	 * @param {NormalModuleCreateData} options options object
+	 */
 	constructor({
+		layer,
 		type,
 		request,
 		userRequest,
 		rawRequest,
 		loaders,
 		resource,
+		resourceResolveData,
+		context,
 		matchResource,
 		parser,
+		parserOptions,
 		generator,
-		resolveOptions
+		generatorOptions,
+		resolveOptions,
+		extractSourceMap
 	}) {
-		super(type, getContext(resource));
+		super(type, context || getContext(resource), layer);
 
 		// Info from Factory
+		/** @type {NormalModuleCreateData['request']} */
 		this.request = request;
+		/** @type {NormalModuleCreateData['userRequest']} */
 		this.userRequest = userRequest;
+		/** @type {NormalModuleCreateData['rawRequest']} */
 		this.rawRequest = rawRequest;
-		this.binary = type.startsWith("webassembly");
+		/** @type {boolean} */
+		this.binary = /^(?:asset|webassembly)\b/.test(type);
+		/** @type {NormalModuleCreateData['parser'] | undefined} */
 		this.parser = parser;
+		/** @type {NormalModuleCreateData['parserOptions']} */
+		this.parserOptions = parserOptions;
+		/** @type {NormalModuleCreateData['generator'] | undefined} */
 		this.generator = generator;
+		/** @type {NormalModuleCreateData['generatorOptions']} */
+		this.generatorOptions = generatorOptions;
+		/** @type {NormalModuleCreateData['resource']} */
 		this.resource = resource;
+		/** @type {NormalModuleCreateData['resourceResolveData']} */
+		this.resourceResolveData = resourceResolveData;
+		/** @type {NormalModuleCreateData['matchResource']} */
 		this.matchResource = matchResource;
+		/** @type {NormalModuleCreateData['loaders']} */
 		this.loaders = loaders;
-		if (resolveOptions !== undefined) this.resolveOptions = resolveOptions;
+		if (resolveOptions !== undefined) {
+			// already declared in super class
+			/** @type {NormalModuleCreateData['resolveOptions']} */
+			this.resolveOptions = resolveOptions;
+		}
+		/** @type {NormalModuleCreateData['extractSourceMap']} */
+		this.extractSourceMap = extractSourceMap;
+
+		// Set by HotModuleReplacementPlugin via NormalModuleFactory's `module` hook
+		/** @type {boolean} */
+		this.hot = false;
 
 		// Info from Build
+		// Redeclared with the normal module specific shape (see KnownNormalModuleBuildInfo)
+		/** @type {NormalModuleBuildInfo | undefined} */
+		this.buildInfo = undefined;
+		/** @type {Error | null} */
 		this.error = null;
+		/**
+		 * @private
+		 * @type {Source | null}
+		 */
 		this._source = null;
-		this._buildHash = "";
-		this.buildTimestamp = undefined;
-		/** @private @type {Map} */
-		this._cachedSources = new Map();
-
-		// Options for the NormalModule set by plugins
-		// TODO refactor this -> options object filled from Factory
-		this.useSourceMap = false;
-		this.lineToLine = false;
-
+		// Set at build time; declared here so the instance shape is fixed at construction
+		/**
+		 * @private
+		 * @type {PreparsedAst | null}
+		 */
+		this._ast = null;
+		/**
+		 * @private
+		 * @type {Map | undefined}
+		 */
+		this._sourceSizes = undefined;
+		/**
+		 * @private
+		 * @type {undefined | SourceTypes}
+		 */
+		this._sourceTypes = undefined;
 		// Cache
+		/**
+		 * @private
+		 * @type {BuildMeta}
+		 */
 		this._lastSuccessfulBuildMeta = {};
+		/**
+		 * @private
+		 * @type {boolean}
+		 */
+		this._forceBuild = true;
+		/**
+		 * @type {boolean}
+		 */
+		this._isEvaluatingSideEffects = false;
+		/**
+		 * @type {WeakSet | undefined}
+		 */
+		this._addedSideEffectsBailout = undefined;
+		/**
+		 * Memoizes the result of `getSideEffectsConnectionState`. The
+		 * graph slot keys the cached value to the `ModuleGraph` it was
+		 * computed against so stale values never leak across compilations
+		 * — a walk that targets a different graph just overwrites both
+		 * slots. Populated only for context-independent results (cycle-free
+		 * or SCC-root modules; see `walkSideEffects`).
+		 * @type {ModuleGraph | undefined}
+		 */
+		this._sideEffectsStateGraph = undefined;
+		/** @type {ConnectionState | undefined} */
+		this._sideEffectsStateValue = undefined;
+		/**
+		 * @private
+		 * @type {CodeGenerationResultData}
+		 */
+		this._codeGeneratorData = new Map();
 	}
 
+	/**
+	 * Returns the unique identifier used to reference this module.
+	 * @returns {string} a unique identifier of the module
+	 */
 	identifier() {
-		return this.request;
+		if (this.layer === null) {
+			if (this.type === JAVASCRIPT_MODULE_TYPE_AUTO) {
+				return this.request;
+			}
+			return `${this.type}|${this.request}`;
+		}
+		return `${this.type}|${this.request}|${this.layer}`;
 	}
 
+	/**
+	 * Returns a human-readable identifier for this module.
+	 * @param {RequestShortener} requestShortener the request shortener
+	 * @returns {string} a user readable identifier of the module
+	 */
 	readableIdentifier(requestShortener) {
-		return requestShortener.shorten(this.userRequest);
+		return /** @type {string} */ (requestShortener.shorten(this.userRequest));
+	}
+
+	/**
+	 * @returns {string | null} return the resource path
+	 */
+	getResource() {
+		return this.matchResource || this.resource;
 	}
 
+	/**
+	 * Gets the library identifier.
+	 * @param {LibIdentOptions} options options
+	 * @returns {LibIdent | null} an identifier for library inclusion
+	 */
 	libIdent(options) {
-		return contextify(options.context, this.userRequest);
+		let ident = contextify(
+			options.context,
+			this.userRequest,
+			options.associatedObjectForCache
+		);
+		if (this.layer) ident = `(${this.layer})/${ident}`;
+		return ident;
 	}
 
+	/**
+	 * Returns the path used when matching this module against rule conditions.
+	 * @returns {NameForCondition | null} absolute path which should be used for condition matching (usually the resource path)
+	 */
 	nameForCondition() {
-		const resource = this.matchResource || this.resource;
+		const resource = /** @type {string} */ (this.getResource());
 		const idx = resource.indexOf("?");
-		if (idx >= 0) return resource.substr(0, idx);
+		if (idx >= 0) return resource.slice(0, idx);
 		return resource;
 	}
 
+	/**
+	 * Assuming this module is in the cache. Update the (cached) module with
+	 * the fresh module from the factory. Usually updates internal references
+	 * and properties.
+	 * @param {Module} module fresh module
+	 * @returns {void}
+	 */
 	updateCacheModule(module) {
-		this.type = module.type;
-		this.request = module.request;
-		this.userRequest = module.userRequest;
-		this.rawRequest = module.rawRequest;
-		this.parser = module.parser;
-		this.generator = module.generator;
-		this.resource = module.resource;
-		this.matchResource = module.matchResource;
-		this.loaders = module.loaders;
-		this.resolveOptions = module.resolveOptions;
-	}
-
-	createSourceForAsset(name, content, sourceMap) {
-		if (!sourceMap) {
-			return new RawSource(content);
+		super.updateCacheModule(module);
+		const m = /** @type {NormalModule} */ (module);
+		this.binary = m.binary;
+		this.request = m.request;
+		this.userRequest = m.userRequest;
+		this.rawRequest = m.rawRequest;
+		this.parser = m.parser;
+		this.parserOptions = m.parserOptions;
+		this.generator = m.generator;
+		this.generatorOptions = m.generatorOptions;
+		this.resource = m.resource;
+		this.resourceResolveData = m.resourceResolveData;
+		this.context = m.context;
+		this.matchResource = m.matchResource;
+		this.loaders = m.loaders;
+		this.extractSourceMap = m.extractSourceMap;
+	}
+
+	/**
+	 * Assuming this module is in the cache. Remove internal references to allow freeing some memory.
+	 */
+	cleanupForCache() {
+		// Make sure to cache types and sizes before cleanup when this module has been built
+		// They are accessed by the stats and we don't want them to crash after cleanup
+		// TODO reconsider this for webpack 6
+		if (this.buildInfo) {
+			if (this._sourceTypes === undefined) this.getSourceTypes();
+			for (const type of /** @type {SourceTypes} */ (this._sourceTypes)) {
+				this.size(type);
+			}
 		}
+		super.cleanupForCache();
+		this.parser = undefined;
+		this.parserOptions = undefined;
+		this.generator = undefined;
+		this.generatorOptions = undefined;
+		// Drop the side-effects memoization so a long-lived module doesn't
+		// strong-reference a stale `ModuleGraph`/`Compilation` for graphs
+		// that never get re-queried with a fresh one.
+		this._sideEffectsStateGraph = undefined;
+		this._sideEffectsStateValue = undefined;
+	}
+
+	/**
+	 * Releases the cached parser between incremental rebuilds so it doesn't keep
+	 * the previous compilation alive.
+	 * @returns {void}
+	 */
+	_cleanupParserForCache() {
+		this.parser = undefined;
+	}
+
+	/**
+	 * Re-acquire factory-derived helpers (parser/generator) for an incremental
+	 * rebuild so this compilation's plugin hooks apply.
+	 * @param {NormalModuleFactory} normalModuleFactory the normal module factory
+	 * @returns {void}
+	 */
+	_restoreParserAndGenerator(normalModuleFactory) {
+		this.parser = normalModuleFactory.getParser(this.type, this.parserOptions);
+		this.generator = normalModuleFactory.getGenerator(
+			this.type,
+			this.generatorOptions
+		);
+	}
+
+	/**
+	 * Module should be unsafe cached. Get data that's needed for that.
+	 * This data will be passed to restoreFromUnsafeCache later.
+	 * @returns {UnsafeCacheData} cached data
+	 */
+	getUnsafeCacheData() {
+		const data = super.getUnsafeCacheData();
+		data.parserOptions = this.parserOptions;
+		data.generatorOptions = this.generatorOptions;
+		return data;
+	}
+
+	/**
+	 * restore unsafe cache data
+	 * @param {UnsafeCacheData} unsafeCacheData data from getUnsafeCacheData
+	 * @param {NormalModuleFactory} normalModuleFactory the normal module factory handling the unsafe caching
+	 */
+	restoreFromUnsafeCache(unsafeCacheData, normalModuleFactory) {
+		this._restoreFromUnsafeCache(unsafeCacheData, normalModuleFactory);
+	}
+
+	/**
+	 * restore unsafe cache data
+	 * @param {UnsafeCacheData} unsafeCacheData data from getUnsafeCacheData
+	 * @param {NormalModuleFactory} normalModuleFactory the normal module factory handling the unsafe caching
+	 */
+	_restoreFromUnsafeCache(unsafeCacheData, normalModuleFactory) {
+		super._restoreFromUnsafeCache(unsafeCacheData, normalModuleFactory);
+		this.parserOptions = unsafeCacheData.parserOptions;
+		this.parser = normalModuleFactory.getParser(this.type, this.parserOptions);
+		this.generatorOptions = unsafeCacheData.generatorOptions;
+		this.generator = normalModuleFactory.getGenerator(
+			this.type,
+			this.generatorOptions
+		);
+		// we assume the generator behaves identically and keep cached sourceTypes/Sizes
+	}
 
-		if (typeof sourceMap === "string") {
-			return new OriginalSource(content, sourceMap);
+	/**
+	 * @param {string} context the compilation context
+	 * @param {string} name the asset name
+	 * @param {string | Buffer} content the content
+	 * @param {(string | RawSourceMap)=} sourceMap an optional source map
+	 * @param {AssociatedObjectForCache=} associatedObjectForCache object for caching
+	 * @returns {Source} the created source
+	 */
+	createSourceForAsset(
+		context,
+		name,
+		content,
+		sourceMap,
+		associatedObjectForCache
+	) {
+		if (sourceMap) {
+			if (
+				typeof sourceMap === "string" &&
+				(this.useSourceMap || this.useSimpleSourceMap)
+			) {
+				return new OriginalSource(
+					content,
+					contextifySourceUrl(context, sourceMap, associatedObjectForCache)
+				);
+			}
+
+			if (this.useSourceMap) {
+				return new SourceMapSource(
+					content,
+					name,
+					contextifySourceMap(
+						context,
+						/** @type {RawSourceMap} */
+						(sourceMap),
+						associatedObjectForCache
+					)
+				);
+			}
 		}
 
-		return new SourceMapSource(content, name, sourceMap);
+		return new RawSource(content);
 	}
 
-	createLoaderContext(resolver, options, compilation, fs) {
-		const requestShortener = compilation.runtimeTemplate.requestShortener;
+	/**
+	 * @private
+	 * @template T
+	 * @param {ResolverWithOptions} resolver a resolver
+	 * @param {WebpackOptions} options webpack options
+	 * @param {Compilation} compilation the compilation
+	 * @param {InputFileSystem} fs file system from reading
+	 * @param {NormalModuleCompilationHooks} hooks the hooks
+	 * @returns {import("../declarations/LoaderContext").LoaderContext} loader context
+	 */
+	_createLoaderContext(resolver, options, compilation, fs, hooks) {
+		const { requestShortener } = compilation.runtimeTemplate;
+		const getCurrentLoaderName = () => {
+			const currentLoader = this.getCurrentLoader(
+				/** @type {AnyLoaderContext} */
+				(loaderContext)
+			);
+			if (!currentLoader) return "(not in loader scope)";
+			return requestShortener.shorten(currentLoader.loader);
+		};
+		/**
+		 * @returns {ResolveContext} resolve context
+		 */
+		const getResolveContext = () => ({
+			fileDependencies: {
+				add: (d) =>
+					/** @type {AnyLoaderContext} */
+					(loaderContext).addDependency(d)
+			},
+			contextDependencies: {
+				add: (d) =>
+					/** @type {AnyLoaderContext} */
+					(loaderContext).addContextDependency(d)
+			},
+			missingDependencies: {
+				add: (d) =>
+					/** @type {AnyLoaderContext} */
+					(loaderContext).addMissingDependency(d)
+			}
+		});
+		const getAbsolutify = memoize(() =>
+			absolutify.bindCache(compilation.compiler.root)
+		);
+		const getAbsolutifyInContext = memoize(() =>
+			absolutify.bindContextCache(
+				/** @type {string} */
+				(this.context),
+				compilation.compiler.root
+			)
+		);
+		const getContextify = memoize(() =>
+			contextify.bindCache(compilation.compiler.root)
+		);
+		const getContextifyInContext = memoize(() =>
+			contextify.bindContextCache(
+				/** @type {string} */
+				(this.context),
+				compilation.compiler.root
+			)
+		);
+		const utils = {
+			/**
+			 * @param {string} context context
+			 * @param {string} request request
+			 * @returns {string} result
+			 */
+			absolutify: (context, request) =>
+				context === this.context
+					? getAbsolutifyInContext()(request)
+					: getAbsolutify()(context, request),
+			/**
+			 * @param {string} context context
+			 * @param {string} request request
+			 * @returns {string} result
+			 */
+			contextify: (context, request) =>
+				context === this.context
+					? getContextifyInContext()(request)
+					: getContextify()(context, request),
+			/**
+			 * @param {HashFunction=} type type
+			 * @returns {Hash} hash
+			 */
+			createHash: (type) =>
+				createHash(type || compilation.outputOptions.hashFunction)
+		};
+		/** @type {NormalModuleLoaderContext} */
 		const loaderContext = {
 			version: 2,
-			emitWarning: warning => {
+			/** @type {LoaderContext["getOptions"]} */
+			getOptions: (/** @type {EXPECTED_ANY} */ schema = undefined) => {
+				const loader = this.getCurrentLoader(
+					/** @type {AnyLoaderContext} */
+					(loaderContext)
+				);
+
+				let { options } = /** @type {LoaderItem} */ (loader);
+
+				if (typeof options === "string") {
+					if (options.startsWith("{") && options.endsWith("}")) {
+						try {
+							options =
+								/** @type {LoaderItem["options"]} */
+								(parseJson(options));
+						} catch (err) {
+							throw new Error(
+								`Cannot parse string options: ${/** @type {Error} */ (err).message}`,
+								{ cause: err }
+							);
+						}
+					} else {
+						options = querystring.parse(options, "&", "=", {
+							maxKeys: 0
+						});
+					}
+				}
+
+				if (options === null || options === undefined) {
+					options = {};
+				}
+
+				if (schema && compilation.options.validate) {
+					let name = "Loader";
+					let baseDataPath = "options";
+					/** @type {RegExpExecArray | null} */
+					let match;
+					if (schema.title && (match = /^(.+) (.+)$/.exec(schema.title))) {
+						[, name, baseDataPath] = match;
+					}
+					getValidate()(schema, /** @type {EXPECTED_OBJECT} */ (options), {
+						name,
+						baseDataPath
+					});
+				}
+
+				return /** @type {T} */ (options);
+			},
+			/** @type {LoaderContext["emitWarning"]} */
+			emitWarning: (warning) => {
 				if (!(warning instanceof Error)) {
-					warning = new NonErrorEmittedError(warning);
+					warning = new (getNonErrorEmittedError())(warning);
 				}
-				const currentLoader = this.getCurrentLoader(loaderContext);
-				this.warnings.push(
-					new ModuleWarning(this, warning, {
-						from: requestShortener.shorten(currentLoader.loader)
+				this.addWarning(
+					new (getModuleWarning())(warning, {
+						from: getCurrentLoaderName()
 					})
 				);
 			},
-			emitError: error => {
+			/** @type {LoaderContext["emitError"]} */
+			emitError: (error) => {
 				if (!(error instanceof Error)) {
-					error = new NonErrorEmittedError(error);
+					error = new (getNonErrorEmittedError())(error);
 				}
-				const currentLoader = this.getCurrentLoader(loaderContext);
-				this.errors.push(
-					new ModuleError(this, error, {
-						from: requestShortener.shorten(currentLoader.loader)
+				this.addError(
+					new (getModuleError())(error, {
+						from: getCurrentLoaderName()
 					})
 				);
 			},
-			exec: (code, filename) => {
-				// @ts-ignore Argument of type 'this' is not assignable to parameter of type 'Module'.
-				const module = new NativeModule(filename, this);
-				// @ts-ignore _nodeModulePaths is deprecated and undocumented Node.js API
-				module.paths = NativeModule._nodeModulePaths(this.context);
-				module.filename = filename;
-				module._compile(code, filename);
-				return module.exports;
+			/** @type {LoaderContext["getLogger"]} */
+			getLogger: (name) => {
+				const currentLoader = this.getCurrentLoader(
+					/** @type {AnyLoaderContext} */
+					(loaderContext)
+				);
+				return compilation.getLogger(() =>
+					[currentLoader && currentLoader.loader, name, this.identifier()]
+						.filter(Boolean)
+						.join("|")
+				);
 			},
+			/** @type {LoaderContext["resolve"]} */
 			resolve(context, request, callback) {
-				resolver.resolve({}, context, request, {}, callback);
+				resolver.resolve({}, context, request, getResolveContext(), callback);
 			},
-			emitFile: (name, content, sourceMap) => {
-				if (!this.buildInfo.assets) {
-					this.buildInfo.assets = Object.create(null);
+			/** @type {LoaderContext["getResolve"]} */
+			getResolve(options) {
+				const child = options ? resolver.withOptions(options) : resolver;
+				return /** @type {ReturnType["getResolve"]>} */ (
+					(context, request, callback) => {
+						if (callback) {
+							child.resolve(
+								{},
+								context,
+								request,
+								getResolveContext(),
+								callback
+							);
+						} else {
+							return new Promise((resolve, reject) => {
+								child.resolve(
+									{},
+									context,
+									request,
+									getResolveContext(),
+									(err, result) => {
+										if (err) reject(err);
+										else resolve(result);
+									}
+								);
+							});
+						}
+					}
+				);
+			},
+			/** @type {LoaderContext["emitFile"]} */
+			emitFile: (name, content, sourceMap, assetInfo) => {
+				const buildInfo = /** @type {NormalModuleBuildInfo} */ (this.buildInfo);
+
+				if (!buildInfo.assets) {
+					buildInfo.assets = Object.create(null);
+					buildInfo.assetsInfo = new Map();
 				}
-				this.buildInfo.assets[name] = this.createSourceForAsset(
+
+				const assets =
+					/** @type {NonNullable} */
+					(buildInfo.assets);
+				const assetsInfo =
+					/** @type {NonNullable} */
+					(buildInfo.assetsInfo);
+
+				assets[name] = this.createSourceForAsset(
+					options.context,
 					name,
 					content,
-					sourceMap
+					sourceMap,
+					compilation.compiler.root
 				);
+				assetsInfo.set(name, assetInfo);
 			},
+			addBuildDependency: (dep) => {
+				const buildInfo = /** @type {NormalModuleBuildInfo} */ (this.buildInfo);
+
+				if (buildInfo.buildDependencies === undefined) {
+					buildInfo.buildDependencies = new LazySet();
+				}
+				buildInfo.buildDependencies.add(dep);
+			},
+			utils,
 			rootContext: options.context,
 			webpack: true,
-			sourceMap: !!this.useSourceMap,
+			sourceMap: Boolean(this.useSourceMap),
+			mode: options.mode || "production",
+			hashFunction: options.output.hashFunction,
+			hashDigest: options.output.hashDigest,
+			hashDigestLength: options.output.hashDigestLength,
+			hashSalt: options.output.hashSalt,
 			_module: this,
 			_compilation: compilation,
 			_compiler: compilation.compiler,
-			fs: fs
+			fs
 		};
 
-		compilation.hooks.normalModuleLoader.call(loaderContext, this);
-		if (options.loader) {
-			Object.assign(loaderContext, options.loader);
-		}
+		Object.assign(loaderContext, options.loader);
+		createLoaderContext(loaderContext);
+
+		hooks.loader.call(
+			/** @type {AnyLoaderContext} */
+			(loaderContext),
+			this
+		);
 
-		return loaderContext;
+		return /** @type {AnyLoaderContext} */ (loaderContext);
 	}
 
+	/**
+	 * @param {AnyLoaderContext} loaderContext loader context
+	 * @param {number} index index
+	 * @returns {LoaderItem | null} loader
+	 */
 	getCurrentLoader(loaderContext, index = loaderContext.loaderIndex) {
 		if (
 			this.loaders &&
@@ -243,125 +1209,305 @@ class NormalModule extends Module {
 		return null;
 	}
 
-	createSource(source, resourceBuffer, sourceMap) {
+	/**
+	 * @param {string} context the compilation context
+	 * @param {string | Buffer} content the content
+	 * @param {(string | RawSourceMap | null)=} sourceMap an optional source map
+	 * @param {AssociatedObjectForCache=} associatedObjectForCache object for caching
+	 * @returns {Source} the created source
+	 */
+	createSource(context, content, sourceMap, associatedObjectForCache) {
+		if (Buffer.isBuffer(content)) {
+			return new RawSource(content);
+		}
+
 		// if there is no identifier return raw source
 		if (!this.identifier) {
-			return new RawSource(source);
+			return new RawSource(content);
 		}
 
 		// from here on we assume we have an identifier
 		const identifier = this.identifier();
 
-		if (this.lineToLine && resourceBuffer) {
-			return new LineToLineMappedSource(
-				source,
-				identifier,
-				asString(resourceBuffer)
-			);
-		}
-
 		if (this.useSourceMap && sourceMap) {
-			return new SourceMapSource(source, identifier, sourceMap);
+			return new SourceMapSource(
+				content,
+				contextifySourceUrl(context, identifier, associatedObjectForCache),
+				contextifySourceMap(context, sourceMap, associatedObjectForCache)
+			);
 		}
 
-		if (Buffer.isBuffer(source)) {
-			return new RawSource(source);
+		if (this.useSourceMap || this.useSimpleSourceMap) {
+			return new OriginalSource(
+				content,
+				contextifySourceUrl(context, identifier, associatedObjectForCache)
+			);
 		}
 
-		return new OriginalSource(source, identifier);
+		return new RawSource(content);
 	}
 
-	doBuild(options, compilation, resolver, fs, callback) {
-		const loaderContext = this.createLoaderContext(
+	/**
+	 * @param {WebpackOptions} options webpack options
+	 * @param {Compilation} compilation the compilation
+	 * @param {ResolverWithOptions} resolver the resolver
+	 * @param {InputFileSystem} fs the file system
+	 * @param {NormalModuleCompilationHooks} hooks the hooks
+	 * @param {BuildCallback} callback callback function
+	 * @returns {void}
+	 */
+	_doBuild(options, compilation, resolver, fs, hooks, callback) {
+		const loaderContext = this._createLoaderContext(
 			resolver,
 			options,
 			compilation,
-			fs
+			fs,
+			hooks
 		);
 
+		/**
+		 * @param {Error | null} err err
+		 * @param {(Result | null)=} result_ result
+		 * @returns {void}
+		 */
+		const processResult = (err, result_) => {
+			if (err) {
+				if (!(err instanceof Error)) {
+					err = new (getNonErrorEmittedError())(err);
+				}
+				const currentLoader = this.getCurrentLoader(loaderContext);
+				const error = new (getModuleBuildError())(err, {
+					from:
+						currentLoader &&
+						compilation.runtimeTemplate.requestShortener.shorten(
+							currentLoader.loader
+						)
+				});
+				return callback(error);
+			}
+			const result = hooks.processResult.call(
+				/** @type {Result} */
+				(result_),
+				this
+			);
+			const source = result[0];
+			const sourceMap = result.length >= 1 ? result[1] : null;
+			const extraInfo = result.length >= 2 ? result[2] : null;
+
+			if (!Buffer.isBuffer(source) && typeof source !== "string") {
+				const currentLoader = this.getCurrentLoader(loaderContext, 0);
+				const err = new Error(
+					`Final loader (${
+						currentLoader
+							? compilation.runtimeTemplate.requestShortener.shorten(
+									currentLoader.loader
+								)
+							: "unknown"
+					}) didn't return a Buffer or String`
+				);
+				const error = new (getModuleBuildError())(err);
+				return callback(error);
+			}
+
+			const isBinaryModule =
+				this.generatorOptions && this.generatorOptions.binary !== undefined
+					? this.generatorOptions.binary
+					: this.binary;
+
+			this._source = this.createSource(
+				options.context,
+				isBinaryModule ? asBuffer(source) : asString(source),
+				sourceMap,
+				compilation.compiler.root
+			);
+			if (this._sourceSizes !== undefined) this._sourceSizes.clear();
+			/** @type {PreparsedAst | null} */
+			this._ast =
+				typeof extraInfo === "object" &&
+				extraInfo !== null &&
+				extraInfo.webpackAST !== undefined
+					? extraInfo.webpackAST
+					: null;
+			return callback();
+		};
+
+		const buildInfo = /** @type {NormalModuleBuildInfo} */ (this.buildInfo);
+
+		buildInfo.fileDependencies = new LazySet();
+		buildInfo.contextDependencies = new LazySet();
+		buildInfo.missingDependencies = new LazySet();
+		buildInfo.cacheable = true;
+
+		try {
+			hooks.beforeLoaders.call(
+				this.loaders,
+				this,
+				/** @type {AnyLoaderContext} */
+				(loaderContext)
+			);
+		} catch (err) {
+			processResult(/** @type {Error} */ (err));
+			return;
+		}
+
+		if (this.loaders.length > 0) {
+			/** @type {NormalModuleBuildInfo} */
+			(this.buildInfo).buildDependencies = new LazySet();
+		}
+
 		runLoaders(
 			{
 				resource: this.resource,
 				loaders: this.loaders,
 				context: loaderContext,
-				readResource: fs.readFile.bind(fs)
+				/**
+				 * @param {AnyLoaderContext} loaderContext the loader context
+				 * @param {string} resourcePath the resource Path
+				 * @param {(err: Error | null, result?: string | Buffer, sourceMap?: Result[1]) => void} callback callback
+				 * @returns {Promise}
+				 */
+				processResource: async (loaderContext, resourcePath, callback) => {
+					/** @type {ReadResource} */
+					const readResource = (resourcePath, getLoaderContext) => {
+						const scheme = getScheme(resourcePath);
+						return new Promise((resolve, reject) => {
+							hooks.readResource
+								.for(scheme)
+								.callAsync(getLoaderContext(resourcePath), (err, result) => {
+									if (err) {
+										reject(err);
+									} else {
+										if (typeof result !== "string" && !result) {
+											return reject(
+												new (getUnhandledSchemeError())(
+													/** @type {string} */
+													(scheme),
+													resourcePath
+												)
+											);
+										}
+										resolve(result);
+									}
+								});
+						});
+					};
+					try {
+						const result = await readResource(
+							resourcePath,
+							() => loaderContext
+						);
+						if (
+							this.extractSourceMap &&
+							(this.useSourceMap || this.useSimpleSourceMap)
+						) {
+							try {
+								const { source, sourceMap } = await getExtractSourceMap()(
+									result,
+									resourcePath,
+									/** @type {ReadResource} */
+									(resourcePath) =>
+										readResource(
+											resourcePath,
+											(resourcePath) =>
+												/** @type {AnyLoaderContext} */
+												({
+													addDependency(dependency) {
+														loaderContext.addDependency(dependency);
+													},
+													fs: loaderContext.fs,
+													_module: undefined,
+													resourcePath,
+													resource: resourcePath
+												})
+										).catch((err) => {
+											throw new Error(
+												`Failed to parse source map. ${/** @type {Error} */ (err).message}`
+											);
+										})
+								);
+								return callback(null, source, sourceMap);
+							} catch (err) {
+								this.addWarning(
+									new (getModuleWarning())(/** @type {Error} */ (err))
+								);
+								return callback(null, result);
+							}
+						}
+						return callback(null, result);
+					} catch (error) {
+						return callback(/** @type {Error} */ (error));
+					}
+				}
 			},
 			(err, result) => {
-				if (result) {
-					this.buildInfo.cacheable = result.cacheable;
-					this.buildInfo.fileDependencies = new Set(result.fileDependencies);
-					this.buildInfo.contextDependencies = new Set(
-						result.contextDependencies
+				// Cleanup loaderContext to avoid leaking memory in ICs
+				loaderContext._compilation =
+					loaderContext._compiler =
+					loaderContext._module =
+					loaderContext.fs =
+						/** @type {EXPECTED_ANY} */
+						(undefined);
+
+				if (!result) {
+					/** @type {NormalModuleBuildInfo} */
+					(this.buildInfo).cacheable = false;
+					return processResult(
+						err || new Error("No result from loader-runner processing"),
+						null
 					);
 				}
 
-				if (err) {
-					if (!(err instanceof Error)) {
-						err = new NonErrorEmittedError(err);
-					}
-					const currentLoader = this.getCurrentLoader(loaderContext);
-					const error = new ModuleBuildError(this, err, {
-						from:
-							currentLoader &&
-							compilation.runtimeTemplate.requestShortener.shorten(
-								currentLoader.loader
-							)
-					});
-					return callback(error);
+				const buildInfo = /** @type {NormalModuleBuildInfo} */ (this.buildInfo);
+
+				const fileDependencies =
+					/** @type {NonNullable} */
+					(buildInfo.fileDependencies);
+				const contextDependencies =
+					/** @type {NonNullable} */
+					(buildInfo.contextDependencies);
+				const missingDependencies =
+					/** @type {NonNullable} */
+					(buildInfo.missingDependencies);
+
+				fileDependencies.addAll(result.fileDependencies);
+				contextDependencies.addAll(result.contextDependencies);
+				missingDependencies.addAll(result.missingDependencies);
+				for (const loader of this.loaders) {
+					const buildDependencies =
+						/** @type {NonNullable} */
+						(buildInfo.buildDependencies);
+
+					buildDependencies.add(loader.loader);
 				}
-
-				const resourceBuffer = result.resourceBuffer;
-				const source = result.result[0];
-				const sourceMap = result.result.length >= 1 ? result.result[1] : null;
-				const extraInfo = result.result.length >= 2 ? result.result[2] : null;
-
-				if (!Buffer.isBuffer(source) && typeof source !== "string") {
-					const currentLoader = this.getCurrentLoader(loaderContext, 0);
-					const err = new Error(
-						`Final loader (${
-							currentLoader
-								? compilation.runtimeTemplate.requestShortener.shorten(
-										currentLoader.loader
-								  )
-								: "unknown"
-						}) didn't return a Buffer or String`
-					);
-					const error = new ModuleBuildError(this, err);
-					return callback(error);
+				buildInfo.cacheable = buildInfo.cacheable && result.cacheable;
+				if (result.notCacheableReasons.length > 0) {
+					buildInfo.notCacheableReasons = result.notCacheableReasons;
 				}
-
-				this._source = this.createSource(
-					this.binary ? asBuffer(source) : asString(source),
-					resourceBuffer,
-					sourceMap
-				);
-				this._ast =
-					typeof extraInfo === "object" &&
-					extraInfo !== null &&
-					extraInfo.webpackAST !== undefined
-						? extraInfo.webpackAST
-						: null;
-				return callback();
+				processResult(err, result.result);
 			}
 		);
 	}
 
+	/**
+	 * @param {Error} error the error
+	 * @returns {void}
+	 */
 	markModuleAsErrored(error) {
 		// Restore build meta from successful build to keep importing state
-		this.buildMeta = Object.assign({}, this._lastSuccessfulBuildMeta);
-
+		this.buildMeta = { ...this._lastSuccessfulBuildMeta };
 		this.error = error;
-		this.errors.push(this.error);
-		this._source = new RawSource(
-			"throw new Error(" + JSON.stringify(this.error.message) + ");"
-		);
-		this._ast = null;
+		this.addError(error);
 	}
 
+	/**
+	 * @param {Exclude} rule rule
+	 * @param {string} content content
+	 * @returns {boolean} result
+	 */
 	applyNoParseRule(rule, content) {
 		// must start with "rule" if rule is a string
 		if (typeof rule === "string") {
-			return content.indexOf(rule) === 0;
+			return content.startsWith(rule);
 		}
 
 		if (typeof rule === "function") {
@@ -371,9 +1517,11 @@ class NormalModule extends Module {
 		return rule.test(content);
 	}
 
-	// check if module should not be parsed
-	// returns "true" if the module should !not! be parsed
-	// returns "false" if the module !must! be parsed
+	/**
+	 * @param {undefined | NoParse} noParseRule no parse rule
+	 * @param {string} request request
+	 * @returns {boolean} check if module should not be parsed, returns "true" if the module should !not! be parsed, returns "false" if the module !must! be parsed
+	 */
 	shouldPreventParsing(noParseRule, request) {
 		// if no noParseRule exists, return false
 		// the module !must! be parsed.
@@ -399,6 +1547,10 @@ class NormalModule extends Module {
 		return false;
 	}
 
+	/**
+	 * @param {Compilation} compilation compilation
+	 * @private
+	 */
 	_initBuildHash(compilation) {
 		const hash = createHash(compilation.outputOptions.hashFunction);
 		if (this._source) {
@@ -407,28 +1559,63 @@ class NormalModule extends Module {
 		}
 		hash.update("meta");
 		hash.update(JSON.stringify(this.buildMeta));
-		this._buildHash = hash.digest("hex");
+		/** @type {NormalModuleBuildInfo} */
+		(this.buildInfo).hash = hash.digest("hex");
 	}
 
+	/**
+	 * Builds the module using the provided compilation context.
+	 * @param {WebpackOptions} options webpack options
+	 * @param {Compilation} compilation the compilation
+	 * @param {ResolverWithOptions} resolver the resolver
+	 * @param {InputFileSystem} fs the file system
+	 * @param {BuildCallback} callback callback function
+	 * @returns {void}
+	 */
 	build(options, compilation, resolver, fs, callback) {
-		this.buildTimestamp = Date.now();
-		this.built = true;
+		this._forceBuild = false;
 		this._source = null;
+		if (this._sourceSizes !== undefined) this._sourceSizes.clear();
+		this._sourceTypes = undefined;
 		this._ast = null;
-		this._buildHash = "";
 		this.error = null;
-		this.errors.length = 0;
-		this.warnings.length = 0;
-		this.buildMeta = {};
+		this.clearWarningsAndErrors();
+		this.clearDependenciesAndBlocks();
+		this.buildMeta = {
+			exportsType: undefined,
+			defaultObject: undefined,
+			strictHarmonyModule: undefined,
+			async: undefined
+		};
 		this.buildInfo = {
 			cacheable: false,
-			fileDependencies: new Set(),
-			contextDependencies: new Set()
+			notCacheableReasons: undefined,
+			parsed: true,
+			fileDependencies: undefined,
+			contextDependencies: undefined,
+			missingDependencies: undefined,
+			buildDependencies: undefined,
+			valueDependencies: undefined,
+			hash: undefined,
+			assets: undefined,
+			assetsInfo: undefined,
+			snapshot: undefined,
+			strict: undefined,
+			exportsArgument: undefined,
+			moduleArgument: undefined,
+			topLevelDeclarations: undefined,
+			pureFunctions: undefined,
+			inlineExports: undefined,
+			moduleConcatenationBailout: undefined,
+			needCreateRequire: undefined,
+			usesTopLevelUsingDeclaration: undefined
 		};
 
-		return this.doBuild(options, compilation, resolver, fs, err => {
-			this._cachedSources.clear();
+		const startTime = compilation.compiler.fsStartTime || Date.now();
 
+		const hooks = NormalModule.getCompilationHooks(compilation);
+
+		return this._doBuild(options, compilation, resolver, fs, hooks, (err) => {
 			// if we have an error mark module as failed and exit
 			if (err) {
 				this.markModuleAsErrored(err);
@@ -436,119 +1623,531 @@ class NormalModule extends Module {
 				return callback();
 			}
 
-			// check if this module should !not! be parsed.
-			// if so, exit here;
-			const noParseRule = options.module && options.module.noParse;
-			if (this.shouldPreventParsing(noParseRule, this.request)) {
-				this._initBuildHash(compilation);
-				return callback();
-			}
-
-			const handleParseError = e => {
-				const source = this._source.source();
-				const error = new ModuleParseError(this, source, e);
+			/**
+			 * @param {Error} e error
+			 * @returns {void}
+			 */
+			const handleParseError = (e) => {
+				const source = /** @type {Source} */ (this._source).source();
+				const loaders = this.loaders.map((item) =>
+					contextify(options.context, item.loader, compilation.compiler.root)
+				);
+				const error = new (getModuleParseError())(
+					source,
+					e,
+					loaders,
+					this.type
+				);
 				this.markModuleAsErrored(error);
 				this._initBuildHash(compilation);
 				return callback();
 			};
 
-			const handleParseResult = result => {
-				this._lastSuccessfulBuildMeta = this.buildMeta;
+			const handleParseResult = () => {
+				this.dependencies.sort(
+					concatComparators(
+						Dependency.compareLocations,
+						keepOriginalOrder(this.dependencies)
+					)
+				);
+				sortWithSourceOrder(this.dependencies, new WeakMap());
 				this._initBuildHash(compilation);
-				return callback();
+				this._lastSuccessfulBuildMeta =
+					/** @type {BuildMeta} */
+					(this.buildMeta);
+				return handleBuildDone();
 			};
 
-			try {
-				const result = this.parser.parse(
-					this._ast || this._source.source(),
-					{
-						current: this,
-						module: this,
-						compilation: compilation,
-						options: options
-					},
-					(err, result) => {
+			const handleBuildDone = () => {
+				try {
+					hooks.beforeSnapshot.call(this);
+				} catch (err) {
+					this.markModuleAsErrored(/** @type {Error} */ (err));
+					return callback();
+				}
+
+				const snapshotOptions = compilation.options.snapshot.module;
+				const { cacheable } = /** @type {NormalModuleBuildInfo} */ (
+					this.buildInfo
+				);
+				if (!cacheable || !snapshotOptions) {
+					return callback();
+				}
+				// add warning for all non-absolute paths in fileDependencies, etc
+				// This makes it easier to find problems with watching and/or caching
+				/** @type {undefined | Set} */
+				let nonAbsoluteDependencies;
+				/**
+				 * @param {FileSystemDependencies} deps deps
+				 */
+				const checkDependencies = (deps) => {
+					for (const dep of deps) {
+						if (!ABSOLUTE_PATH_REGEXP.test(dep)) {
+							if (nonAbsoluteDependencies === undefined) {
+								nonAbsoluteDependencies = new Set();
+							}
+							nonAbsoluteDependencies.add(dep);
+							deps.delete(dep);
+							try {
+								const depWithoutGlob = dep.replace(/[\\/]?\*.*$/, "");
+								const absolute = join(
+									compilation.fileSystemInfo.fs,
+									/** @type {string} */
+									(this.context),
+									depWithoutGlob
+								);
+								if (absolute !== dep && ABSOLUTE_PATH_REGEXP.test(absolute)) {
+									(depWithoutGlob !== dep
+										? /** @type {NonNullable} */
+											(
+												/** @type {NormalModuleBuildInfo} */
+												(this.buildInfo).contextDependencies
+											)
+										: deps
+									).add(absolute);
+								}
+							} catch (_err) {
+								// ignore
+							}
+						}
+					}
+				};
+				const buildInfo = /** @type {NormalModuleBuildInfo} */ (this.buildInfo);
+				const fileDependencies =
+					/** @type {NonNullable} */
+					(buildInfo.fileDependencies);
+				const contextDependencies =
+					/** @type {NonNullable} */
+					(buildInfo.contextDependencies);
+				const missingDependencies =
+					/** @type {NonNullable} */
+					(buildInfo.missingDependencies);
+				checkDependencies(fileDependencies);
+				checkDependencies(missingDependencies);
+				checkDependencies(contextDependencies);
+				if (nonAbsoluteDependencies !== undefined) {
+					const InvalidDependenciesModuleWarning =
+						getInvalidDependenciesModuleWarning();
+					this.addWarning(
+						new InvalidDependenciesModuleWarning(this, nonAbsoluteDependencies)
+					);
+				}
+				// convert file/context/missingDependencies into filesystem snapshot
+				compilation.fileSystemInfo.createSnapshot(
+					startTime,
+					fileDependencies,
+					contextDependencies,
+					missingDependencies,
+					snapshotOptions,
+					(err, snapshot) => {
 						if (err) {
-							handleParseError(err);
-						} else {
-							handleParseResult(result);
+							this.markModuleAsErrored(err);
+							return;
 						}
+						buildInfo.fileDependencies = undefined;
+						buildInfo.contextDependencies = undefined;
+						buildInfo.missingDependencies = undefined;
+						buildInfo.snapshot = snapshot;
+						return callback();
 					}
 				);
-				if (result !== undefined) {
-					// parse is sync
-					handleParseResult(result);
-				}
-			} catch (e) {
-				handleParseError(e);
+			};
+
+			try {
+				hooks.beforeParse.call(this);
+			} catch (err) {
+				this.markModuleAsErrored(/** @type {Error} */ (err));
+				this._initBuildHash(compilation);
+				return callback();
+			}
+
+			// check if this module should !not! be parsed.
+			// if so, exit here;
+			const noParseRule = options.module && options.module.noParse;
+			if (this.shouldPreventParsing(noParseRule, this.request)) {
+				// We assume that we need module and exports
+				/** @type {NormalModuleBuildInfo} */
+				(this.buildInfo).parsed = false;
+				this._initBuildHash(compilation);
+				return handleBuildDone();
 			}
+
+			try {
+				const source = /** @type {Source} */ (this._source).source();
+				/** @type {Parser} */
+				(this.parser).parse(this._ast || source, {
+					source,
+					current: this,
+					module: this,
+					compilation,
+					options,
+					harmonyNamedExports: undefined,
+					harmonyStarExports: undefined,
+					lastHarmonyImportOrder: undefined,
+					localModules: undefined
+				});
+			} catch (parseErr) {
+				handleParseError(/** @type {Error} */ (parseErr));
+				return;
+			}
+			handleParseResult();
 		});
 	}
 
-	getHashDigest(dependencyTemplates) {
-		let dtHash = dependencyTemplates.get("hash");
-		return `${this.hash}-${dtHash}`;
+	/**
+	 * Returns the reason this module cannot be concatenated, when one exists.
+	 * @param {ConcatenationBailoutReasonContext} context context
+	 * @returns {string | undefined} reason why this module can't be concatenated, undefined when it can be concatenated
+	 */
+	getConcatenationBailoutReason(context) {
+		return /** @type {Generator} */ (
+			this.generator
+		).getConcatenationBailoutReason(this, context);
 	}
 
-	source(dependencyTemplates, runtimeTemplate, type = "javascript") {
-		const hashDigest = this.getHashDigest(dependencyTemplates);
-		const cacheEntry = this._cachedSources.get(type);
-		if (cacheEntry !== undefined && cacheEntry.hash === hashDigest) {
-			// We can reuse the cached source
-			return cacheEntry.source;
-		}
-
-		const source = this.generator.generate(
+	/**
+	 * Gets side effects connection state.
+	 * @param {ModuleGraph} moduleGraph the module graph
+	 * @returns {ConnectionState} how this module should be connected to referencing modules when consumed for side-effects only
+	 */
+	getSideEffectsConnectionState(moduleGraph) {
+		// Each call owns its Tarjan bookkeeping; re-entrant calls (e.g.
+		// `ConcatenatedModule.getSideEffectsConnectionState` delegating back
+		// through here) can't clobber the outer walk.
+		return walkSideEffects(
 			this,
-			dependencyTemplates,
-			runtimeTemplate,
-			type
+			moduleGraph,
+			HarmonyImportSideEffectDependency
 		);
+	}
 
-		const cachedSource = new CachedSource(source);
-		this._cachedSources.set(type, {
-			source: cachedSource,
-			hash: hashDigest
-		});
-		return cachedSource;
+	/**
+	 * Returns the source types this module can generate.
+	 * @returns {SourceTypes} types available (do not mutate)
+	 */
+	getSourceTypes() {
+		if (this._sourceTypes === undefined) {
+			this._sourceTypes = /** @type {Generator} */ (this.generator).getTypes(
+				this
+			);
+		}
+		return this._sourceTypes;
+	}
+
+	/**
+	 * Freshly recomputed source types when they depend on incoming connections, for chunk-graph cache invalidation; undefined otherwise. #20800
+	 * @returns {SourceTypes | undefined} source types or undefined
+	 */
+	getReferencedSourceTypes() {
+		const generator = /** @type {Generator} */ (this.generator);
+		// Bypass the _sourceTypes cache: it may be stale when the module is not rebuilt.
+		return generator.getTypesDependOnIncomingConnections()
+			? generator.getTypes(this)
+			: undefined;
+	}
+
+	/**
+	 * Generates code and runtime requirements for this module.
+	 * @param {CodeGenerationContext} context context for code generation
+	 * @returns {CodeGenerationResult} result
+	 */
+	codeGeneration({
+		dependencyTemplates,
+		runtimeTemplate,
+		moduleGraph,
+		chunkGraph,
+		runtime,
+		concatenationScope,
+		codeGenerationResults,
+		sourceTypes
+	}) {
+		/** @type {RuntimeRequirements} */
+		const runtimeRequirements = new Set();
+
+		const { parsed } = /** @type {NormalModuleBuildInfo} */ (this.buildInfo);
+
+		if (!parsed) {
+			runtimeRequirements.add(RuntimeGlobals.module);
+			runtimeRequirements.add(RuntimeGlobals.exports);
+			runtimeRequirements.add(RuntimeGlobals.thisAsExports);
+		}
+
+		const getData = () => this._codeGeneratorData;
+
+		/** @type {Sources} */
+		const sources = new Map();
+		for (const type of sourceTypes || chunkGraph.getModuleSourceTypes(this)) {
+			// TODO webpack@6 make generateError required
+			const generator =
+				/** @type {Generator & { generateError?: GenerateErrorFn }} */
+				(this.generator);
+			const source = this.error
+				? generator.generateError
+					? generator.generateError(this.error, this, {
+							dependencyTemplates,
+							runtimeTemplate,
+							moduleGraph,
+							chunkGraph,
+							runtimeRequirements,
+							runtime,
+							concatenationScope,
+							codeGenerationResults,
+							getData,
+							type
+						})
+					: new RawSource(
+							`throw new Error(${JSON.stringify(this.error.message)});`
+						)
+				: generator.generate(this, {
+						dependencyTemplates,
+						runtimeTemplate,
+						moduleGraph,
+						chunkGraph,
+						runtimeRequirements,
+						runtime,
+						concatenationScope,
+						codeGenerationResults,
+						getData,
+						type
+					});
+
+			if (source) {
+				sources.set(type, new CachedSource(source));
+			}
+		}
+
+		/** @type {CodeGenerationResult} */
+		const resultEntry = {
+			sources,
+			runtimeRequirements,
+			data: this._codeGeneratorData,
+			hash: undefined
+		};
+		return resultEntry;
 	}
 
+	/**
+	 * Gets the original source.
+	 * @returns {Source | null} the original source for the module before webpack transformation
+	 */
 	originalSource() {
 		return this._source;
 	}
 
-	needRebuild(fileTimestamps, contextTimestamps) {
-		// always try to rebuild in case of an error
-		if (this.error) return true;
+	/**
+	 * Invalidates the cached state associated with this value.
+	 * @returns {void}
+	 */
+	invalidateBuild() {
+		this._forceBuild = true;
+	}
+
+	/**
+	 * Checks whether the module needs to be rebuilt for the current build state.
+	 * @param {NeedBuildContext} context context info
+	 * @param {NeedBuildCallback} callback callback function, returns true, if the module needs a rebuild
+	 * @returns {void}
+	 */
+	needBuild(context, callback) {
+		const { fileSystemInfo, compilation, valueCacheVersions } = context;
+		// build if enforced
+		if (this._forceBuild) return callback(null, true);
+
+		// always try to build in case of an error
+		if (this.error) return callback(null, true);
+
+		const { cacheable, snapshot, valueDependencies } =
+			/** @type {NormalModuleBuildInfo} */ (this.buildInfo);
+
+		// always build when module is not cacheable
+		if (!cacheable) return callback(null, true);
+
+		// build when there is no snapshot to check
+		if (!snapshot) return callback(null, true);
+
+		// build when valueDependencies have changed
+		if (valueDependencies) {
+			if (!valueCacheVersions) return callback(null, true);
+			for (const [key, value] of valueDependencies) {
+				if (value === undefined) return callback(null, true);
+				const current = valueCacheVersions.get(key);
+				if (
+					value !== current &&
+					(typeof value === "string" ||
+						typeof current === "string" ||
+						current === undefined ||
+						!isSubset(value, current))
+				) {
+					return callback(null, true);
+				}
+			}
+		}
+
+		// check snapshot for validity
+		fileSystemInfo.checkSnapshotValid(snapshot, (err, valid) => {
+			if (err) return callback(err);
+			if (!valid) return callback(null, true);
+			const hooks = NormalModule.getCompilationHooks(compilation);
+			hooks.needBuild.callAsync(this, context, (err, needBuild) => {
+				if (err) {
+					return callback(
+						HookWebpackError.makeWebpackError(
+							err,
+							"NormalModule.getCompilationHooks().needBuild"
+						)
+					);
+				}
+				callback(null, Boolean(needBuild));
+			});
+		});
+	}
 
-		// always rebuild when module is not cacheable
-		if (!this.buildInfo.cacheable) return true;
+	/**
+	 * Returns the estimated size for the requested source type.
+	 * @param {string=} type the source type for which the size should be estimated
+	 * @returns {number} the estimated size of the module (must be non-zero)
+	 */
+	size(type) {
+		const cachedSize =
+			this._sourceSizes === undefined ? undefined : this._sourceSizes.get(type);
+		if (cachedSize !== undefined) {
+			return cachedSize;
+		}
+		const size = Math.max(
+			1,
+			/** @type {Generator} */ (this.generator).getSize(this, type)
+		);
+		if (this._sourceSizes === undefined) {
+			this._sourceSizes = new Map();
+		}
+		this._sourceSizes.set(type, size);
+		return size;
+	}
 
-		// Check timestamps of all dependencies
-		// Missing timestamp -> need rebuild
-		// Timestamp bigger than buildTimestamp -> need rebuild
-		for (const file of this.buildInfo.fileDependencies) {
-			const timestamp = fileTimestamps.get(file);
-			if (!timestamp) return true;
-			if (timestamp >= this.buildTimestamp) return true;
+	/**
+	 * Adds the provided file dependencies to the module.
+	 * @param {FileSystemDependencies} fileDependencies set where file dependencies are added to
+	 * @param {FileSystemDependencies} contextDependencies set where context dependencies are added to
+	 * @param {FileSystemDependencies} missingDependencies set where missing dependencies are added to
+	 * @param {FileSystemDependencies} buildDependencies set where build dependencies are added to
+	 */
+	addCacheDependencies(
+		fileDependencies,
+		contextDependencies,
+		missingDependencies,
+		buildDependencies
+	) {
+		const { snapshot, buildDependencies: buildDeps } =
+			/** @type {NormalModuleBuildInfo} */ (this.buildInfo);
+		if (snapshot) {
+			fileDependencies.addAll(snapshot.getFileIterable());
+			contextDependencies.addAll(snapshot.getContextIterable());
+			missingDependencies.addAll(snapshot.getMissingIterable());
+		} else {
+			const {
+				fileDependencies: fileDeps,
+				contextDependencies: contextDeps,
+				missingDependencies: missingDeps
+			} = /** @type {NormalModuleBuildInfo} */ (this.buildInfo);
+			if (fileDeps !== undefined) fileDependencies.addAll(fileDeps);
+			if (contextDeps !== undefined) contextDependencies.addAll(contextDeps);
+			if (missingDeps !== undefined) missingDependencies.addAll(missingDeps);
 		}
-		for (const file of this.buildInfo.contextDependencies) {
-			const timestamp = contextTimestamps.get(file);
-			if (!timestamp) return true;
-			if (timestamp >= this.buildTimestamp) return true;
+		if (buildDeps !== undefined) {
+			buildDependencies.addAll(buildDeps);
 		}
-		// elsewise -> no rebuild needed
-		return false;
 	}
 
-	size() {
-		return this._source ? this._source.size() : -1;
+	/**
+	 * Updates the hash with the data contributed by this instance.
+	 * @param {Hash} hash the hash used to track dependencies
+	 * @param {UpdateHashContext} context context
+	 * @returns {void}
+	 */
+	updateHash(hash, context) {
+		const buildInfo = /** @type {NormalModuleBuildInfo} */ (this.buildInfo);
+		hash.update(
+			/** @type {string} */
+			(buildInfo.hash)
+		);
+		// Clear cached source types and re-compute so that changes in incoming
+		// connections (e.g. asset module newly referenced from JS via lazy
+		// compilation) are reflected in the hash and trigger code generation
+		// cache invalidation.
+		// https://github.com/webpack/webpack/issues/20800
+		this._sourceTypes = undefined;
+		for (const type of this.getSourceTypes()) {
+			hash.update(type);
+		}
+		/** @type {Generator} */
+		(this.generator).updateHash(hash, {
+			module: this,
+			...context
+		});
+		super.updateHash(hash, context);
 	}
 
-	updateHash(hash) {
-		hash.update(this._buildHash);
-		super.updateHash(hash);
+	/**
+	 * Serializes this instance into the provided serializer context.
+	 * @param {ObjectSerializerContext} context context
+	 */
+	serialize(context) {
+		const { write } = context;
+		// deserialize
+		write(this._source);
+		write(this.error);
+		write(this._lastSuccessfulBuildMeta);
+		write(this._forceBuild);
+		write(this._codeGeneratorData);
+		write(this.hot);
+		super.serialize(context);
+	}
+
+	/**
+	 * @param {ObjectDeserializerContext} context context
+	 * @returns {NormalModule} module
+	 */
+	static deserialize(context) {
+		// `new this` so subclasses without extra constructor options inherit this method
+		const obj = new this({
+			// will be deserialized by Module
+			layer: /** @type {EXPECTED_ANY} */ (null),
+			type: "",
+			// will be filled by updateCacheModule
+			resource: "",
+			context: "",
+			request: /** @type {EXPECTED_ANY} */ (null),
+			userRequest: /** @type {EXPECTED_ANY} */ (null),
+			rawRequest: /** @type {EXPECTED_ANY} */ (null),
+			loaders: /** @type {EXPECTED_ANY} */ (null),
+			matchResource: /** @type {EXPECTED_ANY} */ (null),
+			parser: /** @type {EXPECTED_ANY} */ (null),
+			parserOptions: /** @type {EXPECTED_ANY} */ (null),
+			generator: /** @type {EXPECTED_ANY} */ (null),
+			generatorOptions: /** @type {EXPECTED_ANY} */ (null),
+			resolveOptions: /** @type {EXPECTED_ANY} */ (null),
+			extractSourceMap: /** @type {EXPECTED_ANY} */ (null)
+		});
+		obj.deserialize(context);
+		return obj;
+	}
+
+	/**
+	 * Restores this instance from the provided deserializer context.
+	 * @param {ObjectDeserializerContext} context context
+	 */
+	deserialize(context) {
+		const { read } = context;
+		this._source = read();
+		this.error = read();
+		this._lastSuccessfulBuildMeta = read();
+		this._forceBuild = read();
+		this._codeGeneratorData = read();
+		this.hot = read();
+		super.deserialize(context);
 	}
 }
 
+makeSerializable(NormalModule, "webpack/lib/NormalModule");
+
 module.exports = NormalModule;
diff --git a/lib/NormalModuleFactory.js b/lib/NormalModuleFactory.js
index 23ef0ae1dde..148a5a4b458 100644
--- a/lib/NormalModuleFactory.js
+++ b/lib/NormalModuleFactory.js
@@ -1,443 +1,1820 @@
 /*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Tobias Koppers @sokra
- */
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
 "use strict";
 
-const path = require("path");
 const asyncLib = require("neo-async");
 const {
-	Tapable,
-	AsyncSeriesWaterfallHook,
-	SyncWaterfallHook,
+	AsyncSeriesBailHook,
+	AsyncSeriesHook,
+	HookMap,
 	SyncBailHook,
 	SyncHook,
-	HookMap
+	SyncWaterfallHook
 } = require("tapable");
+const ChunkGraph = require("./ChunkGraph");
+const Module = require("./Module");
+const ModuleFactory = require("./ModuleFactory");
+const ModuleGraph = require("./ModuleGraph");
+const { JAVASCRIPT_MODULE_TYPE_AUTO } = require("./ModuleTypeConstants");
 const NormalModule = require("./NormalModule");
-const RawModule = require("./RawModule");
-const RuleSet = require("./RuleSet");
-const cachedMerge = require("./util/cachedMerge");
+const { ImportPhaseUtils } = require("./dependencies/ImportPhase");
+const { getContext } = require("./loaders/LoaderRunner");
+const BasicEffectRulePlugin = require("./rules/BasicEffectRulePlugin");
+const BasicMatcherRulePlugin = require("./rules/BasicMatcherRulePlugin");
+const ObjectMatcherRulePlugin = require("./rules/ObjectMatcherRulePlugin");
+const RuleSetCompiler = require("./rules/RuleSetCompiler");
+const UseEffectRulePlugin = require("./rules/UseEffectRulePlugin");
+const LazySet = require("./util/LazySet");
+const { getScheme } = require("./util/URLAbsoluteSpecifier");
+const { cachedCleverMerge, cachedSetProperty } = require("./util/cleverMerge");
+const {
+	applyCaseCorrections,
+	findCaseMismatch
+} = require("./util/findCaseMismatch");
+const { dirname, join, relative } = require("./util/fs");
+const {
+	ABSOLUTE_PATH_REGEXP,
+	WINDOWS_PATH_SEPARATOR_REGEXP,
+	escapeHashInPathRequest,
+	parseResource,
+	parseResourceWithoutFragment
+} = require("./util/identifier");
+
+/** @typedef {import("enhanced-resolve").Resolver["options"]["alias"]} AliasOptions */
+/** @typedef {import("enhanced-resolve").ResolveContext} ResolveContext */
+/** @typedef {import("enhanced-resolve").ResolveRequest} ResolveRequest */
+/** @typedef {import("../declarations/WebpackOptions").ModuleOptionsNormalized} ModuleOptions */
+/** @typedef {import("../declarations/WebpackOptions").RuleSetRule} RuleSetRule */
+/** @typedef {import("./Compilation").FileSystemDependencies} FileSystemDependencies */
+/** @typedef {import("./Generator")} Generator */
+/** @typedef {import("./ModuleFactory").ModuleFactoryCallback} ModuleFactoryCallback */
+/** @typedef {import("./ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */
+/** @typedef {import("./ModuleFactory").ModuleFactoryCreateDataContextInfo} ModuleFactoryCreateDataContextInfo */
+/** @typedef {import("./ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */
+/** @typedef {import("./NormalModule").GeneratorOptions} GeneratorOptions */
+/** @typedef {import("./NormalModule").LoaderItem} LoaderItem */
+/** @typedef {import("./NormalModule").NormalModuleCreateData} NormalModuleCreateData */
+/** @typedef {import("./NormalModule").ParserOptions} ParserOptions */
+/** @typedef {import("./Parser")} Parser */
+/** @typedef {import("./ResolverFactory")} ResolverFactory */
+/** @typedef {import("./ResolverFactory").ResolverWithOptions} ResolverWithOptions */
+/** @typedef {import("./dependencies/ModuleDependency")} ModuleDependency */
+/** @typedef {import("./dependencies/ImportPhase").ImportPhaseType} ImportPhaseType */
+/** @typedef {import("./dependencies/ImportPhase").ImportPhaseName} ImportPhaseName */
+/** @typedef {import("./javascript/JavascriptParser").ImportAttributes} ImportAttributes */
+/** @typedef {import("./rules/RuleSetCompiler").RuleSetRules} RuleSetRules */
+/** @typedef {import("./rules/RuleSetCompiler").RuleSet} RuleSet */
+/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
+/** @typedef {import("./util/identifier").AssociatedObjectForCache} AssociatedObjectForCache */
+
+/**
+ * Defines the callback type used by this module.
+ * @template T
+ * @typedef {import("./Compiler").Callback} Callback
+ */
+
+/** @typedef {Pick} ModuleSettings */
+/** @typedef {NormalModuleCreateData & { settings: ModuleSettings }} CreateData */
+
+/**
+ * Defines the resolve data type used by this module.
+ * @typedef {object} ResolveData
+ * @property {ModuleFactoryCreateData["contextInfo"]} contextInfo
+ * @property {ModuleFactoryCreateData["resolveOptions"]} resolveOptions
+ * @property {string} context
+ * @property {string} request
+ * @property {ImportPhaseName=} phase
+ * @property {ImportAttributes=} attributes
+ * @property {ModuleDependency[]} dependencies
+ * @property {string} dependencyType
+ * @property {Partial} createData
+ * @property {FileSystemDependencies} fileDependencies
+ * @property {FileSystemDependencies} missingDependencies
+ * @property {FileSystemDependencies} contextDependencies
+ * @property {Module=} ignoredModule
+ * @property {boolean} cacheable allow to use the unsafe cache
+ */
+
+/**
+ * Defines the resource data type used by this module.
+ * @typedef {object} ResourceData
+ * @property {string} resource
+ * @property {string=} path
+ * @property {string=} query
+ * @property {string=} fragment
+ * @property {string=} context
+ */
+
+/**
+ * Defines the resource scheme data type used by this module.
+ * @typedef {object} ResourceSchemeData
+ * @property {string=} mimetype mime type of the resource
+ * @property {string=} parameters additional parameters for the resource
+ * @property {"base64" | false=} encoding encoding of the resource
+ * @property {string=} encodedContent encoded content of the resource
+ */
+
+/** @typedef {ResourceData & { data: ResourceSchemeData & Partial }} ResourceDataWithData */
+
+/**
+ * Defines the parsed loader request type used by this module.
+ * @typedef {object} ParsedLoaderRequest
+ * @property {string} loader loader
+ * @property {string | undefined} options options
+ */
+
+/**
+ * Dependencies captured while resolving a loader.
+ * @typedef {object} ResolveDependencies
+ * @property {LazySet} fileDependencies file dependencies of the resolve
+ * @property {LazySet} contextDependencies context dependencies of the resolve
+ * @property {LazySet} missingDependencies missing dependencies of the resolve
+ */
+
+/**
+ * Memoized result of resolving a loader for a given (context, request).
+ * @typedef {ResolveDependencies & { loader: string, query: string | undefined, type: string | undefined }} LoaderResolveCacheEntry
+ */
+
+/** @typedef {import("./ModuleTypeConstants").JAVASCRIPT_MODULE_TYPE_AUTO} JAVASCRIPT_MODULE_TYPE_AUTO */
+/** @typedef {import("./ModuleTypeConstants").JAVASCRIPT_MODULE_TYPE_DYNAMIC} JAVASCRIPT_MODULE_TYPE_DYNAMIC */
+/** @typedef {import("./ModuleTypeConstants").JAVASCRIPT_MODULE_TYPE_ESM} JAVASCRIPT_MODULE_TYPE_ESM */
+/** @typedef {import("./ModuleTypeConstants").JSON_MODULE_TYPE} JSON_MODULE_TYPE */
+/** @typedef {import("./ModuleTypeConstants").ASSET_MODULE_TYPE} ASSET_MODULE_TYPE */
+/** @typedef {import("./ModuleTypeConstants").ASSET_MODULE_TYPE_INLINE} ASSET_MODULE_TYPE_INLINE */
+/** @typedef {import("./ModuleTypeConstants").ASSET_MODULE_TYPE_RESOURCE} ASSET_MODULE_TYPE_RESOURCE */
+/** @typedef {import("./ModuleTypeConstants").ASSET_MODULE_TYPE_SOURCE} ASSET_MODULE_TYPE_SOURCE */
+/** @typedef {import("./ModuleTypeConstants").ASSET_MODULE_TYPE_BYTES} ASSET_MODULE_TYPE_BYTES */
+/** @typedef {import("./ModuleTypeConstants").WEBASSEMBLY_MODULE_TYPE_ASYNC} WEBASSEMBLY_MODULE_TYPE_ASYNC */
+/** @typedef {import("./ModuleTypeConstants").WEBASSEMBLY_MODULE_TYPE_SYNC} WEBASSEMBLY_MODULE_TYPE_SYNC */
+/** @typedef {import("./ModuleTypeConstants").CSS_MODULE_TYPE} CSS_MODULE_TYPE */
+/** @typedef {import("./ModuleTypeConstants").CSS_MODULE_TYPE_GLOBAL} CSS_MODULE_TYPE_GLOBAL */
+/** @typedef {import("./ModuleTypeConstants").CSS_MODULE_TYPE_MODULE} CSS_MODULE_TYPE_MODULE */
+/** @typedef {import("./ModuleTypeConstants").CSS_MODULE_TYPE_AUTO} CSS_MODULE_TYPE_AUTO */
+/** @typedef {import("./ModuleTypeConstants").HTML_MODULE_TYPE} HTML_MODULE_TYPE */
+
+/** @typedef {JAVASCRIPT_MODULE_TYPE_AUTO | JAVASCRIPT_MODULE_TYPE_DYNAMIC | JAVASCRIPT_MODULE_TYPE_ESM | JSON_MODULE_TYPE | ASSET_MODULE_TYPE | ASSET_MODULE_TYPE_INLINE | ASSET_MODULE_TYPE_RESOURCE | ASSET_MODULE_TYPE_SOURCE | WEBASSEMBLY_MODULE_TYPE_ASYNC | WEBASSEMBLY_MODULE_TYPE_SYNC | CSS_MODULE_TYPE | CSS_MODULE_TYPE_GLOBAL | CSS_MODULE_TYPE_MODULE | CSS_MODULE_TYPE_AUTO | HTML_MODULE_TYPE} KnownNormalModuleTypes */
+/** @typedef {KnownNormalModuleTypes | string} NormalModuleTypes */
 
 const EMPTY_RESOLVE_OPTIONS = {};
+/** @type {ParserOptions} */
+const EMPTY_PARSER_OPTIONS = {};
+/** @type {GeneratorOptions} */
+const EMPTY_GENERATOR_OPTIONS = {};
+/** @type {ParsedLoaderRequest[]} */
+const EMPTY_ELEMENTS = [];
 
 const MATCH_RESOURCE_REGEX = /^([^!]+)!=!/;
+const LEADING_DOT_EXTENSION_REGEX = /^[^.]/;
+// Nothing in `util/identifier` matches a request (as opposed to a path) shape.
+const RELATIVE_REQUEST_REGEXP = /^\.\.?[\\/]/;
+
+/**
+ * Computes the edit distance of two strings, counting swapped neighbors as one
+ * edit and giving up as soon as the distance exceeds `max`.
+ * @param {string} a first string
+ * @param {string} b second string
+ * @param {number} max largest distance worth distinguishing
+ * @returns {number} the distance, or a value greater than `max` when it exceeds `max`
+ */
+const editDistance = (a, b, max) => {
+	if (a === b) return 0;
+	if (Math.abs(a.length - b.length) > max) return max + 1;
+	/** @type {number[] | undefined} */
+	let rowBeforePrevious;
+	/** @type {number[]} */
+	let previousRow = [];
+	for (let column = 0; column <= b.length; column++) previousRow.push(column);
+	for (let row = 1; row <= a.length; row++) {
+		const currentRow = [row];
+		let rowMinimum = row;
+		for (let column = 1; column <= b.length; column++) {
+			let distance = Math.min(
+				previousRow[column - 1] + (a[row - 1] === b[column - 1] ? 0 : 1),
+				previousRow[column] + 1,
+				currentRow[column - 1] + 1
+			);
+			if (
+				rowBeforePrevious &&
+				a[row - 1] === b[column - 2] &&
+				a[row - 2] === b[column - 1]
+			) {
+				distance = Math.min(distance, rowBeforePrevious[column - 2] + 1);
+			}
+			currentRow.push(distance);
+			if (distance < rowMinimum) rowMinimum = distance;
+		}
+		if (rowMinimum > max) return max + 1;
+		rowBeforePrevious = previousRow;
+		previousRow = currentRow;
+	}
+	return previousRow[b.length];
+};
+
+/**
+ * Names an entry of a directory can be requested by: as written, and without
+ * the extension the resolver would have appended.
+ * @param {string} entry name of a directory entry
+ * @param {Set} extensions the resolvable extensions
+ * @returns {string[]} names the entry answers to
+ */
+const namesForEntry = (entry, extensions) => {
+	const names = [entry];
+	for (const extension of extensions) {
+		if (entry.length > extension.length && entry.endsWith(extension)) {
+			names.push(entry.slice(0, -extension.length));
+		}
+	}
+	return names;
+};
 
-const loaderToIdent = data => {
+/**
+ * Picks the entry of a directory a failed request most likely meant. Ties are
+ * broken by name so the hint does not depend on the order the directory lists.
+ * @param {string} requested the requested file name
+ * @param {string[]} entries names in the directory
+ * @param {Set} extensions the resolvable extensions
+ * @returns {{ entry: string, distance: number } | undefined} the closest entry and how far it is from the request
+ */
+const findClosestEntry = (requested, entries, extensions) => {
+	// A typo is only distinguishable from an unrelated name once the name is
+	// long enough, so short requests are matched by casing alone.
+	let allowedDistance = requested.length < 4 ? 0 : requested.length < 8 ? 1 : 2;
+	const lowerCaseRequested = requested.toLowerCase();
+	/** @type {{ entry: string, distance: number } | undefined} */
+	let best;
+	for (const entry of entries) {
+		// The entry exists as requested, so the request failed for another reason.
+		if (entry === requested) return;
+		for (const name of namesForEntry(entry, extensions)) {
+			const distance = editDistance(
+				lowerCaseRequested,
+				name.toLowerCase(),
+				allowedDistance
+			);
+			if (distance > allowedDistance) continue;
+			if (best && distance === allowedDistance && entry >= best.entry) continue;
+			allowedDistance = distance;
+			best = { entry, distance };
+		}
+	}
+	return best;
+};
+
+/** Bounds the walk up the context when a module request is looked up. */
+const MAX_MODULE_LOOKUP_DIRECTORIES = 10;
+
+/** Bounds the paths a failed request is looked for under, aliases included. */
+const MAX_SUGGESTION_CANDIDATES = 20;
+
+/**
+ * Directories a module request is looked up in, nearest first.
+ * @param {InputFileSystem} fs input file system
+ * @param {string} context directory the request comes from
+ * @param {Iterable} modules the `resolve.modules` entries
+ * @returns {string[]} absolute directories
+ */
+const moduleLookupDirectories = (fs, context, modules) => {
+	/** @type {string[]} */
+	const relativeNames = [];
+	/** @type {string[]} */
+	const directories = [];
+	for (const entry of modules) {
+		for (const name of Array.isArray(entry) ? entry : [entry]) {
+			if (ABSOLUTE_PATH_REGEXP.test(name)) {
+				directories.push(name);
+			} else {
+				relativeNames.push(name);
+			}
+		}
+	}
+	let current = context;
+	while (
+		relativeNames.length > 0 &&
+		directories.length < MAX_MODULE_LOOKUP_DIRECTORIES
+	) {
+		for (const name of relativeNames) directories.push(join(fs, current, name));
+		const parent = dirname(fs, current);
+		if (parent === current) break;
+		current = parent;
+	}
+	return directories;
+};
+
+/**
+ * Requests an alias could have turned this one into, so a hint can look where
+ * the resolver looked. Mirrors `AliasPlugin`: a name matches the whole request
+ * or a leading segment of it, and `onlyModule` restricts it to the former.
+ * @param {string} request the request as written
+ * @param {AliasOptions} aliases the `resolve.alias` entries
+ * @returns {string[]} the requests the aliases map this one to
+ */
+const aliasedRequests = (request, aliases) => {
+	/** @type {string[]} */
+	const requests = [];
+	for (const { name, alias, onlyModule } of aliases) {
+		if (!request.startsWith(name)) continue;
+		if (
+			request.length !== name.length &&
+			(onlyModule || request.charCodeAt(name.length) !== 47) /* "/" */
+		) {
+			continue;
+		}
+		const tail = request.slice(name.length);
+		for (const target of Array.isArray(alias) ? alias : [alias]) {
+			// `false` tells the resolver to ignore the request, so it names no path
+			if (typeof target === "string") requests.push(target + tail);
+		}
+	}
+	return requests;
+};
+
+/**
+ * Whether a request spells out directories of its own, as opposed to naming an
+ * entry of the directory it is resolved against.
+ * @param {string} prefix the request without its last segment
+ * @returns {boolean} true when the prefix names at least one directory
+ */
+const namesDirectories = (prefix) =>
+	prefix
+		.replace(WINDOWS_PATH_SEPARATOR_REGEXP, "/")
+		.split("/")
+		.some((segment) => segment !== "" && segment !== "." && segment !== "..");
+
+/**
+ * Replays the file/context/missing dependencies captured by a loader resolve
+ * into a resolve context, so memoized loaders still record everything needed
+ * for watch invalidation.
+ * @param {ResolveContext} resolveContext resolve context to add dependencies to
+ * @param {ResolveDependencies} source captured resolve dependencies
+ * @returns {void}
+ */
+const replayResolveDependencies = (resolveContext, source) => {
+	// On this path resolveContext always carries the three LazySet dependency
+	// sets built in the `resolve` hook, so they are safe to treat as LazySet.
+	/** @type {LazySet} */
+	(resolveContext.fileDependencies).addAll(source.fileDependencies);
+	/** @type {LazySet} */
+	(resolveContext.contextDependencies).addAll(source.contextDependencies);
+	/** @type {LazySet} */
+	(resolveContext.missingDependencies).addAll(source.missingDependencies);
+};
+
+/**
+ * Returns ident.
+ * @param {LoaderItem} data data
+ * @returns {string} ident
+ */
+const loaderToIdent = (data) => {
 	if (!data.options) {
 		return data.loader;
 	}
 	if (typeof data.options === "string") {
-		return data.loader + "?" + data.options;
+		return `${data.loader}?${data.options}`;
 	}
 	if (typeof data.options !== "object") {
 		throw new Error("loader options must be string or object");
 	}
 	if (data.ident) {
-		return data.loader + "??" + data.ident;
+		return `${data.loader}??${data.ident}`;
 	}
-	return data.loader + "?" + JSON.stringify(data.options);
+	return `${data.loader}?${JSON.stringify(data.options)}`;
 };
 
-const identToLoaderRequest = resultString => {
-	const idx = resultString.indexOf("?");
-	if (idx >= 0) {
-		const loader = resultString.substr(0, idx);
-		const options = resultString.substr(idx + 1);
-		return {
-			loader,
-			options
-		};
-	} else {
-		return {
-			loader: resultString,
-			options: undefined
-		};
+/**
+ * Stringify loaders and resource.
+ * @param {LoaderItem[]} loaders loaders
+ * @param {string} resource resource
+ * @returns {string} stringified loaders and resource
+ */
+const stringifyLoadersAndResource = (loaders, resource) => {
+	let str = "";
+	for (const loader of loaders) {
+		str += `${loaderToIdent(loader)}!`;
 	}
+	return str + resource;
 };
 
-const dependencyCache = new WeakMap();
+/**
+ * Checks whether it needs calls.
+ * @param {number} times times
+ * @param {(err?: null | Error) => void} callback callback
+ * @returns {(err?: null | Error) => void} callback
+ */
+const needCalls = (times, callback) => (err) => {
+	if (--times === 0) {
+		return callback(err);
+	}
+	if (err && times > 0) {
+		times = Number.NaN;
+		return callback(err);
+	}
+};
+
+/**
+ * Merges global options.
+ * @template T
+ * @template O
+ * @param {T} globalOptions global options
+ * @param {string} type type
+ * @param {O} localOptions local options
+ * @returns {T & O | T | O} result
+ */
+const mergeGlobalOptions = (globalOptions, type, localOptions) => {
+	const parts = type.split("/");
+	/** @type {undefined | T} */
+	let result;
+	let current = "";
+	for (const part of parts) {
+		current = current ? `${current}/${part}` : part;
+		const options =
+			/** @type {T} */
+			(globalOptions[/** @type {keyof T} */ (current)]);
+		if (typeof options === "object") {
+			result =
+				result === undefined ? options : cachedCleverMerge(result, options);
+		}
+	}
+	if (result === undefined) {
+		return localOptions;
+	}
+	return cachedCleverMerge(result, localOptions);
+};
+
+// TODO webpack 6 remove
+/**
+ * Deprecation changed hook message.
+ * @template {import("tapable").Hook} T
+ * @param {string} name name
+ * @param {T} hook hook
+ * @returns {string} result
+ */
+const deprecationChangedHookMessage = (name, hook) => {
+	const names = hook.taps.map((tapped) => tapped.name).join(", ");
+
+	return (
+		`NormalModuleFactory.${name} (${names}) is no longer a waterfall hook, but a bailing hook instead. ` +
+		"Do not return the passed object, but modify it instead. " +
+		"Returning false will ignore the request and results in no module created."
+	);
+};
+
+const ruleSetCompiler = new RuleSetCompiler([
+	new BasicMatcherRulePlugin("test", "resource"),
+	new BasicMatcherRulePlugin("scheme"),
+	new BasicMatcherRulePlugin("mimetype"),
+	new BasicMatcherRulePlugin("dependency"),
+	new BasicMatcherRulePlugin("include", "resource"),
+	new BasicMatcherRulePlugin("exclude", "resource", true),
+	new BasicMatcherRulePlugin("resource"),
+	new BasicMatcherRulePlugin("resourceQuery"),
+	new BasicMatcherRulePlugin("resourceFragment"),
+	new BasicMatcherRulePlugin("realResource"),
+	new BasicMatcherRulePlugin("issuer"),
+	new BasicMatcherRulePlugin("compiler"),
+	new BasicMatcherRulePlugin("issuerLayer"),
+	new BasicMatcherRulePlugin("phase"),
+	new ObjectMatcherRulePlugin("assert", "attributes", (value) => {
+		if (value) {
+			return (
+				/** @type {ImportAttributes} */ (value)._isLegacyAssert !== undefined
+			);
+		}
+
+		return false;
+	}),
+	new ObjectMatcherRulePlugin("with", "attributes", (value) => {
+		if (value) {
+			return !(/** @type {ImportAttributes} */ (value)._isLegacyAssert);
+		}
+		return false;
+	}),
+	new ObjectMatcherRulePlugin("descriptionData"),
+	new BasicMatcherRulePlugin("descriptionRelativePath"),
+	new BasicEffectRulePlugin("type"),
+	new BasicEffectRulePlugin("sideEffects"),
+	new BasicEffectRulePlugin("parser"),
+	new BasicEffectRulePlugin("resolve"),
+	new BasicEffectRulePlugin("generator"),
+	new BasicEffectRulePlugin("layer"),
+	new BasicEffectRulePlugin("extractSourceMap"),
+	new UseEffectRulePlugin()
+]);
+
+/** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */
+/** @typedef {import("../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */
+/** @typedef {import("./javascript/JavascriptGenerator")} JavascriptGenerator */
+/** @typedef {import("../declarations/WebpackOptions").EmptyGeneratorOptions} EmptyGeneratorOptions */
+
+/** @typedef {import("./json/JsonParser")} JsonParser */
+/** @typedef {import("../declarations/WebpackOptions").JsonParserOptions} JsonParserOptions */
+/** @typedef {import("./json/JsonGenerator")} JsonGenerator */
+/** @typedef {import("../declarations/WebpackOptions").JsonGeneratorOptions} JsonGeneratorOptions */
 
-class NormalModuleFactory extends Tapable {
-	constructor(context, resolverFactory, options) {
+/** @typedef {import("./asset/AssetParser")} AssetParser */
+/** @typedef {import("./asset/AssetSourceParser")} AssetSourceParser */
+/** @typedef {import("./asset/AssetBytesParser")} AssetBytesParser */
+/** @typedef {import("../declarations/WebpackOptions").AssetParserOptions} AssetParserOptions */
+/** @typedef {import("../declarations/WebpackOptions").EmptyParserOptions} EmptyParserOptions */
+/** @typedef {import("./asset/AssetGenerator")} AssetGenerator */
+/** @typedef {import("../declarations/WebpackOptions").AssetGeneratorOptions} AssetGeneratorOptions */
+/** @typedef {import("../declarations/WebpackOptions").AssetInlineGeneratorOptions} AssetInlineGeneratorOptions */
+/** @typedef {import("../declarations/WebpackOptions").AssetResourceGeneratorOptions} AssetResourceGeneratorOptions */
+/** @typedef {import("./asset/AssetSourceGenerator")} AssetSourceGenerator */
+/** @typedef {import("./asset/AssetBytesGenerator")} AssetBytesGenerator */
+
+/** @typedef {import("./wasm-async/AsyncWebAssemblyParser")} AsyncWebAssemblyParser */
+/** @typedef {import("./wasm-sync/WebAssemblyParser")} WebAssemblyParser */
+
+/** @typedef {import("./css/CssParser")} CssParser */
+/** @typedef {import("../declarations/WebpackOptions").CssParserOptions} CssParserOptions */
+/** @typedef {import("../declarations/WebpackOptions").CssModuleParserOptions} CssModuleParserOptions */
+/** @typedef {import("./css/CssGenerator")} CssGenerator */
+/** @typedef {import("../declarations/WebpackOptions").CssGeneratorOptions} CssGeneratorOptions */
+/** @typedef {import("../declarations/WebpackOptions").CssModuleGeneratorOptions} CssModuleGeneratorOptions */
+
+/** @typedef {import("./html/HtmlParser")} HtmlParser */
+/** @typedef {import("../declarations/WebpackOptions").EmptyParserOptions} HtmlParserOptions */
+/** @typedef {import("./html/HtmlGenerator")} HtmlGenerator */
+/** @typedef {import("../declarations/WebpackOptions").HtmlGeneratorOptions} HtmlGeneratorOptions */
+
+/* eslint-disable jsdoc/type-formatting */
+/**
+ * Defines the shared type used by this module.
+ * @typedef {[
+ * [JAVASCRIPT_MODULE_TYPE_AUTO, JavascriptParser, JavascriptParserOptions, JavascriptGenerator, EmptyGeneratorOptions],
+ * [JAVASCRIPT_MODULE_TYPE_DYNAMIC, JavascriptParser, JavascriptParserOptions, JavascriptGenerator, EmptyGeneratorOptions],
+ * [JAVASCRIPT_MODULE_TYPE_ESM, JavascriptParser, JavascriptParserOptions, JavascriptGenerator, EmptyGeneratorOptions],
+ * [JSON_MODULE_TYPE, JsonParser, JsonParserOptions, JsonGenerator, JsonGeneratorOptions],
+ * [ASSET_MODULE_TYPE, AssetParser, AssetParserOptions, AssetGenerator, AssetGeneratorOptions],
+ * [ASSET_MODULE_TYPE_INLINE, AssetParser, EmptyParserOptions, AssetGenerator, AssetGeneratorOptions],
+ * [ASSET_MODULE_TYPE_RESOURCE, AssetParser, EmptyParserOptions, AssetGenerator, AssetGeneratorOptions],
+ * [ASSET_MODULE_TYPE_SOURCE, AssetSourceParser, EmptyParserOptions, AssetSourceGenerator, EmptyGeneratorOptions],
+ * [ASSET_MODULE_TYPE_BYTES, AssetBytesParser, EmptyParserOptions, AssetBytesGenerator, EmptyGeneratorOptions],
+ * [WEBASSEMBLY_MODULE_TYPE_ASYNC, AsyncWebAssemblyParser, EmptyParserOptions, Generator, EmptyGeneratorOptions],
+ * [WEBASSEMBLY_MODULE_TYPE_SYNC, WebAssemblyParser, EmptyParserOptions, Generator, EmptyGeneratorOptions],
+ * [CSS_MODULE_TYPE, CssParser, CssParserOptions, CssGenerator, CssGeneratorOptions],
+ * [CSS_MODULE_TYPE_AUTO, CssParser, CssModuleParserOptions, CssGenerator, CssModuleGeneratorOptions],
+ * [CSS_MODULE_TYPE_MODULE, CssParser, CssModuleParserOptions, CssGenerator, CssModuleGeneratorOptions],
+ * [CSS_MODULE_TYPE_GLOBAL, CssParser, CssModuleParserOptions, CssGenerator, CssModuleGeneratorOptions],
+ * [HTML_MODULE_TYPE, HtmlParser, HtmlParserOptions, HtmlGenerator, HtmlGeneratorOptions],
+ * [string, Parser, ParserOptions, Generator, GeneratorOptions],
+ * ]} ParsersAndGeneratorsByTypes
+ */
+/* eslint-enable jsdoc/type-formatting */
+
+/**
+ * Defines the extract tuple elements type used by this module.
+ * @template {unknown[]} T
+ * @template {number[]} I
+ * @typedef {{ [K in keyof I]: K extends keyof I ? I[K] extends keyof T ? T[I[K]] : never : never }} ExtractTupleElements
+ */
+
+/**
+ * Represents the normal module factory runtime component.
+ * @template {unknown[]} T
+ * @template {number[]} A
+ * @template [R=void]
+ * @typedef {T extends [infer Head extends [string, ...unknown[]], ...infer Tail extends [string, ...unknown[]][]] ? Record, R extends number ? Head[R] : R>> & RecordFactoryFromTuple : unknown } RecordFactoryFromTuple
+ */
+
+/**
+ * Maps each tuple in `T` to a record from its `[0]` key to its `[I]` value.
+ * @template {unknown[]} T
+ * @template {number} I
+ * @typedef {T extends [infer Head extends [string, ...unknown[]], ...infer Tail extends [string, ...unknown[]][]] ? Record & TupleToTypeMap : unknown } TupleToTypeMap
+ */
+
+/** @typedef {TupleToTypeMap} ParserByType */
+/** @typedef {TupleToTypeMap} ParserOptionsByType */
+/** @typedef {TupleToTypeMap} GeneratorByType */
+/** @typedef {TupleToTypeMap} GeneratorOptionsByType */
+
+class NormalModuleFactory extends ModuleFactory {
+	/**
+	 * Creates an instance of NormalModuleFactory.
+	 * @param {object} param params
+	 * @param {string=} param.context context
+	 * @param {InputFileSystem} param.fs file system
+	 * @param {ResolverFactory} param.resolverFactory resolverFactory
+	 * @param {ModuleOptions} param.options options
+	 * @param {AssociatedObjectForCache} param.associatedObjectForCache an object to which the cache will be attached
+	 */
+	constructor({
+		context,
+		fs,
+		resolverFactory,
+		options,
+		associatedObjectForCache
+	}) {
 		super();
-		this.hooks = {
-			resolver: new SyncWaterfallHook(["resolver"]),
-			factory: new SyncWaterfallHook(["factory"]),
-			beforeResolve: new AsyncSeriesWaterfallHook(["data"]),
-			afterResolve: new AsyncSeriesWaterfallHook(["data"]),
-			createModule: new SyncBailHook(["data"]),
-			module: new SyncWaterfallHook(["module", "data"]),
+		this.hooks = Object.freeze({
+			/** @type {AsyncSeriesBailHook<[ResolveData], Module | false | void>} */
+			resolve: new AsyncSeriesBailHook(["resolveData"]),
+			/** @type {HookMap>} */
+			resolveForScheme: new HookMap(
+				() => new AsyncSeriesBailHook(["resourceData", "resolveData"])
+			),
+			/**
+			 * @type {HookMap>}
+			 * @since 5.49.0
+			 */
+			resolveInScheme: new HookMap(
+				() => new AsyncSeriesBailHook(["resourceData", "resolveData"])
+			),
+			/** @type {AsyncSeriesBailHook<[ResolveData], Module | undefined>} */
+			factorize: new AsyncSeriesBailHook(["resolveData"]),
+			/** @type {AsyncSeriesBailHook<[ResolveData], false | void>} */
+			beforeResolve: new AsyncSeriesBailHook(["resolveData"]),
+			/** @type {AsyncSeriesBailHook<[ResolveData], false | void>} */
+			afterResolve: new AsyncSeriesBailHook(["resolveData"]),
+			/** @type {AsyncSeriesBailHook<[CreateData, ResolveData], Module | void>} */
+			createModule: new AsyncSeriesBailHook(["createData", "resolveData"]),
+			/** @type {SyncWaterfallHook<[Module, CreateData, ResolveData]>} */
+			module: new SyncWaterfallHook(["module", "createData", "resolveData"]),
+			/**
+			 * @type {HookMap>}
+			 * @since 5.110.0
+			 */
+			prepareModuleType: new HookMap(() => new AsyncSeriesHook([])),
+			/** @type {import("tapable").TypedHookMap>} */
 			createParser: new HookMap(() => new SyncBailHook(["parserOptions"])),
+			/** @type {import("tapable").TypedHookMap>} */
 			parser: new HookMap(() => new SyncHook(["parser", "parserOptions"])),
+			/** @type {import("tapable").TypedHookMap>} */
 			createGenerator: new HookMap(
 				() => new SyncBailHook(["generatorOptions"])
 			),
+			/** @type {import("tapable").TypedHookMap>} */
 			generator: new HookMap(
 				() => new SyncHook(["generator", "generatorOptions"])
+			),
+			/**
+			 * @type {HookMap>}
+			 * @since 5.81.0
+			 */
+			createModuleClass: new HookMap(
+				() => new SyncBailHook(["createData", "resolveData"])
 			)
-		};
-		this._pluginCompat.tap("NormalModuleFactory", options => {
-			switch (options.name) {
-				case "before-resolve":
-				case "after-resolve":
-					options.async = true;
-					break;
-				case "parser":
-					this.hooks.parser
-						.for("javascript/auto")
-						.tap(options.fn.name || "unnamed compat plugin", options.fn);
-					return true;
-			}
-			let match;
-			match = /^parser (.+)$/.exec(options.name);
-			if (match) {
-				this.hooks.parser
-					.for(match[1])
-					.tap(
-						options.fn.name || "unnamed compat plugin",
-						options.fn.bind(this)
-					);
-				return true;
-			}
-			match = /^create-parser (.+)$/.exec(options.name);
-			if (match) {
-				this.hooks.createParser
-					.for(match[1])
-					.tap(
-						options.fn.name || "unnamed compat plugin",
-						options.fn.bind(this)
-					);
-				return true;
-			}
 		});
+		/** @type {ResolverFactory} */
 		this.resolverFactory = resolverFactory;
-		this.ruleSet = new RuleSet(options.defaultRules.concat(options.rules));
-		this.cachePredicate =
-			typeof options.unsafeCache === "function"
-				? options.unsafeCache
-				: Boolean.bind(null, options.unsafeCache);
+		/** @type {RuleSet} */
+		this.ruleSet = ruleSetCompiler.compile([
+			{
+				rules: /** @type {RuleSetRules} */ (options.defaultRules)
+			},
+			{
+				rules: /** @type {RuleSetRules} */ (options.rules)
+			}
+		]);
+		/** @type {string} */
 		this.context = context || "";
-		this.parserCache = Object.create(null);
-		this.generatorCache = Object.create(null);
-		this.hooks.factory.tap("NormalModuleFactory", () => (result, callback) => {
-			let resolver = this.hooks.resolver.call(null);
+		/** @type {InputFileSystem} */
+		this.fs = fs;
+		this._globalParserOptions = options.parser;
+		this._globalGeneratorOptions = options.generator;
+		/** @type {Map>} */
+		this.parserCache = new Map();
+		/** @type {Map>} */
+		this.generatorCache = new Map();
+		// `true` once a type is prepared, otherwise the calls waiting on the
+		// in-flight `prepareModuleType` for it
+		/** @type {Map[]>} */
+		this._moduleTypePreparation = new Map();
+		/** @type {Set} */
+		this._restoredUnsafeCacheEntries = new Set();
+		/** @type {Map} */
+		this._loaderResolveCache = new Map();
 
-			// Ignored
-			if (!resolver) return callback();
+		/** @type {(resource: string) => import("./util/identifier").ParsedResource} */
+		const cacheParseResource = parseResource.bindCache(
+			associatedObjectForCache
+		);
+		const cachedParseResourceWithoutFragment =
+			parseResourceWithoutFragment.bindCache(associatedObjectForCache);
+		this._parseResourceWithoutFragment = cachedParseResourceWithoutFragment;
 
-			resolver(result, (err, data) => {
-				if (err) return callback(err);
+		this.hooks.factorize.tapAsync(
+			{
+				name: "NormalModuleFactory",
+				stage: 100
+			},
+			(resolveData, callback) => {
+				this.hooks.resolve.callAsync(resolveData, (err, result) => {
+					if (err) return callback(err);
 
-				// Ignored
-				if (!data) return callback();
+					// Ignored
+					if (result === false) return callback();
 
-				// direct module
-				if (typeof data.source === "function") return callback(null, data);
+					// direct module
+					if (result instanceof Module) return callback(null, result);
 
-				this.hooks.afterResolve.callAsync(data, (err, result) => {
-					if (err) return callback(err);
+					if (typeof result === "object") {
+						throw new Error(
+							`${deprecationChangedHookMessage(
+								"resolve",
+								this.hooks.resolve
+							)} Returning a Module object will result in this module used as result.`
+						);
+					}
 
-					// Ignored
-					if (!result) return callback();
+					this.hooks.afterResolve.callAsync(resolveData, (err, result) => {
+						if (err) return callback(err);
 
-					let createdModule = this.hooks.createModule.call(result);
-					if (!createdModule) {
-						if (!result.request) {
-							return callback(new Error("Empty dependency (no request)"));
+						if (typeof result === "object") {
+							throw new Error(
+								deprecationChangedHookMessage(
+									"afterResolve",
+									this.hooks.afterResolve
+								)
+							);
 						}
 
-						createdModule = new NormalModule(result);
-					}
+						// Ignored
+						if (result === false) return callback();
+
+						const createData =
+							/** @type {CreateData} */
+							(resolveData.createData);
 
-					createdModule = this.hooks.module.call(createdModule, result);
+						this.hooks.createModule.callAsync(
+							createData,
+							resolveData,
+							(err, createdModule) => {
+								if (!createdModule) {
+									if (!resolveData.request) {
+										return callback(new Error("Empty dependency (no request)"));
+									}
 
-					return callback(null, createdModule);
+									// TODO webpack 6 make it required and move javascript/wasm/asset properties to own module
+									createdModule = this.hooks.createModuleClass
+										.for(createData.settings.type)
+										.call(createData, resolveData);
+
+									if (!createdModule) {
+										createdModule = /** @type {Module} */ (
+											new NormalModule(createData)
+										);
+									}
+								}
+
+								createdModule = this.hooks.module.call(
+									createdModule,
+									createData,
+									resolveData
+								);
+
+								return callback(null, createdModule);
+							}
+						);
+					});
 				});
-			});
-		});
-		this.hooks.resolver.tap("NormalModuleFactory", () => (data, callback) => {
-			const contextInfo = data.contextInfo;
-			const context = data.context;
-			const request = data.request;
-
-			const loaderResolver = this.getResolver("loader");
-			const normalResolver = this.getResolver("normal", data.resolveOptions);
-
-			let matchResource = undefined;
-			let requestWithoutMatchResource = request;
-			const matchResourceMatch = MATCH_RESOURCE_REGEX.exec(request);
-			if (matchResourceMatch) {
-				matchResource = matchResourceMatch[1];
-				if (/^\.\.?\//.test(matchResource)) {
-					matchResource = path.join(context, matchResource);
-				}
-				requestWithoutMatchResource = request.substr(
-					matchResourceMatch[0].length
-				);
 			}
+		);
+		this.hooks.resolve.tapAsync(
+			{
+				name: "NormalModuleFactory",
+				stage: 100
+			},
+			(data, callback) => {
+				const {
+					contextInfo,
+					context,
+					dependencies,
+					dependencyType,
+					request,
+					phase,
+					attributes,
+					resolveOptions,
+					fileDependencies,
+					missingDependencies,
+					contextDependencies
+				} = data;
+				const loaderResolver = this.getResolver("loader");
 
-			const noPreAutoLoaders = requestWithoutMatchResource.startsWith("-!");
-			const noAutoLoaders =
-				noPreAutoLoaders || requestWithoutMatchResource.startsWith("!");
-			const noPrePostAutoLoaders = requestWithoutMatchResource.startsWith("!!");
-			let elements = requestWithoutMatchResource
-				.replace(/^-?!+/, "")
-				.replace(/!!+/g, "!")
-				.split("!");
-			let resource = elements.pop();
-			elements = elements.map(identToLoaderRequest);
-
-			asyncLib.parallel(
-				[
-					callback =>
-						this.resolveRequestArray(
-							contextInfo,
-							context,
-							elements,
-							loaderResolver,
-							callback
-						),
-					callback => {
-						if (resource === "" || resource[0] === "?") {
-							return callback(null, {
-								resource
-							});
-						}
+				/** @type {ResourceData | undefined} */
+				let matchResourceData;
+				/** @type {string} */
+				let unresolvedResource;
+				/** @type {ParsedLoaderRequest[]} */
+				let elements;
+				let noPreAutoLoaders = false;
+				let noAutoLoaders = false;
+				let noPrePostAutoLoaders = false;
 
-						normalResolver.resolve(
-							contextInfo,
-							context,
-							resource,
-							{},
-							(err, resource, resourceResolveData) => {
-								if (err) return callback(err);
-								callback(null, {
-									resourceResolveData,
-									resource
-								});
+				const contextScheme = getScheme(context);
+				/** @type {string | undefined} */
+				let scheme = getScheme(request);
+
+				if (!scheme) {
+					/** @type {string} */
+					let requestWithoutMatchResource = request;
+					const matchResourceMatch = MATCH_RESOURCE_REGEX.exec(request);
+					if (matchResourceMatch) {
+						let matchResource = matchResourceMatch[1];
+						// Check if matchResource starts with ./ or ../
+						if (matchResource.charCodeAt(0) === 46) {
+							// 46 is "."
+							const secondChar = matchResource.charCodeAt(1);
+							if (
+								secondChar === 47 || // 47 is "/"
+								(secondChar === 46 && matchResource.charCodeAt(2) === 47) // "../"
+							) {
+								// Resolve relative path against context
+								matchResource = join(this.fs, context, matchResource);
 							}
+						}
+
+						matchResourceData = {
+							...cacheParseResource(matchResource),
+							resource: matchResource
+						};
+						requestWithoutMatchResource = request.slice(
+							matchResourceMatch[0].length
 						);
 					}
-				],
-				(err, results) => {
+
+					scheme = getScheme(requestWithoutMatchResource);
+
+					if (!scheme && !contextScheme) {
+						const firstChar = requestWithoutMatchResource.charCodeAt(0);
+						const secondChar = requestWithoutMatchResource.charCodeAt(1);
+						noPreAutoLoaders = firstChar === 45 && secondChar === 33; // startsWith "-!"
+						noAutoLoaders = noPreAutoLoaders || firstChar === 33; // startsWith "!"
+						noPrePostAutoLoaders = firstChar === 33 && secondChar === 33; // startsWith "!!";
+						const rawElements = requestWithoutMatchResource
+							.slice(
+								noPreAutoLoaders || noPrePostAutoLoaders
+									? 2
+									: noAutoLoaders
+										? 1
+										: 0
+							)
+							.split(/!+/);
+						unresolvedResource = /** @type {string} */ (rawElements.pop());
+						elements = rawElements.map((el) => {
+							const { path, query } = cachedParseResourceWithoutFragment(el);
+							return {
+								loader: path,
+								options: query ? query.slice(1) : undefined
+							};
+						});
+						scheme = getScheme(unresolvedResource);
+					} else {
+						unresolvedResource = requestWithoutMatchResource;
+						elements = EMPTY_ELEMENTS;
+					}
+				} else {
+					unresolvedResource = request;
+					elements = EMPTY_ELEMENTS;
+				}
+
+				/** @type {ResolveContext} */
+				const resolveContext = {
+					fileDependencies,
+					missingDependencies,
+					contextDependencies
+				};
+
+				/** @type {ResourceDataWithData} */
+				let resourceData;
+
+				/** @type {undefined | LoaderItem[]} */
+				let loaders;
+
+				const continueCallback = needCalls(2, (err) => {
 					if (err) return callback(err);
-					let loaders = results[0];
-					const resourceResolveData = results[1].resourceResolveData;
-					resource = results[1].resource;
 
 					// translate option idents
 					try {
-						for (const item of loaders) {
+						for (const item of /** @type {LoaderItem[]} */ (loaders)) {
 							if (typeof item.options === "string" && item.options[0] === "?") {
-								const ident = item.options.substr(1);
-								item.options = this.ruleSet.findOptionsByIdent(ident);
+								const ident = item.options.slice(1);
+								if (ident === "[[missing ident]]") {
+									throw new Error(
+										"No ident is provided by referenced loader. " +
+											"When using a function for Rule.use in config you need to " +
+											"provide an 'ident' property for referenced loader options."
+									);
+								}
+								item.options = this.ruleSet.references.get(ident);
+								if (item.options === undefined) {
+									throw new Error(
+										"Invalid ident is provided by referenced loader"
+									);
+								}
 								item.ident = ident;
 							}
 						}
-					} catch (e) {
-						return callback(e);
+					} catch (identErr) {
+						return callback(/** @type {Error} */ (identErr));
 					}
 
-					if (resource === false) {
+					if (!resourceData) {
 						// ignored
-						return callback(
-							null,
-							new RawModule(
-								"/* (ignored) */",
-								`ignored ${context} ${request}`,
-								`${request} (ignored)`
-							)
-						);
+						return callback(null, dependencies[0].createIgnoredModule(context));
 					}
 
 					const userRequest =
-						(matchResource !== undefined ? `${matchResource}!=!` : "") +
-						loaders
-							.map(loaderToIdent)
-							.concat([resource])
-							.join("!");
-
-					let resourcePath =
-						matchResource !== undefined ? matchResource : resource;
-					let resourceQuery = "";
-					const queryIndex = resourcePath.indexOf("?");
-					if (queryIndex >= 0) {
-						resourceQuery = resourcePath.substr(queryIndex);
-						resourcePath = resourcePath.substr(0, queryIndex);
-					}
+						(matchResourceData !== undefined
+							? `${matchResourceData.resource}!=!`
+							: "") +
+						stringifyLoadersAndResource(
+							/** @type {LoaderItem[]} */ (loaders),
+							resourceData.resource
+						);
 
-					const result = this.ruleSet.exec({
-						resource: resourcePath,
-						realResource:
-							matchResource !== undefined
-								? resource.replace(/\?.*/, "")
-								: resourcePath,
-						resourceQuery,
-						issuer: contextInfo.issuer,
-						compiler: contextInfo.compiler
-					});
+					/** @type {ModuleSettings} */
 					const settings = {};
+					/** @type {LoaderItem[]} */
 					const useLoadersPost = [];
+					/** @type {LoaderItem[]} */
 					const useLoaders = [];
+					/** @type {LoaderItem[]} */
 					const useLoadersPre = [];
-					for (const r of result) {
-						if (r.type === "use") {
-							if (r.enforce === "post" && !noPrePostAutoLoaders) {
-								useLoadersPost.push(r.value);
-							} else if (
-								r.enforce === "pre" &&
-								!noPreAutoLoaders &&
-								!noPrePostAutoLoaders
-							) {
-								useLoadersPre.push(r.value);
+
+					// handle .webpack[] suffix
+					/** @type {string} */
+					let resource;
+					/** @type {RegExpExecArray | null} */
+					let match;
+					if (
+						matchResourceData &&
+						typeof (resource = matchResourceData.resource) === "string" &&
+						(match = /\.webpack\[([^\]]+)\]$/.exec(resource))
+					) {
+						settings.type = match[1];
+						matchResourceData.resource = matchResourceData.resource.slice(
+							0,
+							-settings.type.length - 10
+						);
+					} else {
+						settings.type = JAVASCRIPT_MODULE_TYPE_AUTO;
+						const resourceDataForRules = matchResourceData || resourceData;
+
+						const result = this.ruleSet.exec({
+							resource: resourceDataForRules.path,
+							realResource: resourceData.path,
+							resourceQuery: resourceDataForRules.query,
+							resourceFragment: resourceDataForRules.fragment,
+							scheme,
+							phase,
+							attributes,
+							mimetype: matchResourceData
+								? ""
+								: resourceData.data.mimetype || "",
+							dependency: dependencyType,
+							descriptionData: matchResourceData
+								? undefined
+								: resourceData.data.descriptionFileData,
+							descriptionRelativePath: matchResourceData
+								? undefined
+								: resourceData.data.relativePath,
+							issuer: contextInfo.issuer,
+							compiler: contextInfo.compiler,
+							issuerLayer: contextInfo.issuerLayer || ""
+						});
+						for (const r of result) {
+							// https://github.com/webpack/webpack/issues/16466
+							// if a request exists PrePostAutoLoaders, should disable modifying Rule.type
+							if (r.type === "type" && noPrePostAutoLoaders) {
+								continue;
+							}
+							if (r.type === "use") {
+								if (!noAutoLoaders && !noPrePostAutoLoaders) {
+									useLoaders.push(r.value);
+								}
+							} else if (r.type === "use-post") {
+								if (!noPrePostAutoLoaders) {
+									useLoadersPost.push(r.value);
+								}
+							} else if (r.type === "use-pre") {
+								if (!noPreAutoLoaders && !noPrePostAutoLoaders) {
+									useLoadersPre.push(r.value);
+								}
 							} else if (
-								!r.enforce &&
-								!noAutoLoaders &&
-								!noPrePostAutoLoaders
+								typeof r.value === "object" &&
+								r.value !== null &&
+								typeof settings[
+									/** @type {keyof ModuleSettings} */
+									(r.type)
+								] === "object" &&
+								settings[/** @type {keyof ModuleSettings} */ (r.type)] !== null
 							) {
-								useLoaders.push(r.value);
+								const type = /** @type {keyof ModuleSettings} */ (r.type);
+								settings[type] = cachedCleverMerge(settings[type], r.value);
+							} else {
+								const type = /** @type {keyof ModuleSettings} */ (r.type);
+								settings[type] = r.value;
 							}
-						} else if (
-							typeof r.value === "object" &&
-							r.value !== null &&
-							typeof settings[r.type] === "object" &&
-							settings[r.type] !== null
-						) {
-							settings[r.type] = cachedMerge(settings[r.type], r.value);
-						} else {
-							settings[r.type] = r.value;
 						}
 					}
-					asyncLib.parallel(
-						[
-							this.resolveRequestArray.bind(
-								this,
-								contextInfo,
-								this.context,
-								useLoadersPost,
-								loaderResolver
-							),
-							this.resolveRequestArray.bind(
-								this,
-								contextInfo,
-								this.context,
-								useLoaders,
-								loaderResolver
-							),
-							this.resolveRequestArray.bind(
-								this,
-								contextInfo,
-								this.context,
-								useLoadersPre,
-								loaderResolver
-							)
-						],
-						(err, results) => {
-							if (err) return callback(err);
-							loaders = results[0].concat(loaders, results[1], results[2]);
-							process.nextTick(() => {
-								const type = settings.type;
-								const resolveOptions = settings.resolve;
-								callback(null, {
-									context: context,
-									request: loaders
-										.map(loaderToIdent)
-										.concat([resource])
-										.join("!"),
-									dependencies: data.dependencies,
+
+					/** @type {undefined | LoaderItem[]} */
+					let postLoaders;
+					/** @type {undefined | LoaderItem[]} */
+					let normalLoaders;
+					/** @type {undefined | LoaderItem[]} */
+					let preLoaders;
+
+					const continueCallback = needCalls(3, (err) => {
+						if (err) {
+							return callback(err);
+						}
+						const allLoaders = /** @type {LoaderItem[]} */ (postLoaders);
+						if (matchResourceData === undefined) {
+							for (const loader of /** @type {LoaderItem[]} */ (loaders)) {
+								allLoaders.push(loader);
+							}
+							for (const loader of /** @type {LoaderItem[]} */ (
+								normalLoaders
+							)) {
+								allLoaders.push(loader);
+							}
+						} else {
+							for (const loader of /** @type {LoaderItem[]} */ (
+								normalLoaders
+							)) {
+								allLoaders.push(loader);
+							}
+							for (const loader of /** @type {LoaderItem[]} */ (loaders)) {
+								allLoaders.push(loader);
+							}
+						}
+						for (const loader of /** @type {LoaderItem[]} */ (preLoaders)) {
+							allLoaders.push(loader);
+						}
+						const type = /** @type {NormalModuleTypes} */ (settings.type);
+						const resolveOptions = settings.resolve;
+						const layer = settings.layer;
+
+						const applyCreateData = () => {
+							try {
+								Object.assign(data.createData, {
+									layer:
+										layer === undefined
+											? contextInfo.issuerLayer || null
+											: layer,
+									request: stringifyLoadersAndResource(
+										allLoaders,
+										resourceData.resource
+									),
 									userRequest,
 									rawRequest: request,
-									loaders,
-									resource,
-									matchResource,
-									resourceResolveData,
+									loaders: allLoaders,
+									resource: resourceData.resource,
+									context:
+										resourceData.context || getContext(resourceData.resource),
+									matchResource: matchResourceData
+										? matchResourceData.resource
+										: undefined,
+									resourceResolveData: resourceData.data,
 									settings,
 									type,
 									parser: this.getParser(type, settings.parser),
+									parserOptions: settings.parser,
 									generator: this.getGenerator(type, settings.generator),
-									resolveOptions
+									generatorOptions: settings.generator,
+									resolveOptions,
+									extractSourceMap: settings.extractSourceMap || false
 								});
-							});
+							} catch (createDataErr) {
+								return callback(/** @type {Error} */ (createDataErr));
+							}
+							callback();
+						};
+
+						this._prepareModuleType(type, (err) => {
+							if (err) return callback(err);
+							applyCreateData();
+						});
+					});
+					this.resolveRequestArray(
+						contextInfo,
+						this.context,
+						useLoadersPost,
+						loaderResolver,
+						resolveContext,
+						(err, result) => {
+							postLoaders = result;
+							continueCallback(err);
+						}
+					);
+					this.resolveRequestArray(
+						contextInfo,
+						this.context,
+						useLoaders,
+						loaderResolver,
+						resolveContext,
+						(err, result) => {
+							normalLoaders = result;
+							continueCallback(err);
+						}
+					);
+					this.resolveRequestArray(
+						contextInfo,
+						this.context,
+						useLoadersPre,
+						loaderResolver,
+						resolveContext,
+						(err, result) => {
+							preLoaders = result;
+							continueCallback(err);
 						}
 					);
+				});
+
+				this.resolveRequestArray(
+					contextInfo,
+					contextScheme ? this.context : context,
+					/** @type {LoaderItem[]} */ (elements),
+					loaderResolver,
+					resolveContext,
+					(err, result) => {
+						if (err) return continueCallback(err);
+						loaders = result;
+						continueCallback();
+					}
+				);
+
+				/**
+				 * Processes the provided string.
+				 * @param {string} context context
+				 */
+				const defaultResolve = (context) => {
+					if (/^(?:$|\?)/.test(unresolvedResource)) {
+						resourceData = {
+							...cacheParseResource(unresolvedResource),
+							resource: unresolvedResource,
+							data: {}
+						};
+						continueCallback();
+					}
+
+					// resource without scheme and with path
+					else {
+						const normalResolver = this.getResolver(
+							"normal",
+							dependencyType
+								? cachedSetProperty(
+										resolveOptions || EMPTY_RESOLVE_OPTIONS,
+										"dependencyType",
+										dependencyType
+									)
+								: resolveOptions
+						);
+						this.resolveResource(
+							contextInfo,
+							context,
+							escapeHashInPathRequest(unresolvedResource),
+							normalResolver,
+							resolveContext,
+							(err, _resolvedResource, resolvedResourceResolveData) => {
+								if (err) return continueCallback(err);
+								if (_resolvedResource !== false) {
+									const resolvedResource =
+										/** @type {string} */
+										(_resolvedResource);
+									resourceData = {
+										...cacheParseResource(resolvedResource),
+										resource: resolvedResource,
+										data:
+											/** @type {ResolveRequest} */
+											(resolvedResourceResolveData)
+									};
+								}
+								continueCallback();
+							}
+						);
+					}
+				};
+
+				// resource with scheme
+				if (scheme) {
+					resourceData = {
+						resource: unresolvedResource,
+						data: {},
+						path: undefined,
+						query: undefined,
+						fragment: undefined,
+						context: undefined
+					};
+					this.hooks.resolveForScheme
+						.for(scheme)
+						.callAsync(resourceData, data, (err) => {
+							if (err) return continueCallback(err);
+							continueCallback();
+						});
 				}
-			);
-		});
+
+				// resource within scheme
+				else if (contextScheme) {
+					resourceData = {
+						resource: unresolvedResource,
+						data: {},
+						path: undefined,
+						query: undefined,
+						fragment: undefined,
+						context: undefined
+					};
+					this.hooks.resolveInScheme
+						.for(contextScheme)
+						.callAsync(resourceData, data, (err, handled) => {
+							if (err) return continueCallback(err);
+							if (!handled) return defaultResolve(this.context);
+							continueCallback();
+						});
+				}
+
+				// resource without scheme and without path
+				else {
+					defaultResolve(context);
+				}
+			}
+		);
+	}
+
+	cleanupForCache() {
+		for (const module of this._restoredUnsafeCacheEntries) {
+			ChunkGraph.clearChunkGraphForModule(module);
+			ModuleGraph.clearModuleGraphForModule(module);
+			module.cleanupForCache();
+		}
 	}
 
+	/**
+	 * Processes the provided data.
+	 * @param {ModuleFactoryCreateData} data data object
+	 * @param {ModuleFactoryCallback} callback callback
+	 * @returns {void}
+	 */
 	create(data, callback) {
-		const dependencies = data.dependencies;
-		const cacheEntry = dependencyCache.get(dependencies[0]);
-		if (cacheEntry) return callback(null, cacheEntry);
+		const dependencies = /** @type {ModuleDependency[]} */ (data.dependencies);
 		const context = data.context || this.context;
 		const resolveOptions = data.resolveOptions || EMPTY_RESOLVE_OPTIONS;
-		const request = dependencies[0].request;
-		const contextInfo = data.contextInfo || {};
-		this.hooks.beforeResolve.callAsync(
-			{
-				contextInfo,
-				resolveOptions,
-				context,
-				request,
-				dependencies
-			},
-			(err, result) => {
-				if (err) return callback(err);
+		const dependency = dependencies[0];
+		const request = dependency.request;
+		const attributes =
+			/** @type {ModuleDependency & { attributes: ImportAttributes }} */
+			(dependency).attributes;
+		const phase =
+			typeof (
+				/** @type {ModuleDependency & { phase?: ImportPhaseType }} */
+				(dependency).phase
+			) === "number"
+				? ImportPhaseUtils.stringify(
+						/** @type {ModuleDependency & { phase?: ImportPhaseType }} */
+						(dependency).phase
+					)
+				: "evaluation";
+		const dependencyType = dependency.category || "";
+		const contextInfo = data.contextInfo;
+		/** @type {FileSystemDependencies} */
+		const fileDependencies = new LazySet();
+		/** @type {FileSystemDependencies} */
+		const missingDependencies = new LazySet();
+		/** @type {FileSystemDependencies} */
+		const contextDependencies = new LazySet();
+		/** @type {ResolveData} */
+		const resolveData = {
+			contextInfo,
+			resolveOptions,
+			context,
+			request,
+			phase,
+			attributes,
+			dependencies,
+			dependencyType,
+			fileDependencies,
+			missingDependencies,
+			contextDependencies,
+			createData: {},
+			cacheable: true
+		};
+		this.hooks.beforeResolve.callAsync(resolveData, (err, result) => {
+			if (err) {
+				return callback(err, {
+					fileDependencies,
+					missingDependencies,
+					contextDependencies,
+					cacheable: false
+				});
+			}
 
-				// Ignored
-				if (!result) return callback();
+			// Ignored
+			if (result === false) {
+				/** @type {ModuleFactoryResult} * */
+				const factoryResult = {
+					fileDependencies,
+					missingDependencies,
+					contextDependencies,
+					cacheable: resolveData.cacheable
+				};
 
-				const factory = this.hooks.factory.call(null);
+				if (resolveData.ignoredModule) {
+					factoryResult.module = resolveData.ignoredModule;
+				}
 
-				// Ignored
-				if (!factory) return callback();
+				return callback(null, factoryResult);
+			}
 
-				factory(result, (err, module) => {
-					if (err) return callback(err);
+			if (typeof result === "object") {
+				throw new Error(
+					deprecationChangedHookMessage(
+						"beforeResolve",
+						this.hooks.beforeResolve
+					)
+				);
+			}
+
+			this.hooks.factorize.callAsync(resolveData, (err, module) => {
+				if (err) {
+					return callback(err, {
+						fileDependencies,
+						missingDependencies,
+						contextDependencies,
+						cacheable: false
+					});
+				}
+
+				/** @type {ModuleFactoryResult} * */
+				const factoryResult = {
+					module,
+					fileDependencies,
+					missingDependencies,
+					contextDependencies,
+					cacheable: resolveData.cacheable
+				};
+
+				callback(null, factoryResult);
+			});
+		});
+	}
+
+	/**
+	 * Processes the provided context info.
+	 * @param {ModuleFactoryCreateDataContextInfo} contextInfo context info
+	 * @param {string} context context
+	 * @param {string} unresolvedResource unresolved resource
+	 * @param {ResolverWithOptions} resolver resolver
+	 * @param {ResolveContext} resolveContext resolver context
+	 * @param {(err: null | Error, res?: string | false, req?: ResolveRequest) => void} callback callback
+	 */
+	resolveResource(
+		contextInfo,
+		context,
+		unresolvedResource,
+		resolver,
+		resolveContext,
+		callback
+	) {
+		resolver.resolve(
+			contextInfo,
+			context,
+			unresolvedResource,
+			resolveContext,
+			(err, resolvedResource, resolvedResourceResolveData) => {
+				if (err) {
+					return this._resolveResourceErrorHints(
+						err,
+						contextInfo,
+						context,
+						unresolvedResource,
+						resolver,
+						resolveContext,
+						(err2, hints) => {
+							if (err2) {
+								err.message += `
+A fatal error happened during resolving additional hints for this error: ${err2.message}`;
+								err.stack += `
+
+A fatal error happened during resolving additional hints for this error:
+${err2.stack}`;
+								return callback(err);
+							}
+							if (hints && hints.length > 0) {
+								err.message += `
+${hints.join("\n\n")}`;
+							}
+
+							// Check if the extension is missing a leading dot (e.g. "js" instead of ".js")
+							let appendResolveExtensionsHint = false;
+							const specifiedExtensions = [...resolver.options.extensions];
+							const expectedExtensions = specifiedExtensions.map(
+								(extension) => {
+									if (LEADING_DOT_EXTENSION_REGEX.test(extension)) {
+										appendResolveExtensionsHint = true;
+										return `.${extension}`;
+									}
+									return extension;
+								}
+							);
+							if (appendResolveExtensionsHint) {
+								err.message += `\nDid you miss the leading dot in 'resolve.extensions'? Did you mean '${JSON.stringify(
+									expectedExtensions
+								)}' instead of '${JSON.stringify(specifiedExtensions)}'?`;
+							}
+
+							callback(err);
+						}
+					);
+				}
+				callback(err, resolvedResource, resolvedResourceResolveData);
+			}
+		);
+	}
+
+	/**
+	 * Resolve resource error hints.
+	 * @param {Error} error error
+	 * @param {ModuleFactoryCreateDataContextInfo} contextInfo context info
+	 * @param {string} context context
+	 * @param {string} unresolvedResource unresolved resource
+	 * @param {ResolverWithOptions} resolver resolver
+	 * @param {ResolveContext} resolveContext resolver context
+	 * @param {Callback} callback callback
+	 * @private
+	 */
+	_resolveResourceErrorHints(
+		error,
+		contextInfo,
+		context,
+		unresolvedResource,
+		resolver,
+		resolveContext,
+		callback
+	) {
+		/**
+		 * Names the entry of the request's directory it comes closest to, for
+		 * requests that failed over a typo or the casing of a file name. A module
+		 * request is looked up in the module directories, so a wrongly cased
+		 * package name is named too, and a directory the request spells out is
+		 * itself matched by casing.
+		 * @param {(hint?: string) => void} callback callback
+		 * @returns {void}
+		 */
+		const suggestClosestEntry = (callback) => {
+			const {
+				path: requestPath,
+				query,
+				fragment
+			} = parseResource(unresolvedResource);
 
-					if (module && this.cachePredicate(module)) {
-						for (const d of dependencies) {
-							dependencyCache.set(d, module);
+			/**
+			 * Absolute paths the resolver would have tried for a request, in the
+			 * order it tries them.
+			 * @param {string} request a request
+			 * @returns {string[]} absolute paths
+			 */
+			const lookupPaths = (request) => {
+				if (ABSOLUTE_PATH_REGEXP.test(request)) {
+					const paths = [request];
+					const firstChar = request.charCodeAt(0);
+					// A request rooted at '/' is looked up under 'resolve.roots' as well
+					if (firstChar === 47 /* "/" */ || firstChar === 92 /* "\\" */) {
+						for (const root of resolver.options.roots) {
+							paths.push(join(this.fs, root, request.slice(1)));
 						}
 					}
+					return paths;
+				}
+				if (RELATIVE_REQUEST_REGEXP.test(request)) {
+					return [join(this.fs, context, request)];
+				}
+				/** @type {string[]} */
+				const paths = [];
+				// 'preferRelative' lets a module request resolve next to its origin too
+				if (resolver.options.preferRelative) {
+					paths.push(join(this.fs, context, request));
+				}
+				for (const directory of moduleLookupDirectories(
+					this.fs,
+					context,
+					resolver.options.modules
+				)) {
+					paths.push(join(this.fs, directory, request));
+				}
+				return paths;
+			};
+
+			const absoluteRequests = lookupPaths(requestPath);
+			// Each path costs a readdir, a casing walk and a resolve, so a large
+			// alias map must not turn one failed request into unbounded work
+			for (const aliased of aliasedRequests(
+				requestPath,
+				resolver.options.alias
+			)) {
+				if (absoluteRequests.length >= MAX_SUGGESTION_CANDIDATES) break;
+				for (const path of lookupPaths(aliased)) absoluteRequests.push(path);
+			}
+			if (absoluteRequests.length > MAX_SUGGESTION_CANDIDATES) {
+				absoluteRequests.length = MAX_SUGGESTION_CANDIDATES;
+			}
+
+			/**
+			 * @param {string} prefix the request without its last segment, with the real casing
+			 * @param {string} requestedName the last segment of the request
+			 * @param {string[]} entries entries of the directory it was looked up in
+			 * @param {boolean} directoryCorrected whether the casing of `prefix` had to be corrected
+			 * @param {() => void} giveUp continues the search when nothing is close enough
+			 * @returns {void}
+			 */
+			const suggest = (
+				prefix,
+				requestedName,
+				entries,
+				directoryCorrected,
+				giveUp
+			) => {
+				const closest =
+					directoryCorrected && entries.includes(requestedName)
+						? { entry: requestedName, distance: 0 }
+						: findClosestEntry(
+								requestedName,
+								entries,
+								resolver.options.extensions
+							);
+				if (!closest) return giveUp();
+				const existing = prefix + closest.entry;
+				// The query and fragment select what to do with the file, so they
+				// belong in a request the user is meant to copy — but not in a
+				// sentence about what is on disk
+				const suggestion = existing + query + fragment;
+				const characters =
+					closest.distance === 1
+						? "1 character"
+						: `${closest.distance} characters`;
+				// A corrected directory is not part of `entry`, so the whole path has
+				// to be named for the sentence to account for every difference
+				const difference =
+					closest.distance === 0
+						? directoryCorrected
+							? `'${existing}' exists and differs from the request only in casing. Case-sensitive filesystems (most Linux ones) fail to resolve it, even when the same build succeeds on a case-insensitive one.`
+							: `'${closest.entry}' exists in that directory and differs from the request only in casing. Case-sensitive filesystems (most Linux ones) fail to resolve it, even when the same build succeeds on a case-insensitive one.`
+						: directoryCorrected
+							? `'${prefix}' differs from the request only in casing, and '${closest.entry}' is the closest name in it. It differs from the request by ${characters}, ignoring case and file extension.`
+							: `'${closest.entry}' is the closest name in that directory. It differs from the request by ${characters}, ignoring case and file extension.`;
+				// Offering a request that fails too would only be a second error, so the
+				// resolver has the last word. That keeps the hint aligned with every
+				// resolve option, not just the ones consulted to find the entry.
+				resolver.resolve(
+					contextInfo,
+					context,
+					suggestion,
+					resolveContext,
+					(suggestionError, resolved) => {
+						if (suggestionError || !resolved) return giveUp();
+						callback(`Did you mean '${suggestion}'?
+${difference}`);
+					}
+				);
+			};
 
-					callback(null, module);
+			/**
+			 * @param {number} index index into `absoluteRequests`
+			 * @returns {void}
+			 */
+			const next = (index) => {
+				if (index >= absoluteRequests.length) return callback();
+				const absoluteRequest = absoluteRequests[index];
+				const directory = dirname(this.fs, absoluteRequest);
+				const requestedName = relative(this.fs, directory, absoluteRequest);
+				// A request ending in '.' or '..' names a directory, not an entry in one.
+				if (!requestPath.endsWith(requestedName)) return next(index + 1);
+				const prefix = requestPath.slice(
+					0,
+					requestPath.length - requestedName.length
+				);
+				this.fs.readdir(directory, (err, entries) => {
+					if (!err && entries) {
+						return suggest(
+							prefix,
+							requestedName,
+							/** @type {string[]} */ (entries),
+							false,
+							() => next(index + 1)
+						);
+					}
+					// The directory is missing, so only a directory the request spells
+					// out itself can be the wrongly cased one
+					if (!namesDirectories(prefix)) return next(index + 1);
+					findCaseMismatch(this.fs, directory, (mismatch) => {
+						if (!mismatch) return next(index + 1);
+						const fixedPrefix = applyCaseCorrections(
+							prefix,
+							mismatch.corrections
+						);
+						if (fixedPrefix === undefined) return next(index + 1);
+						this.fs.readdir(mismatch.path, (realError, realEntries) => {
+							if (realError || !realEntries) return next(index + 1);
+							suggest(
+								fixedPrefix,
+								requestedName,
+								/** @type {string[]} */ (realEntries),
+								true,
+								() => next(index + 1)
+							);
+						});
+					});
 				});
+			};
+			next(0);
+		};
+
+		asyncLib.parallel(
+			[
+				(callback) => {
+					if (!resolver.options.fullySpecified) return callback();
+					resolver
+						.withOptions({
+							fullySpecified: false
+						})
+						.resolve(
+							contextInfo,
+							context,
+							unresolvedResource,
+							resolveContext,
+							(err, resolvedResource) => {
+								if (!err && resolvedResource) {
+									const resource = parseResource(resolvedResource).path.replace(
+										/^.*[\\/]/,
+										""
+									);
+									return callback(
+										null,
+										`Did you mean '${resource}'?
+BREAKING CHANGE: The request '${unresolvedResource}' failed to resolve only because it was resolved as fully specified
+(probably because the origin is strict EcmaScript Module, e. g. a module with javascript mimetype, a '*.mjs' file, or a '*.js' file where the package.json contains '"type": "module"').
+The extension in the request is mandatory for it to be fully specified.
+Add the extension to the request.`
+									);
+								}
+								callback();
+							}
+						);
+				},
+				(callback) => {
+					if (!resolver.options.enforceExtension) return callback();
+					resolver
+						.withOptions({
+							enforceExtension: false,
+							extensions: []
+						})
+						.resolve(
+							contextInfo,
+							context,
+							unresolvedResource,
+							resolveContext,
+							(err, resolvedResource) => {
+								if (!err && resolvedResource) {
+									let hint = "";
+									const match = /\.[^.]+(?:\?|$)/.exec(unresolvedResource);
+									if (match) {
+										const fixedRequest = unresolvedResource.replace(
+											/(\.[^.]+)(\?|$)/,
+											"$2"
+										);
+										hint = resolver.options.extensions.has(match[1])
+											? `Did you mean '${fixedRequest}'?`
+											: `Did you mean '${fixedRequest}'? Also note that '${match[1]}' is not in 'resolve.extensions' yet and need to be added for this to work?`;
+									} else {
+										hint =
+											"Did you mean to omit the extension or to remove 'resolve.enforceExtension'?";
+									}
+									return callback(
+										null,
+										`The request '${unresolvedResource}' failed to resolve only because 'resolve.enforceExtension' was specified.
+${hint}
+Including the extension in the request is no longer possible. Did you mean to enforce including the extension in requests with 'resolve.extensions: []' instead?`
+									);
+								}
+								callback();
+							}
+						);
+				},
+				(callback) => {
+					if (
+						/^\.\.?\//.test(unresolvedResource) ||
+						resolver.options.preferRelative
+					) {
+						return callback();
+					}
+					resolver.resolve(
+						contextInfo,
+						context,
+						`./${unresolvedResource}`,
+						resolveContext,
+						(err, resolvedResource) => {
+							if (err || !resolvedResource) return callback();
+							const moduleDirectories = resolver.options.modules
+								.map((m) => (Array.isArray(m) ? m.join(", ") : m))
+								.join(", ");
+							callback(
+								null,
+								`Did you mean './${unresolvedResource}'?
+Requests that should resolve in the current directory need to start with './'.
+Requests that start with a name are treated as module requests and resolve within module directories (${moduleDirectories}).
+If changing the source code is not an option there is also a resolve options called 'preferRelative' which tries to resolve these kind of requests in the current directory too.`
+							);
+						}
+					);
+				}
+			],
+			(err, hints) => {
+				if (err) return callback(err);
+				const resolveHints = /** @type {string[]} */ (hints).filter(Boolean);
+				// The hints above explain why an existing file was rejected and name it
+				// themselves, so only look for a similar name when none of them applied.
+				if (resolveHints.length > 0) return callback(null, resolveHints);
+				suggestClosestEntry((hint) => callback(null, hint ? [hint] : []));
 			}
 		);
 	}
 
-	resolveRequestArray(contextInfo, context, array, resolver, callback) {
-		if (array.length === 0) return callback(null, []);
+	/**
+	 * Resolves request array.
+	 * @param {ModuleFactoryCreateDataContextInfo} contextInfo context info
+	 * @param {string} context context
+	 * @param {LoaderItem[]} array array
+	 * @param {ResolverWithOptions} resolver resolver
+	 * @param {ResolveContext} resolveContext resolve context
+	 * @param {Callback} callback callback
+	 * @returns {void} result
+	 */
+	resolveRequestArray(
+		contextInfo,
+		context,
+		array,
+		resolver,
+		resolveContext,
+		callback
+	) {
+		// LoaderItem
+		if (array.length === 0) return callback(null, array);
 		asyncLib.map(
 			array,
+			/**
+			 * Handles the callback logic for this hook.
+			 * @param {LoaderItem} item item
+			 * @param {Callback} callback callback
+			 * @returns {void}
+			 */
 			(item, callback) => {
+				// Loaders resolve identically for a given (context, request), so memoize
+				// the result and the resolve's dependencies to skip repeated resolves.
+				const cacheKey = `${context}\n${item.loader}`;
+				const cached = this._loaderResolveCache.get(cacheKey);
+				if (cached !== undefined) {
+					replayResolveDependencies(resolveContext, cached);
+					return callback(null, {
+						loader: cached.loader,
+						type: cached.type,
+						options:
+							item.options === undefined
+								? cached.query
+									? cached.query.slice(1)
+									: undefined
+								: item.options,
+						ident: item.options === undefined ? undefined : item.ident
+					});
+				}
+				// Capture this resolve's dependencies in dedicated sets so a future hit
+				// can replay them; also feed them back into the caller's resolveContext.
+				const captureContext = {
+					...resolveContext,
+					fileDependencies: new LazySet(),
+					contextDependencies: new LazySet(),
+					missingDependencies: new LazySet()
+				};
 				resolver.resolve(
 					contextInfo,
 					context,
 					item.loader,
-					{},
-					(err, result) => {
+					captureContext,
+					(err, result, resolveRequest) => {
+						// Merge the captured dependencies back so a failed or successful
+						// resolve records them for watch invalidation just like before.
+						replayResolveDependencies(resolveContext, captureContext);
 						if (
 							err &&
 							/^[^/]*$/.test(item.loader) &&
-							!/-loader$/.test(item.loader)
+							!item.loader.endsWith("-loader")
 						) {
 							return resolver.resolve(
 								contextInfo,
 								context,
-								item.loader + "-loader",
-								{},
-								err2 => {
+								`${item.loader}-loader`,
+								resolveContext,
+								(err2) => {
 									if (!err2) {
 										err.message =
-											err.message +
-											"\n" +
+											`${err.message}\n` +
 											"BREAKING CHANGE: It's no longer allowed to omit the '-loader' suffix when using loaders.\n" +
-											`                 You need to specify '${
-												item.loader
-											}-loader' instead of '${item.loader}',\n` +
+											`                 You need to specify '${item.loader}-loader' instead of '${item.loader}',\n` +
 											"                 see https://webpack.js.org/migrate/3/#automatic-loader-module-name-extension-removed";
 									}
 									callback(err);
@@ -446,65 +1823,189 @@ class NormalModuleFactory extends Tapable {
 						}
 						if (err) return callback(err);
 
-						const optionsOnly = item.options
-							? {
-									options: item.options
-							  }
-							: undefined;
-						return callback(
-							null,
-							Object.assign({}, item, identToLoaderRequest(result), optionsOnly)
+						const parsedResult = this._parseResourceWithoutFragment(
+							/** @type {string} */
+							(result)
 						);
+
+						const type = /\.mjs$/i.test(parsedResult.path)
+							? "module"
+							: /\.cjs$/i.test(parsedResult.path)
+								? "commonjs"
+								: /** @type {ResolveRequest} */
+									(resolveRequest).descriptionFileData === undefined
+									? undefined
+									: /** @type {string} */
+										(
+											/** @type {ResolveRequest} */
+											(resolveRequest).descriptionFileData.type
+										);
+
+						this._loaderResolveCache.set(cacheKey, {
+							loader: parsedResult.path,
+							query: parsedResult.query,
+							type,
+							fileDependencies: captureContext.fileDependencies,
+							contextDependencies: captureContext.contextDependencies,
+							missingDependencies: captureContext.missingDependencies
+						});
+
+						/** @type {LoaderItem} */
+						const resolved = {
+							loader: parsedResult.path,
+							type,
+							options:
+								item.options === undefined
+									? parsedResult.query
+										? parsedResult.query.slice(1)
+										: undefined
+									: item.options,
+							ident: item.options === undefined ? undefined : item.ident
+						};
+
+						return callback(null, resolved);
 					}
 				);
 			},
-			callback
+			(err, value) => {
+				callback(
+					/** @type {Error | null} */ (err),
+					/** @type {(LoaderItem)[]} */ (value)
+				);
+			}
 		);
 	}
 
-	getParser(type, parserOptions) {
-		let ident = type;
-		if (parserOptions) {
-			if (parserOptions.ident) {
-				ident = `${type}|${parserOptions.ident}`;
-			} else {
-				ident = JSON.stringify([type, parserOptions]);
+	/**
+	 * Runs `prepareModuleType` for a type once, so a plugin can load what its
+	 * synchronous `createParser`/`createGenerator` tap needs. Calls back
+	 * synchronously for a type nothing taps or that is already prepared.
+	 * @param {string} type module type
+	 * @param {Callback} callback signals the type is ready
+	 * @returns {void}
+	 */
+	_prepareModuleType(type, callback) {
+		const state = this._moduleTypePreparation.get(type);
+		if (state === true) return callback(null);
+		if (state !== undefined) {
+			state.push(callback);
+			return;
+		}
+		const hook = this.hooks.prepareModuleType.get(type);
+		if (hook === undefined) {
+			this._moduleTypePreparation.set(type, true);
+			return callback(null);
+		}
+		/** @type {Callback[]} */
+		const waiting = [];
+		this._moduleTypePreparation.set(type, waiting);
+		hook.callAsync((err) => {
+			// a failed preparation must not be remembered as done
+			if (err) this._moduleTypePreparation.delete(type);
+			else this._moduleTypePreparation.set(type, true);
+			// every queued factorize must be resumed, so one callback throwing
+			// cannot starve the rest — the first throw is rethrown afterwards
+			waiting.unshift(callback);
+			const error = err || null;
+			/** @type {Error | undefined} */
+			let thrown;
+			for (const waitingCallback of waiting) {
+				try {
+					waitingCallback(error);
+				} catch (err_) {
+					if (thrown === undefined) {
+						thrown = /** @type {Error} */ (err_);
+					}
+				}
 			}
+			if (thrown !== undefined) throw thrown;
+		});
+	}
+
+	/**
+	 * Returns parser.
+	 * @template {string} T
+	 * @param {T} type type
+	 * @param {ParserOptions} parserOptions parser options
+	 * @returns {ParserByType[T]} parser
+	 */
+	getParser(type, parserOptions = EMPTY_PARSER_OPTIONS) {
+		let cache = this.parserCache.get(type);
+
+		if (cache === undefined) {
+			cache = new WeakMap();
+			this.parserCache.set(type, cache);
 		}
-		if (ident in this.parserCache) {
-			return this.parserCache[ident];
+
+		let parser = cache.get(parserOptions);
+
+		if (parser === undefined) {
+			parser = this.createParser(type, parserOptions);
+			cache.set(parserOptions, parser);
 		}
-		return (this.parserCache[ident] = this.createParser(type, parserOptions));
+
+		return /** @type {ParserByType[T]} */ (parser);
 	}
 
+	/**
+	 * Creates a parser from the provided type.
+	 * @template {string} T
+	 * @param {T} type type
+	 * @param {ParserOptions} parserOptions parser options
+	 * @returns {ParserByType[T]} parser
+	 */
 	createParser(type, parserOptions = {}) {
+		parserOptions = mergeGlobalOptions(
+			this._globalParserOptions,
+			type,
+			parserOptions
+		);
 		const parser = this.hooks.createParser.for(type).call(parserOptions);
 		if (!parser) {
 			throw new Error(`No parser registered for ${type}`);
 		}
 		this.hooks.parser.for(type).call(parser, parserOptions);
-		return parser;
+		return /** @type {ParserByType[T]} */ (parser);
 	}
 
-	getGenerator(type, generatorOptions) {
-		let ident = type;
-		if (generatorOptions) {
-			if (generatorOptions.ident) {
-				ident = `${type}|${generatorOptions.ident}`;
-			} else {
-				ident = JSON.stringify([type, generatorOptions]);
-			}
+	/**
+	 * Returns generator.
+	 * @template {string} T
+	 * @param {T} type type of generator
+	 * @param {GeneratorOptions} generatorOptions generator options
+	 * @returns {GeneratorByType[T]} generator
+	 */
+	getGenerator(type, generatorOptions = EMPTY_GENERATOR_OPTIONS) {
+		let cache = this.generatorCache.get(type);
+
+		if (cache === undefined) {
+			cache = new WeakMap();
+			this.generatorCache.set(type, cache);
 		}
-		if (ident in this.generatorCache) {
-			return this.generatorCache[ident];
+
+		let generator = cache.get(generatorOptions);
+
+		if (generator === undefined) {
+			generator = this.createGenerator(type, generatorOptions);
+			cache.set(generatorOptions, generator);
 		}
-		return (this.generatorCache[ident] = this.createGenerator(
-			type,
-			generatorOptions
-		));
+
+		return /** @type {GeneratorByType[T]} */ (generator);
 	}
 
+	/**
+	 * Creates a generator.
+	 * @template {string} T
+	 * @param {T} type type of generator
+	 * @param {GeneratorOptions} generatorOptions generator options
+	 * @returns {GeneratorByType[T]} generator
+	 */
 	createGenerator(type, generatorOptions = {}) {
+		generatorOptions = mergeGlobalOptions(
+			this._globalGeneratorOptions,
+			type,
+			generatorOptions
+		);
 		const generator = this.hooks.createGenerator
 			.for(type)
 			.call(generatorOptions);
@@ -512,14 +2013,17 @@ class NormalModuleFactory extends Tapable {
 			throw new Error(`No generator registered for ${type}`);
 		}
 		this.hooks.generator.for(type).call(generator, generatorOptions);
-		return generator;
+		return /** @type {GeneratorByType[T]} */ (generator);
 	}
 
+	/**
+	 * Returns the resolver.
+	 * @param {Parameters[0]} type type of resolver
+	 * @param {Parameters[1]=} resolveOptions options
+	 * @returns {ReturnType} the resolver
+	 */
 	getResolver(type, resolveOptions) {
-		return this.resolverFactory.get(
-			type,
-			resolveOptions || EMPTY_RESOLVE_OPTIONS
-		);
+		return this.resolverFactory.get(type, resolveOptions);
 	}
 }
 
diff --git a/lib/NormalModuleReplacementPlugin.js b/lib/NormalModuleReplacementPlugin.js
index d4f23a58bae..1fddba513df 100644
--- a/lib/NormalModuleReplacementPlugin.js
+++ b/lib/NormalModuleReplacementPlugin.js
@@ -2,49 +2,75 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
-const path = require("path");
+const { dirname, join } = require("./util/fs");
+
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./NormalModuleFactory").ResolveData} ResolveData */
+/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
+
+/** @typedef {(resolveData: ResolveData) => void} ModuleReplacer */
+
+const PLUGIN_NAME = "NormalModuleReplacementPlugin";
 
 class NormalModuleReplacementPlugin {
+	/**
+	 * Create an instance of the plugin
+	 * @param {RegExp} resourceRegExp the resource matcher
+	 * @param {string | ModuleReplacer} newResource the resource replacement
+	 */
 	constructor(resourceRegExp, newResource) {
+		/** @type {RegExp} */
 		this.resourceRegExp = resourceRegExp;
+		/** @type {string | ModuleReplacer} */
 		this.newResource = newResource;
 	}
 
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
 	apply(compiler) {
 		const resourceRegExp = this.resourceRegExp;
 		const newResource = this.newResource;
-		compiler.hooks.normalModuleFactory.tap(
-			"NormalModuleReplacementPlugin",
-			nmf => {
-				nmf.hooks.beforeResolve.tap("NormalModuleReplacementPlugin", result => {
-					if (!result) return;
-					if (resourceRegExp.test(result.request)) {
-						if (typeof newResource === "function") {
-							newResource(result);
-						} else {
-							result.request = newResource;
-						}
+		compiler.hooks.normalModuleFactory.tap(PLUGIN_NAME, (nmf) => {
+			nmf.hooks.beforeResolve.tap(PLUGIN_NAME, (result) => {
+				if (resourceRegExp.test(result.request)) {
+					if (typeof newResource === "function") {
+						newResource(result);
+					} else {
+						result.request = newResource;
 					}
-					return result;
-				});
-				nmf.hooks.afterResolve.tap("NormalModuleReplacementPlugin", result => {
-					if (!result) return;
-					if (resourceRegExp.test(result.resource)) {
-						if (typeof newResource === "function") {
-							newResource(result);
+				}
+			});
+			nmf.hooks.afterResolve.tap(PLUGIN_NAME, (result) => {
+				const createData = result.createData;
+				if (resourceRegExp.test(/** @type {string} */ (createData.resource))) {
+					if (typeof newResource === "function") {
+						newResource(result);
+					} else {
+						const fs =
+							/** @type {InputFileSystem} */
+							(compiler.inputFileSystem);
+						if (
+							newResource.startsWith("/") ||
+							(newResource.length > 1 && newResource[1] === ":")
+						) {
+							createData.resource = newResource;
 						} else {
-							result.resource = path.resolve(
-								path.dirname(result.resource),
+							createData.resource = join(
+								fs,
+								dirname(fs, /** @type {string} */ (createData.resource)),
 								newResource
 							);
 						}
 					}
-					return result;
-				});
-			}
-		);
+				}
+			});
+		});
 	}
 }
 
diff --git a/lib/NullFactory.js b/lib/NullFactory.js
index 90ede1fe3f1..0ebd5dac85d 100644
--- a/lib/NullFactory.js
+++ b/lib/NullFactory.js
@@ -2,11 +2,24 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
-class NullFactory {
+const ModuleFactory = require("./ModuleFactory");
+
+/** @typedef {import("./ModuleFactory").ModuleFactoryCallback} ModuleFactoryCallback */
+/** @typedef {import("./ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */
+
+class NullFactory extends ModuleFactory {
+	/**
+	 * Processes the provided data.
+	 * @param {ModuleFactoryCreateData} data data object
+	 * @param {ModuleFactoryCallback} callback callback
+	 * @returns {void}
+	 */
 	create(data, callback) {
 		return callback();
 	}
 }
+
 module.exports = NullFactory;
diff --git a/lib/OptimizationStages.js b/lib/OptimizationStages.js
new file mode 100644
index 00000000000..c464b8abef3
--- /dev/null
+++ b/lib/OptimizationStages.js
@@ -0,0 +1,10 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Florent Cailhol @ooflorent
+*/
+
+"use strict";
+
+module.exports.STAGE_ADVANCED = 10;
+module.exports.STAGE_BASIC = -10;
+module.exports.STAGE_DEFAULT = 0;
diff --git a/lib/OptionsApply.js b/lib/OptionsApply.js
index 3b1ec316485..815a952eff5 100644
--- a/lib/OptionsApply.js
+++ b/lib/OptionsApply.js
@@ -2,9 +2,24 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
+/** @typedef {import("./config/defaults").WebpackOptionsNormalizedWithDefaults} WebpackOptions */
+/** @typedef {import("./config/normalization").WebpackOptionsInterception} WebpackOptionsInterception */
+/** @typedef {import("./Compiler")} Compiler */
+
 class OptionsApply {
-	process(options, compiler) {}
+	/**
+	 * Returns options object.
+	 * @param {WebpackOptions} options options object
+	 * @param {Compiler} compiler compiler object
+	 * @param {WebpackOptionsInterception=} interception intercepted options
+	 * @returns {WebpackOptions} options object
+	 */
+	process(options, compiler, interception) {
+		return options;
+	}
 }
+
 module.exports = OptionsApply;
diff --git a/lib/OptionsDefaulter.js b/lib/OptionsDefaulter.js
deleted file mode 100644
index c5ff612d8d7..00000000000
--- a/lib/OptionsDefaulter.js
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const getProperty = (obj, name) => {
-	name = name.split(".");
-	for (let i = 0; i < name.length - 1; i++) {
-		obj = obj[name[i]];
-		if (typeof obj !== "object" || !obj || Array.isArray(obj)) return;
-	}
-	return obj[name.pop()];
-};
-
-const setProperty = (obj, name, value) => {
-	name = name.split(".");
-	for (let i = 0; i < name.length - 1; i++) {
-		if (typeof obj[name[i]] !== "object" && typeof obj[name[i]] !== "undefined")
-			return;
-		if (Array.isArray(obj[name[i]])) return;
-		if (!obj[name[i]]) obj[name[i]] = {};
-		obj = obj[name[i]];
-	}
-	obj[name.pop()] = value;
-};
-
-class OptionsDefaulter {
-	constructor() {
-		this.defaults = {};
-		this.config = {};
-	}
-
-	process(options) {
-		options = Object.assign({}, options);
-		for (let name in this.defaults) {
-			switch (this.config[name]) {
-				case undefined:
-					if (getProperty(options, name) === undefined) {
-						setProperty(options, name, this.defaults[name]);
-					}
-					break;
-				case "call":
-					setProperty(
-						options,
-						name,
-						this.defaults[name].call(this, getProperty(options, name), options)
-					);
-					break;
-				case "make":
-					if (getProperty(options, name) === undefined) {
-						setProperty(options, name, this.defaults[name].call(this, options));
-					}
-					break;
-				case "append": {
-					let oldValue = getProperty(options, name);
-					if (!Array.isArray(oldValue)) {
-						oldValue = [];
-					}
-					oldValue.push(...this.defaults[name]);
-					setProperty(options, name, oldValue);
-					break;
-				}
-				default:
-					throw new Error(
-						"OptionsDefaulter cannot process " + this.config[name]
-					);
-			}
-		}
-		return options;
-	}
-
-	set(name, config, def) {
-		if (def !== undefined) {
-			this.defaults[name] = def;
-			this.config[name] = config;
-		} else {
-			this.defaults[name] = config;
-			delete this.config[name];
-		}
-	}
-}
-
-module.exports = OptionsDefaulter;
diff --git a/lib/Parser.js b/lib/Parser.js
index 515df38771f..58d085fde54 100644
--- a/lib/Parser.js
+++ b/lib/Parser.js
@@ -2,2192 +2,41 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
-"use strict";
-
-// Syntax: https://developer.mozilla.org/en/SpiderMonkey/Parser_API
-
-const acorn = require("acorn-dynamic-import").default;
-const { Tapable, SyncBailHook, HookMap } = require("tapable");
-const util = require("util");
-const vm = require("vm");
-const BasicEvaluatedExpression = require("./BasicEvaluatedExpression");
-const StackedSetMap = require("./util/StackedSetMap");
-const TrackingSet = require("./util/TrackingSet");
-
-const joinRanges = (startRange, endRange) => {
-	if (!endRange) return startRange;
-	if (!startRange) return endRange;
-	return [startRange[0], endRange[1]];
-};
-
-const defaultParserOptions = {
-	ranges: true,
-	locations: true,
-	ecmaVersion: 2019,
-	sourceType: "module",
-	onComment: null,
-	plugins: {
-		dynamicImport: true
-	}
-};
-
-// regexp to match at lease one "magic comment"
-const webpackCommentRegExp = new RegExp(/(^|\W)webpack[A-Z]{1,}[A-Za-z]{1,}:/);
-
-const EMPTY_ARRAY = [];
-
-const EMPTY_COMMENT_OPTIONS = {
-	options: null,
-	errors: null
-};
-
-class Parser extends Tapable {
-	constructor(options, sourceType = "auto") {
-		super();
-		this.hooks = {
-			evaluateTypeof: new HookMap(() => new SyncBailHook(["expression"])),
-			evaluate: new HookMap(() => new SyncBailHook(["expression"])),
-			evaluateIdentifier: new HookMap(() => new SyncBailHook(["expression"])),
-			evaluateDefinedIdentifier: new HookMap(
-				() => new SyncBailHook(["expression"])
-			),
-			evaluateCallExpressionMember: new HookMap(
-				() => new SyncBailHook(["expression", "param"])
-			),
-			statement: new SyncBailHook(["statement"]),
-			statementIf: new SyncBailHook(["statement"]),
-			label: new HookMap(() => new SyncBailHook(["statement"])),
-			import: new SyncBailHook(["statement", "source"]),
-			importSpecifier: new SyncBailHook([
-				"statement",
-				"source",
-				"exportName",
-				"identifierName"
-			]),
-			export: new SyncBailHook(["statement"]),
-			exportImport: new SyncBailHook(["statement", "source"]),
-			exportDeclaration: new SyncBailHook(["statement", "declaration"]),
-			exportExpression: new SyncBailHook(["statement", "declaration"]),
-			exportSpecifier: new SyncBailHook([
-				"statement",
-				"identifierName",
-				"exportName",
-				"index"
-			]),
-			exportImportSpecifier: new SyncBailHook([
-				"statement",
-				"source",
-				"identifierName",
-				"exportName",
-				"index"
-			]),
-			varDeclaration: new HookMap(() => new SyncBailHook(["declaration"])),
-			varDeclarationLet: new HookMap(() => new SyncBailHook(["declaration"])),
-			varDeclarationConst: new HookMap(() => new SyncBailHook(["declaration"])),
-			varDeclarationVar: new HookMap(() => new SyncBailHook(["declaration"])),
-			canRename: new HookMap(() => new SyncBailHook(["initExpression"])),
-			rename: new HookMap(() => new SyncBailHook(["initExpression"])),
-			assigned: new HookMap(() => new SyncBailHook(["expression"])),
-			assign: new HookMap(() => new SyncBailHook(["expression"])),
-			typeof: new HookMap(() => new SyncBailHook(["expression"])),
-			importCall: new SyncBailHook(["expression"]),
-			call: new HookMap(() => new SyncBailHook(["expression"])),
-			callAnyMember: new HookMap(() => new SyncBailHook(["expression"])),
-			new: new HookMap(() => new SyncBailHook(["expression"])),
-			expression: new HookMap(() => new SyncBailHook(["expression"])),
-			expressionAnyMember: new HookMap(() => new SyncBailHook(["expression"])),
-			expressionConditionalOperator: new SyncBailHook(["expression"]),
-			program: new SyncBailHook(["ast", "comments"])
-		};
-		const HOOK_MAP_COMPAT_CONFIG = {
-			evaluateTypeof: /^evaluate typeof (.+)$/,
-			evaluateIdentifier: /^evaluate Identifier (.+)$/,
-			evaluateDefinedIdentifier: /^evaluate defined Identifier (.+)$/,
-			evaluateCallExpressionMember: /^evaluate CallExpression .(.+)$/,
-			evaluate: /^evaluate (.+)$/,
-			label: /^label (.+)$/,
-			varDeclarationLet: /^var-let (.+)$/,
-			varDeclarationConst: /^var-const (.+)$/,
-			varDeclarationVar: /^var-var (.+)$/,
-			varDeclaration: /^var (.+)$/,
-			canRename: /^can-rename (.+)$/,
-			rename: /^rename (.+)$/,
-			typeof: /^typeof (.+)$/,
-			assigned: /^assigned (.+)$/,
-			assign: /^assign (.+)$/,
-			callAnyMember: /^call (.+)\.\*$/,
-			call: /^call (.+)$/,
-			new: /^new (.+)$/,
-			expressionConditionalOperator: /^expression \?:$/,
-			expressionAnyMember: /^expression (.+)\.\*$/,
-			expression: /^expression (.+)$/
-		};
-		this._pluginCompat.tap("Parser", options => {
-			for (const name of Object.keys(HOOK_MAP_COMPAT_CONFIG)) {
-				const regexp = HOOK_MAP_COMPAT_CONFIG[name];
-				const match = regexp.exec(options.name);
-				if (match) {
-					if (match[1]) {
-						this.hooks[name].tap(
-							match[1],
-							options.fn.name || "unnamed compat plugin",
-							options.fn.bind(this)
-						);
-					} else {
-						this.hooks[name].tap(
-							options.fn.name || "unnamed compat plugin",
-							options.fn.bind(this)
-						);
-					}
-					return true;
-				}
-			}
-		});
-		this.options = options;
-		this.sourceType = sourceType;
-		this.scope = undefined;
-		this.state = undefined;
-		this.comments = undefined;
-		this.initializeEvaluating();
-	}
-
-	initializeEvaluating() {
-		this.hooks.evaluate.for("Literal").tap("Parser", expr => {
-			switch (typeof expr.value) {
-				case "number":
-					return new BasicEvaluatedExpression()
-						.setNumber(expr.value)
-						.setRange(expr.range);
-				case "string":
-					return new BasicEvaluatedExpression()
-						.setString(expr.value)
-						.setRange(expr.range);
-				case "boolean":
-					return new BasicEvaluatedExpression()
-						.setBoolean(expr.value)
-						.setRange(expr.range);
-			}
-			if (expr.value === null) {
-				return new BasicEvaluatedExpression().setNull().setRange(expr.range);
-			}
-			if (expr.value instanceof RegExp) {
-				return new BasicEvaluatedExpression()
-					.setRegExp(expr.value)
-					.setRange(expr.range);
-			}
-		});
-		this.hooks.evaluate.for("LogicalExpression").tap("Parser", expr => {
-			let left;
-			let leftAsBool;
-			let right;
-			if (expr.operator === "&&") {
-				left = this.evaluateExpression(expr.left);
-				leftAsBool = left && left.asBool();
-				if (leftAsBool === false) return left.setRange(expr.range);
-				if (leftAsBool !== true) return;
-				right = this.evaluateExpression(expr.right);
-				return right.setRange(expr.range);
-			} else if (expr.operator === "||") {
-				left = this.evaluateExpression(expr.left);
-				leftAsBool = left && left.asBool();
-				if (leftAsBool === true) return left.setRange(expr.range);
-				if (leftAsBool !== false) return;
-				right = this.evaluateExpression(expr.right);
-				return right.setRange(expr.range);
-			}
-		});
-		this.hooks.evaluate.for("BinaryExpression").tap("Parser", expr => {
-			let left;
-			let right;
-			let res;
-			if (expr.operator === "+") {
-				left = this.evaluateExpression(expr.left);
-				right = this.evaluateExpression(expr.right);
-				if (!left || !right) return;
-				res = new BasicEvaluatedExpression();
-				if (left.isString()) {
-					if (right.isString()) {
-						res.setString(left.string + right.string);
-					} else if (right.isNumber()) {
-						res.setString(left.string + right.number);
-					} else if (
-						right.isWrapped() &&
-						right.prefix &&
-						right.prefix.isString()
-					) {
-						res.setWrapped(
-							new BasicEvaluatedExpression()
-								.setString(left.string + right.prefix.string)
-								.setRange(joinRanges(left.range, right.prefix.range)),
-							right.postfix
-						);
-					} else if (right.isWrapped()) {
-						res.setWrapped(
-							new BasicEvaluatedExpression()
-								.setString(left.string)
-								.setRange(left.range),
-							right.postfix
-						);
-					} else {
-						res.setWrapped(left, null);
-					}
-				} else if (left.isNumber()) {
-					if (right.isString()) {
-						res.setString(left.number + right.string);
-					} else if (right.isNumber()) {
-						res.setNumber(left.number + right.number);
-					}
-				} else if (left.isWrapped()) {
-					if (left.postfix && left.postfix.isString() && right.isString()) {
-						res.setWrapped(
-							left.prefix,
-							new BasicEvaluatedExpression()
-								.setString(left.postfix.string + right.string)
-								.setRange(joinRanges(left.postfix.range, right.range))
-						);
-					} else if (
-						left.postfix &&
-						left.postfix.isString() &&
-						right.isNumber()
-					) {
-						res.setWrapped(
-							left.prefix,
-							new BasicEvaluatedExpression()
-								.setString(left.postfix.string + right.number)
-								.setRange(joinRanges(left.postfix.range, right.range))
-						);
-					} else if (right.isString()) {
-						res.setWrapped(left.prefix, right);
-					} else if (right.isNumber()) {
-						res.setWrapped(
-							left.prefix,
-							new BasicEvaluatedExpression()
-								.setString(right.number + "")
-								.setRange(right.range)
-						);
-					} else {
-						res.setWrapped(left.prefix, new BasicEvaluatedExpression());
-					}
-				} else {
-					if (right.isString()) {
-						res.setWrapped(null, right);
-					}
-				}
-				res.setRange(expr.range);
-				return res;
-			} else if (expr.operator === "-") {
-				left = this.evaluateExpression(expr.left);
-				right = this.evaluateExpression(expr.right);
-				if (!left || !right) return;
-				if (!left.isNumber() || !right.isNumber()) return;
-				res = new BasicEvaluatedExpression();
-				res.setNumber(left.number - right.number);
-				res.setRange(expr.range);
-				return res;
-			} else if (expr.operator === "*") {
-				left = this.evaluateExpression(expr.left);
-				right = this.evaluateExpression(expr.right);
-				if (!left || !right) return;
-				if (!left.isNumber() || !right.isNumber()) return;
-				res = new BasicEvaluatedExpression();
-				res.setNumber(left.number * right.number);
-				res.setRange(expr.range);
-				return res;
-			} else if (expr.operator === "/") {
-				left = this.evaluateExpression(expr.left);
-				right = this.evaluateExpression(expr.right);
-				if (!left || !right) return;
-				if (!left.isNumber() || !right.isNumber()) return;
-				res = new BasicEvaluatedExpression();
-				res.setNumber(left.number / right.number);
-				res.setRange(expr.range);
-				return res;
-			} else if (expr.operator === "**") {
-				left = this.evaluateExpression(expr.left);
-				right = this.evaluateExpression(expr.right);
-				if (!left || !right) return;
-				if (!left.isNumber() || !right.isNumber()) return;
-				res = new BasicEvaluatedExpression();
-				res.setNumber(Math.pow(left.number, right.number));
-				res.setRange(expr.range);
-				return res;
-			} else if (expr.operator === "==" || expr.operator === "===") {
-				left = this.evaluateExpression(expr.left);
-				right = this.evaluateExpression(expr.right);
-				if (!left || !right) return;
-				res = new BasicEvaluatedExpression();
-				res.setRange(expr.range);
-				if (left.isString() && right.isString()) {
-					return res.setBoolean(left.string === right.string);
-				} else if (left.isNumber() && right.isNumber()) {
-					return res.setBoolean(left.number === right.number);
-				} else if (left.isBoolean() && right.isBoolean()) {
-					return res.setBoolean(left.bool === right.bool);
-				}
-			} else if (expr.operator === "!=" || expr.operator === "!==") {
-				left = this.evaluateExpression(expr.left);
-				right = this.evaluateExpression(expr.right);
-				if (!left || !right) return;
-				res = new BasicEvaluatedExpression();
-				res.setRange(expr.range);
-				if (left.isString() && right.isString()) {
-					return res.setBoolean(left.string !== right.string);
-				} else if (left.isNumber() && right.isNumber()) {
-					return res.setBoolean(left.number !== right.number);
-				} else if (left.isBoolean() && right.isBoolean()) {
-					return res.setBoolean(left.bool !== right.bool);
-				}
-			} else if (expr.operator === "&") {
-				left = this.evaluateExpression(expr.left);
-				right = this.evaluateExpression(expr.right);
-				if (!left || !right) return;
-				if (!left.isNumber() || !right.isNumber()) return;
-				res = new BasicEvaluatedExpression();
-				res.setNumber(left.number & right.number);
-				res.setRange(expr.range);
-				return res;
-			} else if (expr.operator === "|") {
-				left = this.evaluateExpression(expr.left);
-				right = this.evaluateExpression(expr.right);
-				if (!left || !right) return;
-				if (!left.isNumber() || !right.isNumber()) return;
-				res = new BasicEvaluatedExpression();
-				res.setNumber(left.number | right.number);
-				res.setRange(expr.range);
-				return res;
-			} else if (expr.operator === "^") {
-				left = this.evaluateExpression(expr.left);
-				right = this.evaluateExpression(expr.right);
-				if (!left || !right) return;
-				if (!left.isNumber() || !right.isNumber()) return;
-				res = new BasicEvaluatedExpression();
-				res.setNumber(left.number ^ right.number);
-				res.setRange(expr.range);
-				return res;
-			} else if (expr.operator === ">>>") {
-				left = this.evaluateExpression(expr.left);
-				right = this.evaluateExpression(expr.right);
-				if (!left || !right) return;
-				if (!left.isNumber() || !right.isNumber()) return;
-				res = new BasicEvaluatedExpression();
-				res.setNumber(left.number >>> right.number);
-				res.setRange(expr.range);
-				return res;
-			} else if (expr.operator === ">>") {
-				left = this.evaluateExpression(expr.left);
-				right = this.evaluateExpression(expr.right);
-				if (!left || !right) return;
-				if (!left.isNumber() || !right.isNumber()) return;
-				res = new BasicEvaluatedExpression();
-				res.setNumber(left.number >> right.number);
-				res.setRange(expr.range);
-				return res;
-			} else if (expr.operator === "<<") {
-				left = this.evaluateExpression(expr.left);
-				right = this.evaluateExpression(expr.right);
-				if (!left || !right) return;
-				if (!left.isNumber() || !right.isNumber()) return;
-				res = new BasicEvaluatedExpression();
-				res.setNumber(left.number << right.number);
-				res.setRange(expr.range);
-				return res;
-			}
-		});
-		this.hooks.evaluate.for("UnaryExpression").tap("Parser", expr => {
-			if (expr.operator === "typeof") {
-				let res;
-				let name;
-				if (expr.argument.type === "Identifier") {
-					name =
-						this.scope.renames.get(expr.argument.name) || expr.argument.name;
-					if (!this.scope.definitions.has(name)) {
-						const hook = this.hooks.evaluateTypeof.get(name);
-						if (hook !== undefined) {
-							res = hook.call(expr);
-							if (res !== undefined) return res;
-						}
-					}
-				}
-				if (expr.argument.type === "MemberExpression") {
-					const exprName = this.getNameForExpression(expr.argument);
-					if (exprName && exprName.free) {
-						const hook = this.hooks.evaluateTypeof.get(exprName.name);
-						if (hook !== undefined) {
-							res = hook.call(expr);
-							if (res !== undefined) return res;
-						}
-					}
-				}
-				if (expr.argument.type === "FunctionExpression") {
-					return new BasicEvaluatedExpression()
-						.setString("function")
-						.setRange(expr.range);
-				}
-				const arg = this.evaluateExpression(expr.argument);
-				if (arg.isString() || arg.isWrapped()) {
-					return new BasicEvaluatedExpression()
-						.setString("string")
-						.setRange(expr.range);
-				}
-				if (arg.isNumber()) {
-					return new BasicEvaluatedExpression()
-						.setString("number")
-						.setRange(expr.range);
-				}
-				if (arg.isBoolean()) {
-					return new BasicEvaluatedExpression()
-						.setString("boolean")
-						.setRange(expr.range);
-				}
-				if (arg.isArray() || arg.isConstArray() || arg.isRegExp()) {
-					return new BasicEvaluatedExpression()
-						.setString("object")
-						.setRange(expr.range);
-				}
-			} else if (expr.operator === "!") {
-				const argument = this.evaluateExpression(expr.argument);
-				if (!argument) return;
-				if (argument.isBoolean()) {
-					return new BasicEvaluatedExpression()
-						.setBoolean(!argument.bool)
-						.setRange(expr.range);
-				}
-				if (argument.isTruthy()) {
-					return new BasicEvaluatedExpression()
-						.setBoolean(false)
-						.setRange(expr.range);
-				}
-				if (argument.isFalsy()) {
-					return new BasicEvaluatedExpression()
-						.setBoolean(true)
-						.setRange(expr.range);
-				}
-				if (argument.isString()) {
-					return new BasicEvaluatedExpression()
-						.setBoolean(!argument.string)
-						.setRange(expr.range);
-				}
-				if (argument.isNumber()) {
-					return new BasicEvaluatedExpression()
-						.setBoolean(!argument.number)
-						.setRange(expr.range);
-				}
-			} else if (expr.operator === "~") {
-				const argument = this.evaluateExpression(expr.argument);
-				if (!argument) return;
-				if (!argument.isNumber()) return;
-				const res = new BasicEvaluatedExpression();
-				res.setNumber(~argument.number);
-				res.setRange(expr.range);
-				return res;
-			}
-		});
-		this.hooks.evaluateTypeof.for("undefined").tap("Parser", expr => {
-			return new BasicEvaluatedExpression()
-				.setString("undefined")
-				.setRange(expr.range);
-		});
-		this.hooks.evaluate.for("Identifier").tap("Parser", expr => {
-			const name = this.scope.renames.get(expr.name) || expr.name;
-			if (!this.scope.definitions.has(expr.name)) {
-				const hook = this.hooks.evaluateIdentifier.get(name);
-				if (hook !== undefined) {
-					const result = hook.call(expr);
-					if (result) return result;
-				}
-				return new BasicEvaluatedExpression()
-					.setIdentifier(name)
-					.setRange(expr.range);
-			} else {
-				const hook = this.hooks.evaluateDefinedIdentifier.get(name);
-				if (hook !== undefined) {
-					return hook.call(expr);
-				}
-			}
-		});
-		this.hooks.evaluate.for("ThisExpression").tap("Parser", expr => {
-			const name = this.scope.renames.get("this");
-			if (name) {
-				const hook = this.hooks.evaluateIdentifier.get(name);
-				if (hook !== undefined) {
-					const result = hook.call(expr);
-					if (result) return result;
-				}
-				return new BasicEvaluatedExpression()
-					.setIdentifier(name)
-					.setRange(expr.range);
-			}
-		});
-		this.hooks.evaluate.for("MemberExpression").tap("Parser", expression => {
-			let exprName = this.getNameForExpression(expression);
-			if (exprName) {
-				if (exprName.free) {
-					const hook = this.hooks.evaluateIdentifier.get(exprName.name);
-					if (hook !== undefined) {
-						const result = hook.call(expression);
-						if (result) return result;
-					}
-					return new BasicEvaluatedExpression()
-						.setIdentifier(exprName.name)
-						.setRange(expression.range);
-				} else {
-					const hook = this.hooks.evaluateDefinedIdentifier.get(exprName.name);
-					if (hook !== undefined) {
-						return hook.call(expression);
-					}
-				}
-			}
-		});
-		this.hooks.evaluate.for("CallExpression").tap("Parser", expr => {
-			if (expr.callee.type !== "MemberExpression") return;
-			if (
-				expr.callee.property.type !==
-				(expr.callee.computed ? "Literal" : "Identifier")
-			)
-				return;
-			const param = this.evaluateExpression(expr.callee.object);
-			if (!param) return;
-			const property = expr.callee.property.name || expr.callee.property.value;
-			const hook = this.hooks.evaluateCallExpressionMember.get(property);
-			if (hook !== undefined) {
-				return hook.call(expr, param);
-			}
-		});
-		this.hooks.evaluateCallExpressionMember
-			.for("replace")
-			.tap("Parser", (expr, param) => {
-				if (!param.isString()) return;
-				if (expr.arguments.length !== 2) return;
-				let arg1 = this.evaluateExpression(expr.arguments[0]);
-				let arg2 = this.evaluateExpression(expr.arguments[1]);
-				if (!arg1.isString() && !arg1.isRegExp()) return;
-				arg1 = arg1.regExp || arg1.string;
-				if (!arg2.isString()) return;
-				arg2 = arg2.string;
-				return new BasicEvaluatedExpression()
-					.setString(param.string.replace(arg1, arg2))
-					.setRange(expr.range);
-			});
-		["substr", "substring"].forEach(fn => {
-			this.hooks.evaluateCallExpressionMember
-				.for(fn)
-				.tap("Parser", (expr, param) => {
-					if (!param.isString()) return;
-					let arg1;
-					let result,
-						str = param.string;
-					switch (expr.arguments.length) {
-						case 1:
-							arg1 = this.evaluateExpression(expr.arguments[0]);
-							if (!arg1.isNumber()) return;
-							result = str[fn](arg1.number);
-							break;
-						case 2: {
-							arg1 = this.evaluateExpression(expr.arguments[0]);
-							const arg2 = this.evaluateExpression(expr.arguments[1]);
-							if (!arg1.isNumber()) return;
-							if (!arg2.isNumber()) return;
-							result = str[fn](arg1.number, arg2.number);
-							break;
-						}
-						default:
-							return;
-					}
-					return new BasicEvaluatedExpression()
-						.setString(result)
-						.setRange(expr.range);
-				});
-		});
-
-		/**
-		 * @param {string} kind "cooked" | "raw"
-		 * @param {TODO[]} quasis quasis
-		 * @param {TODO[]} expressions expressions
-		 * @returns {BasicEvaluatedExpression[]} Simplified template
-		 */
-		const getSimplifiedTemplateResult = (kind, quasis, expressions) => {
-			const parts = [];
-
-			for (let i = 0; i < quasis.length; i++) {
-				parts.push(
-					new BasicEvaluatedExpression()
-						.setString(quasis[i].value[kind])
-						.setRange(quasis[i].range)
-				);
-
-				if (i > 0) {
-					const prevExpr = parts[parts.length - 2],
-						lastExpr = parts[parts.length - 1];
-					const expr = this.evaluateExpression(expressions[i - 1]);
-					if (!(expr.isString() || expr.isNumber())) continue;
-
-					prevExpr.setString(
-						prevExpr.string +
-							(expr.isString() ? expr.string : expr.number) +
-							lastExpr.string
-					);
-					prevExpr.setRange([prevExpr.range[0], lastExpr.range[1]]);
-					parts.pop();
-				}
-			}
-			return parts;
-		};
-
-		this.hooks.evaluate.for("TemplateLiteral").tap("Parser", node => {
-			const parts = getSimplifiedTemplateResult.call(
-				this,
-				"cooked",
-				node.quasis,
-				node.expressions
-			);
-			if (parts.length === 1) {
-				return parts[0].setRange(node.range);
-			}
-			return new BasicEvaluatedExpression()
-				.setTemplateString(parts)
-				.setRange(node.range);
-		});
-		this.hooks.evaluate.for("TaggedTemplateExpression").tap("Parser", node => {
-			if (this.evaluateExpression(node.tag).identifier !== "String.raw") return;
-			const parts = getSimplifiedTemplateResult.call(
-				this,
-				"raw",
-				node.quasi.quasis,
-				node.quasi.expressions
-			);
-			return new BasicEvaluatedExpression()
-				.setTemplateString(parts)
-				.setRange(node.range);
-		});
-
-		this.hooks.evaluateCallExpressionMember
-			.for("concat")
-			.tap("Parser", (expr, param) => {
-				if (!param.isString() && !param.isWrapped()) return;
-
-				let stringSuffix = null;
-				let hasUnknownParams = false;
-				for (let i = expr.arguments.length - 1; i >= 0; i--) {
-					const argExpr = this.evaluateExpression(expr.arguments[i]);
-					if (!argExpr.isString() && !argExpr.isNumber()) {
-						hasUnknownParams = true;
-						break;
-					}
-
-					const value = argExpr.isString()
-						? argExpr.string
-						: "" + argExpr.number;
-
-					const newString = value + (stringSuffix ? stringSuffix.string : "");
-					const newRange = [
-						argExpr.range[0],
-						(stringSuffix || argExpr).range[1]
-					];
-					stringSuffix = new BasicEvaluatedExpression()
-						.setString(newString)
-						.setRange(newRange);
-				}
-
-				if (hasUnknownParams) {
-					const prefix = param.isString() ? param : param.prefix;
-					return new BasicEvaluatedExpression()
-						.setWrapped(prefix, stringSuffix)
-						.setRange(expr.range);
-				} else if (param.isWrapped()) {
-					const postfix = stringSuffix || param.postfix;
-					return new BasicEvaluatedExpression()
-						.setWrapped(param.prefix, postfix)
-						.setRange(expr.range);
-				} else {
-					const newString =
-						param.string + (stringSuffix ? stringSuffix.string : "");
-					return new BasicEvaluatedExpression()
-						.setString(newString)
-						.setRange(expr.range);
-				}
-			});
-		this.hooks.evaluateCallExpressionMember
-			.for("split")
-			.tap("Parser", (expr, param) => {
-				if (!param.isString()) return;
-				if (expr.arguments.length !== 1) return;
-				let result;
-				const arg = this.evaluateExpression(expr.arguments[0]);
-				if (arg.isString()) {
-					result = param.string.split(arg.string);
-				} else if (arg.isRegExp()) {
-					result = param.string.split(arg.regExp);
-				} else {
-					return;
-				}
-				return new BasicEvaluatedExpression()
-					.setArray(result)
-					.setRange(expr.range);
-			});
-		this.hooks.evaluate.for("ConditionalExpression").tap("Parser", expr => {
-			const condition = this.evaluateExpression(expr.test);
-			const conditionValue = condition.asBool();
-			let res;
-			if (conditionValue === undefined) {
-				const consequent = this.evaluateExpression(expr.consequent);
-				const alternate = this.evaluateExpression(expr.alternate);
-				if (!consequent || !alternate) return;
-				res = new BasicEvaluatedExpression();
-				if (consequent.isConditional()) {
-					res.setOptions(consequent.options);
-				} else {
-					res.setOptions([consequent]);
-				}
-				if (alternate.isConditional()) {
-					res.addOptions(alternate.options);
-				} else {
-					res.addOptions([alternate]);
-				}
-			} else {
-				res = this.evaluateExpression(
-					conditionValue ? expr.consequent : expr.alternate
-				);
-			}
-			res.setRange(expr.range);
-			return res;
-		});
-		this.hooks.evaluate.for("ArrayExpression").tap("Parser", expr => {
-			const items = expr.elements.map(element => {
-				return element !== null && this.evaluateExpression(element);
-			});
-			if (!items.every(Boolean)) return;
-			return new BasicEvaluatedExpression()
-				.setItems(items)
-				.setRange(expr.range);
-		});
-	}
-
-	getRenameIdentifier(expr) {
-		const result = this.evaluateExpression(expr);
-		if (result && result.isIdentifier()) {
-			return result.identifier;
-		}
-	}
-
-	walkClass(classy) {
-		if (classy.superClass) this.walkExpression(classy.superClass);
-		if (classy.body && classy.body.type === "ClassBody") {
-			const wasTopLevel = this.scope.topLevelScope;
-			this.scope.topLevelScope = false;
-			for (const methodDefinition of classy.body.body) {
-				if (methodDefinition.type === "MethodDefinition") {
-					this.walkMethodDefinition(methodDefinition);
-				}
-			}
-			this.scope.topLevelScope = wasTopLevel;
-		}
-	}
-
-	walkMethodDefinition(methodDefinition) {
-		if (methodDefinition.computed && methodDefinition.key) {
-			this.walkExpression(methodDefinition.key);
-		}
-		if (methodDefinition.value) {
-			this.walkExpression(methodDefinition.value);
-		}
-	}
-
-	// Prewalking iterates the scope for variable declarations
-	prewalkStatements(statements) {
-		for (let index = 0, len = statements.length; index < len; index++) {
-			const statement = statements[index];
-			this.prewalkStatement(statement);
-		}
-	}
-
-	// Walking iterates the statements and expressions and processes them
-	walkStatements(statements) {
-		for (let index = 0, len = statements.length; index < len; index++) {
-			const statement = statements[index];
-			this.walkStatement(statement);
-		}
-	}
-
-	prewalkStatement(statement) {
-		switch (statement.type) {
-			case "BlockStatement":
-				this.prewalkBlockStatement(statement);
-				break;
-			case "ClassDeclaration":
-				this.prewalkClassDeclaration(statement);
-				break;
-			case "DoWhileStatement":
-				this.prewalkDoWhileStatement(statement);
-				break;
-			case "ExportAllDeclaration":
-				this.prewalkExportAllDeclaration(statement);
-				break;
-			case "ExportDefaultDeclaration":
-				this.prewalkExportDefaultDeclaration(statement);
-				break;
-			case "ExportNamedDeclaration":
-				this.prewalkExportNamedDeclaration(statement);
-				break;
-			case "ForInStatement":
-				this.prewalkForInStatement(statement);
-				break;
-			case "ForOfStatement":
-				this.prewalkForOfStatement(statement);
-				break;
-			case "ForStatement":
-				this.prewalkForStatement(statement);
-				break;
-			case "FunctionDeclaration":
-				this.prewalkFunctionDeclaration(statement);
-				break;
-			case "IfStatement":
-				this.prewalkIfStatement(statement);
-				break;
-			case "ImportDeclaration":
-				this.prewalkImportDeclaration(statement);
-				break;
-			case "LabeledStatement":
-				this.prewalkLabeledStatement(statement);
-				break;
-			case "SwitchStatement":
-				this.prewalkSwitchStatement(statement);
-				break;
-			case "TryStatement":
-				this.prewalkTryStatement(statement);
-				break;
-			case "VariableDeclaration":
-				this.prewalkVariableDeclaration(statement);
-				break;
-			case "WhileStatement":
-				this.prewalkWhileStatement(statement);
-				break;
-			case "WithStatement":
-				this.prewalkWithStatement(statement);
-				break;
-		}
-	}
-
-	walkStatement(statement) {
-		if (this.hooks.statement.call(statement) !== undefined) return;
-		switch (statement.type) {
-			case "BlockStatement":
-				this.walkBlockStatement(statement);
-				break;
-			case "ClassDeclaration":
-				this.walkClassDeclaration(statement);
-				break;
-			case "DoWhileStatement":
-				this.walkDoWhileStatement(statement);
-				break;
-			case "ExportDefaultDeclaration":
-				this.walkExportDefaultDeclaration(statement);
-				break;
-			case "ExportNamedDeclaration":
-				this.walkExportNamedDeclaration(statement);
-				break;
-			case "ExpressionStatement":
-				this.walkExpressionStatement(statement);
-				break;
-			case "ForInStatement":
-				this.walkForInStatement(statement);
-				break;
-			case "ForOfStatement":
-				this.walkForOfStatement(statement);
-				break;
-			case "ForStatement":
-				this.walkForStatement(statement);
-				break;
-			case "FunctionDeclaration":
-				this.walkFunctionDeclaration(statement);
-				break;
-			case "IfStatement":
-				this.walkIfStatement(statement);
-				break;
-			case "LabeledStatement":
-				this.walkLabeledStatement(statement);
-				break;
-			case "ReturnStatement":
-				this.walkReturnStatement(statement);
-				break;
-			case "SwitchStatement":
-				this.walkSwitchStatement(statement);
-				break;
-			case "ThrowStatement":
-				this.walkThrowStatement(statement);
-				break;
-			case "TryStatement":
-				this.walkTryStatement(statement);
-				break;
-			case "VariableDeclaration":
-				this.walkVariableDeclaration(statement);
-				break;
-			case "WhileStatement":
-				this.walkWhileStatement(statement);
-				break;
-			case "WithStatement":
-				this.walkWithStatement(statement);
-				break;
-		}
-	}
-
-	// Real Statements
-	prewalkBlockStatement(statement) {
-		this.prewalkStatements(statement.body);
-	}
-
-	walkBlockStatement(statement) {
-		this.walkStatements(statement.body);
-	}
-
-	walkExpressionStatement(statement) {
-		this.walkExpression(statement.expression);
-	}
-
-	prewalkIfStatement(statement) {
-		this.prewalkStatement(statement.consequent);
-		if (statement.alternate) {
-			this.prewalkStatement(statement.alternate);
-		}
-	}
-
-	walkIfStatement(statement) {
-		const result = this.hooks.statementIf.call(statement);
-		if (result === undefined) {
-			this.walkExpression(statement.test);
-			this.walkStatement(statement.consequent);
-			if (statement.alternate) {
-				this.walkStatement(statement.alternate);
-			}
-		} else {
-			if (result) {
-				this.walkStatement(statement.consequent);
-			} else if (statement.alternate) {
-				this.walkStatement(statement.alternate);
-			}
-		}
-	}
-
-	prewalkLabeledStatement(statement) {
-		this.prewalkStatement(statement.body);
-	}
-
-	walkLabeledStatement(statement) {
-		const hook = this.hooks.label.get(statement.label.name);
-		if (hook !== undefined) {
-			const result = hook.call(statement);
-			if (result === true) return;
-		}
-		this.walkStatement(statement.body);
-	}
-
-	prewalkWithStatement(statement) {
-		this.prewalkStatement(statement.body);
-	}
-
-	walkWithStatement(statement) {
-		this.walkExpression(statement.object);
-		this.walkStatement(statement.body);
-	}
-
-	prewalkSwitchStatement(statement) {
-		this.prewalkSwitchCases(statement.cases);
-	}
-
-	walkSwitchStatement(statement) {
-		this.walkExpression(statement.discriminant);
-		this.walkSwitchCases(statement.cases);
-	}
-
-	walkTerminatingStatement(statement) {
-		if (statement.argument) this.walkExpression(statement.argument);
-	}
-
-	walkReturnStatement(statement) {
-		this.walkTerminatingStatement(statement);
-	}
-
-	walkThrowStatement(statement) {
-		this.walkTerminatingStatement(statement);
-	}
-
-	prewalkTryStatement(statement) {
-		this.prewalkStatement(statement.block);
-	}
-
-	walkTryStatement(statement) {
-		if (this.scope.inTry) {
-			this.walkStatement(statement.block);
-		} else {
-			this.scope.inTry = true;
-			this.walkStatement(statement.block);
-			this.scope.inTry = false;
-		}
-		if (statement.handler) this.walkCatchClause(statement.handler);
-		if (statement.finalizer) this.walkStatement(statement.finalizer);
-	}
-
-	prewalkWhileStatement(statement) {
-		this.prewalkStatement(statement.body);
-	}
-
-	walkWhileStatement(statement) {
-		this.walkExpression(statement.test);
-		this.walkStatement(statement.body);
-	}
-
-	prewalkDoWhileStatement(statement) {
-		this.prewalkStatement(statement.body);
-	}
-
-	walkDoWhileStatement(statement) {
-		this.walkStatement(statement.body);
-		this.walkExpression(statement.test);
-	}
-
-	prewalkForStatement(statement) {
-		if (statement.init) {
-			if (statement.init.type === "VariableDeclaration") {
-				this.prewalkStatement(statement.init);
-			}
-		}
-		this.prewalkStatement(statement.body);
-	}
-
-	walkForStatement(statement) {
-		if (statement.init) {
-			if (statement.init.type === "VariableDeclaration") {
-				this.walkStatement(statement.init);
-			} else {
-				this.walkExpression(statement.init);
-			}
-		}
-		if (statement.test) {
-			this.walkExpression(statement.test);
-		}
-		if (statement.update) {
-			this.walkExpression(statement.update);
-		}
-		this.walkStatement(statement.body);
-	}
-
-	prewalkForInStatement(statement) {
-		if (statement.left.type === "VariableDeclaration") {
-			this.prewalkVariableDeclaration(statement.left);
-		}
-		this.prewalkStatement(statement.body);
-	}
-
-	walkForInStatement(statement) {
-		if (statement.left.type === "VariableDeclaration") {
-			this.walkVariableDeclaration(statement.left);
-		} else {
-			this.walkPattern(statement.left);
-		}
-		this.walkExpression(statement.right);
-		this.walkStatement(statement.body);
-	}
-
-	prewalkForOfStatement(statement) {
-		if (statement.left.type === "VariableDeclaration") {
-			this.prewalkVariableDeclaration(statement.left);
-		}
-		this.prewalkStatement(statement.body);
-	}
-
-	walkForOfStatement(statement) {
-		if (statement.left.type === "VariableDeclaration") {
-			this.walkVariableDeclaration(statement.left);
-		} else {
-			this.walkPattern(statement.left);
-		}
-		this.walkExpression(statement.right);
-		this.walkStatement(statement.body);
-	}
-
-	// Declarations
-	prewalkFunctionDeclaration(statement) {
-		if (statement.id) {
-			this.scope.renames.set(statement.id.name, null);
-			this.scope.definitions.add(statement.id.name);
-		}
-	}
-
-	walkFunctionDeclaration(statement) {
-		const wasTopLevel = this.scope.topLevelScope;
-		this.scope.topLevelScope = false;
-		this.inScope(statement.params, () => {
-			for (const param of statement.params) {
-				this.walkPattern(param);
-			}
-			if (statement.body.type === "BlockStatement") {
-				this.detectStrictMode(statement.body.body);
-				this.prewalkStatement(statement.body);
-				this.walkStatement(statement.body);
-			} else {
-				this.walkExpression(statement.body);
-			}
-		});
-		this.scope.topLevelScope = wasTopLevel;
-	}
-
-	prewalkImportDeclaration(statement) {
-		const source = statement.source.value;
-		this.hooks.import.call(statement, source);
-		for (const specifier of statement.specifiers) {
-			const name = specifier.local.name;
-			this.scope.renames.set(name, null);
-			this.scope.definitions.add(name);
-			switch (specifier.type) {
-				case "ImportDefaultSpecifier":
-					this.hooks.importSpecifier.call(statement, source, "default", name);
-					break;
-				case "ImportSpecifier":
-					this.hooks.importSpecifier.call(
-						statement,
-						source,
-						specifier.imported.name,
-						name
-					);
-					break;
-				case "ImportNamespaceSpecifier":
-					this.hooks.importSpecifier.call(statement, source, null, name);
-					break;
-			}
-		}
-	}
-
-	prewalkExportNamedDeclaration(statement) {
-		let source;
-		if (statement.source) {
-			source = statement.source.value;
-			this.hooks.exportImport.call(statement, source);
-		} else {
-			this.hooks.export.call(statement);
-		}
-		if (statement.declaration) {
-			if (
-				!this.hooks.exportDeclaration.call(statement, statement.declaration)
-			) {
-				const originalDefinitions = this.scope.definitions;
-				const tracker = new TrackingSet(this.scope.definitions);
-				this.scope.definitions = tracker;
-				this.prewalkStatement(statement.declaration);
-				const newDefs = Array.from(tracker.getAddedItems());
-				this.scope.definitions = originalDefinitions;
-				for (let index = newDefs.length - 1; index >= 0; index--) {
-					const def = newDefs[index];
-					this.hooks.exportSpecifier.call(statement, def, def, index);
-				}
-			}
-		}
-		if (statement.specifiers) {
-			for (
-				let specifierIndex = 0;
-				specifierIndex < statement.specifiers.length;
-				specifierIndex++
-			) {
-				const specifier = statement.specifiers[specifierIndex];
-				switch (specifier.type) {
-					case "ExportSpecifier": {
-						const name = specifier.exported.name;
-						if (source) {
-							this.hooks.exportImportSpecifier.call(
-								statement,
-								source,
-								specifier.local.name,
-								name,
-								specifierIndex
-							);
-						} else {
-							this.hooks.exportSpecifier.call(
-								statement,
-								specifier.local.name,
-								name,
-								specifierIndex
-							);
-						}
-						break;
-					}
-				}
-			}
-		}
-	}
-
-	walkExportNamedDeclaration(statement) {
-		if (statement.declaration) {
-			this.walkStatement(statement.declaration);
-		}
-	}
-
-	prewalkExportDefaultDeclaration(statement) {
-		if (statement.declaration.id) {
-			const originalDefinitions = this.scope.definitions;
-			const tracker = new TrackingSet(this.scope.definitions);
-			this.scope.definitions = tracker;
-			this.prewalkStatement(statement.declaration);
-			const newDefs = Array.from(tracker.getAddedItems());
-			this.scope.definitions = originalDefinitions;
-			for (let index = 0, len = newDefs.length; index < len; index++) {
-				const def = newDefs[index];
-				this.hooks.exportSpecifier.call(statement, def, "default");
-			}
-		}
-	}
-
-	walkExportDefaultDeclaration(statement) {
-		this.hooks.export.call(statement);
-		if (
-			statement.declaration.id &&
-			statement.declaration.type !== "FunctionExpression" &&
-			statement.declaration.type !== "ClassExpression"
-		) {
-			if (
-				!this.hooks.exportDeclaration.call(statement, statement.declaration)
-			) {
-				this.walkStatement(statement.declaration);
-			}
-		} else {
-			// Acorn parses `export default function() {}` as `FunctionDeclaration` and
-			// `export default class {}` as `ClassDeclaration`, both with `id = null`.
-			// These nodes must be treated as expressions.
-			if (statement.declaration.type === "FunctionDeclaration") {
-				this.walkFunctionDeclaration(statement.declaration);
-			} else if (statement.declaration.type === "ClassDeclaration") {
-				this.walkClassDeclaration(statement.declaration);
-			} else {
-				this.walkExpression(statement.declaration);
-			}
-			if (!this.hooks.exportExpression.call(statement, statement.declaration)) {
-				this.hooks.exportSpecifier.call(
-					statement,
-					statement.declaration,
-					"default"
-				);
-			}
-		}
-	}
-
-	prewalkExportAllDeclaration(statement) {
-		const source = statement.source.value;
-		this.hooks.exportImport.call(statement, source);
-		this.hooks.exportImportSpecifier.call(statement, source, null, null, 0);
-	}
-
-	prewalkVariableDeclaration(statement) {
-		const hookMap =
-			statement.kind === "const"
-				? this.hooks.varDeclarationConst
-				: statement.kind === "let"
-					? this.hooks.varDeclarationLet
-					: this.hooks.varDeclarationVar;
-		for (const declarator of statement.declarations) {
-			switch (declarator.type) {
-				case "VariableDeclarator": {
-					this.enterPattern(declarator.id, (name, decl) => {
-						let hook = hookMap.get(name);
-						if (hook === undefined || !hook.call(decl)) {
-							hook = this.hooks.varDeclaration.get(name);
-							if (hook === undefined || !hook.call(decl)) {
-								this.scope.renames.set(name, null);
-								this.scope.definitions.add(name);
-							}
-						}
-					});
-					break;
-				}
-			}
-		}
-	}
-
-	walkVariableDeclaration(statement) {
-		for (const declarator of statement.declarations) {
-			switch (declarator.type) {
-				case "VariableDeclarator": {
-					const renameIdentifier =
-						declarator.init && this.getRenameIdentifier(declarator.init);
-					if (renameIdentifier && declarator.id.type === "Identifier") {
-						const hook = this.hooks.canRename.get(renameIdentifier);
-						if (hook !== undefined && hook.call(declarator.init)) {
-							// renaming with "var a = b;"
-							const hook = this.hooks.rename.get(renameIdentifier);
-							if (hook === undefined || !hook.call(declarator.init)) {
-								this.scope.renames.set(
-									declarator.id.name,
-									this.scope.renames.get(renameIdentifier) || renameIdentifier
-								);
-								this.scope.definitions.delete(declarator.id.name);
-							}
-							break;
-						}
-					}
-					this.walkPattern(declarator.id);
-					if (declarator.init) this.walkExpression(declarator.init);
-					break;
-				}
-			}
-		}
-	}
-
-	prewalkClassDeclaration(statement) {
-		if (statement.id) {
-			this.scope.renames.set(statement.id.name, null);
-			this.scope.definitions.add(statement.id.name);
-		}
-	}
-
-	walkClassDeclaration(statement) {
-		this.walkClass(statement);
-	}
-
-	prewalkSwitchCases(switchCases) {
-		for (let index = 0, len = switchCases.length; index < len; index++) {
-			const switchCase = switchCases[index];
-			this.prewalkStatements(switchCase.consequent);
-		}
-	}
-
-	walkSwitchCases(switchCases) {
-		for (let index = 0, len = switchCases.length; index < len; index++) {
-			const switchCase = switchCases[index];
-
-			if (switchCase.test) {
-				this.walkExpression(switchCase.test);
-			}
-			this.walkStatements(switchCase.consequent);
-		}
-	}
-
-	walkCatchClause(catchClause) {
-		// Error binding is optional in catch clause since ECMAScript 2019
-		const errorBinding =
-			catchClause.param === null ? EMPTY_ARRAY : [catchClause.param];
-
-		this.inScope(errorBinding, () => {
-			this.prewalkStatement(catchClause.body);
-			this.walkStatement(catchClause.body);
-		});
-	}
-
-	walkPattern(pattern) {
-		switch (pattern.type) {
-			case "ArrayPattern":
-				this.walkArrayPattern(pattern);
-				break;
-			case "AssignmentPattern":
-				this.walkAssignmentPattern(pattern);
-				break;
-			case "MemberExpression":
-				this.walkMemberExpression(pattern);
-				break;
-			case "ObjectPattern":
-				this.walkObjectPattern(pattern);
-				break;
-			case "RestElement":
-				this.walkRestElement(pattern);
-				break;
-		}
-	}
-
-	walkAssignmentPattern(pattern) {
-		this.walkExpression(pattern.right);
-		this.walkPattern(pattern.left);
-	}
-
-	walkObjectPattern(pattern) {
-		for (let i = 0, len = pattern.properties.length; i < len; i++) {
-			const prop = pattern.properties[i];
-			if (prop) {
-				if (prop.computed) this.walkExpression(prop.key);
-				if (prop.value) this.walkPattern(prop.value);
-			}
-		}
-	}
-
-	walkArrayPattern(pattern) {
-		for (let i = 0, len = pattern.elements.length; i < len; i++) {
-			const element = pattern.elements[i];
-			if (element) this.walkPattern(element);
-		}
-	}
-
-	walkRestElement(pattern) {
-		this.walkPattern(pattern.argument);
-	}
-
-	walkExpressions(expressions) {
-		for (const expression of expressions) {
-			if (expression) {
-				this.walkExpression(expression);
-			}
-		}
-	}
-
-	walkExpression(expression) {
-		switch (expression.type) {
-			case "ArrayExpression":
-				this.walkArrayExpression(expression);
-				break;
-			case "ArrowFunctionExpression":
-				this.walkArrowFunctionExpression(expression);
-				break;
-			case "AssignmentExpression":
-				this.walkAssignmentExpression(expression);
-				break;
-			case "AwaitExpression":
-				this.walkAwaitExpression(expression);
-				break;
-			case "BinaryExpression":
-				this.walkBinaryExpression(expression);
-				break;
-			case "CallExpression":
-				this.walkCallExpression(expression);
-				break;
-			case "ClassExpression":
-				this.walkClassExpression(expression);
-				break;
-			case "ConditionalExpression":
-				this.walkConditionalExpression(expression);
-				break;
-			case "FunctionExpression":
-				this.walkFunctionExpression(expression);
-				break;
-			case "Identifier":
-				this.walkIdentifier(expression);
-				break;
-			case "LogicalExpression":
-				this.walkLogicalExpression(expression);
-				break;
-			case "MemberExpression":
-				this.walkMemberExpression(expression);
-				break;
-			case "NewExpression":
-				this.walkNewExpression(expression);
-				break;
-			case "ObjectExpression":
-				this.walkObjectExpression(expression);
-				break;
-			case "SequenceExpression":
-				this.walkSequenceExpression(expression);
-				break;
-			case "SpreadElement":
-				this.walkSpreadElement(expression);
-				break;
-			case "TaggedTemplateExpression":
-				this.walkTaggedTemplateExpression(expression);
-				break;
-			case "TemplateLiteral":
-				this.walkTemplateLiteral(expression);
-				break;
-			case "ThisExpression":
-				this.walkThisExpression(expression);
-				break;
-			case "UnaryExpression":
-				this.walkUnaryExpression(expression);
-				break;
-			case "UpdateExpression":
-				this.walkUpdateExpression(expression);
-				break;
-			case "YieldExpression":
-				this.walkYieldExpression(expression);
-				break;
-		}
-	}
-
-	walkAwaitExpression(expression) {
-		this.walkExpression(expression.argument);
-	}
-
-	walkArrayExpression(expression) {
-		if (expression.elements) {
-			this.walkExpressions(expression.elements);
-		}
-	}
-
-	walkSpreadElement(expression) {
-		if (expression.argument) {
-			this.walkExpression(expression.argument);
-		}
-	}
-
-	walkObjectExpression(expression) {
-		for (
-			let propIndex = 0, len = expression.properties.length;
-			propIndex < len;
-			propIndex++
-		) {
-			const prop = expression.properties[propIndex];
-			if (prop.type === "SpreadElement") {
-				this.walkExpression(prop.argument);
-				continue;
-			}
-			if (prop.computed) {
-				this.walkExpression(prop.key);
-			}
-			if (prop.shorthand) {
-				this.scope.inShorthand = true;
-			}
-			this.walkExpression(prop.value);
-			if (prop.shorthand) {
-				this.scope.inShorthand = false;
-			}
-		}
-	}
-
-	walkFunctionExpression(expression) {
-		const wasTopLevel = this.scope.topLevelScope;
-		this.scope.topLevelScope = false;
-		this.inScope(expression.params, () => {
-			for (const param of expression.params) {
-				this.walkPattern(param);
-			}
-			if (expression.body.type === "BlockStatement") {
-				this.detectStrictMode(expression.body.body);
-				this.prewalkStatement(expression.body);
-				this.walkStatement(expression.body);
-			} else {
-				this.walkExpression(expression.body);
-			}
-		});
-		this.scope.topLevelScope = wasTopLevel;
-	}
-
-	walkArrowFunctionExpression(expression) {
-		this.inScope(expression.params, () => {
-			for (const param of expression.params) {
-				this.walkPattern(param);
-			}
-			if (expression.body.type === "BlockStatement") {
-				this.detectStrictMode(expression.body.body);
-				this.prewalkStatement(expression.body);
-				this.walkStatement(expression.body);
-			} else {
-				this.walkExpression(expression.body);
-			}
-		});
-	}
-
-	walkSequenceExpression(expression) {
-		if (expression.expressions) this.walkExpressions(expression.expressions);
-	}
-
-	walkUpdateExpression(expression) {
-		this.walkExpression(expression.argument);
-	}
-
-	walkUnaryExpression(expression) {
-		if (expression.operator === "typeof") {
-			const exprName = this.getNameForExpression(expression.argument);
-			if (exprName && exprName.free) {
-				const hook = this.hooks.typeof.get(exprName.name);
-				if (hook !== undefined) {
-					const result = hook.call(expression);
-					if (result === true) return;
-				}
-			}
-		}
-		this.walkExpression(expression.argument);
-	}
-
-	walkLeftRightExpression(expression) {
-		this.walkExpression(expression.left);
-		this.walkExpression(expression.right);
-	}
-
-	walkBinaryExpression(expression) {
-		this.walkLeftRightExpression(expression);
-	}
-
-	walkLogicalExpression(expression) {
-		this.walkLeftRightExpression(expression);
-	}
-
-	walkAssignmentExpression(expression) {
-		const renameIdentifier = this.getRenameIdentifier(expression.right);
-		if (expression.left.type === "Identifier" && renameIdentifier) {
-			const hook = this.hooks.canRename.get(renameIdentifier);
-			if (hook !== undefined && hook.call(expression.right)) {
-				// renaming "a = b;"
-				const hook = this.hooks.rename.get(renameIdentifier);
-				if (hook === undefined || !hook.call(expression.right)) {
-					this.scope.renames.set(expression.left.name, renameIdentifier);
-					this.scope.definitions.delete(expression.left.name);
-				}
-				return;
-			}
-		}
-		if (expression.left.type === "Identifier") {
-			const assignedHook = this.hooks.assigned.get(expression.left.name);
-			if (assignedHook === undefined || !assignedHook.call(expression)) {
-				this.walkExpression(expression.right);
-			}
-			this.scope.renames.set(expression.left.name, null);
-			const assignHook = this.hooks.assign.get(expression.left.name);
-			if (assignHook === undefined || !assignHook.call(expression)) {
-				this.walkExpression(expression.left);
-			}
-			return;
-		}
-		this.walkExpression(expression.right);
-		this.walkPattern(expression.left);
-		this.enterPattern(expression.left, (name, decl) => {
-			this.scope.renames.set(name, null);
-		});
-	}
-
-	walkConditionalExpression(expression) {
-		const result = this.hooks.expressionConditionalOperator.call(expression);
-		if (result === undefined) {
-			this.walkExpression(expression.test);
-			this.walkExpression(expression.consequent);
-			if (expression.alternate) {
-				this.walkExpression(expression.alternate);
-			}
-		} else {
-			if (result) {
-				this.walkExpression(expression.consequent);
-			} else if (expression.alternate) {
-				this.walkExpression(expression.alternate);
-			}
-		}
-	}
-
-	walkNewExpression(expression) {
-		const callee = this.evaluateExpression(expression.callee);
-		if (callee.isIdentifier()) {
-			const hook = this.hooks.new.get(callee.identifier);
-			if (hook !== undefined) {
-				const result = hook.call(expression);
-				if (result === true) {
-					return;
-				}
-			}
-		}
-
-		this.walkExpression(expression.callee);
-		if (expression.arguments) {
-			this.walkExpressions(expression.arguments);
-		}
-	}
-
-	walkYieldExpression(expression) {
-		if (expression.argument) {
-			this.walkExpression(expression.argument);
-		}
-	}
-
-	walkTemplateLiteral(expression) {
-		if (expression.expressions) {
-			this.walkExpressions(expression.expressions);
-		}
-	}
-
-	walkTaggedTemplateExpression(expression) {
-		if (expression.tag) {
-			this.walkExpression(expression.tag);
-		}
-		if (expression.quasi && expression.quasi.expressions) {
-			this.walkExpressions(expression.quasi.expressions);
-		}
-	}
-
-	walkClassExpression(expression) {
-		this.walkClass(expression);
-	}
-
-	_walkIIFE(functionExpression, options, currentThis) {
-		const renameArgOrThis = argOrThis => {
-			const renameIdentifier = this.getRenameIdentifier(argOrThis);
-			if (renameIdentifier) {
-				const hook = this.hooks.canRename.get(renameIdentifier);
-				if (hook !== undefined && hook.call(argOrThis)) {
-					const hook = this.hooks.rename.get(renameIdentifier);
-					if (hook === undefined || !hook.call(argOrThis)) {
-						return renameIdentifier;
-					}
-				}
-			}
-			this.walkExpression(argOrThis);
-		};
-		const params = functionExpression.params;
-		const renameThis = currentThis ? renameArgOrThis(currentThis) : null;
-		const args = options.map(renameArgOrThis);
-		const wasTopLevel = this.scope.topLevelScope;
-		this.scope.topLevelScope = false;
-		this.inScope(params.filter((identifier, idx) => !args[idx]), () => {
-			if (renameThis) {
-				this.scope.renames.set("this", renameThis);
-			}
-			for (let i = 0; i < args.length; i++) {
-				const param = args[i];
-				if (!param) continue;
-				if (!params[i] || params[i].type !== "Identifier") continue;
-				this.scope.renames.set(params[i].name, param);
-			}
-			if (functionExpression.body.type === "BlockStatement") {
-				this.prewalkStatement(functionExpression.body);
-				this.walkStatement(functionExpression.body);
-			} else {
-				this.walkExpression(functionExpression.body);
-			}
-		});
-		this.scope.topLevelScope = wasTopLevel;
-	}
-
-	walkCallExpression(expression) {
-		if (
-			expression.callee.type === "MemberExpression" &&
-			expression.callee.object.type === "FunctionExpression" &&
-			!expression.callee.computed &&
-			(expression.callee.property.name === "call" ||
-				expression.callee.property.name === "bind") &&
-			expression.arguments.length > 0
-		) {
-			// (function(…) { }.call/bind(?, …))
-			this._walkIIFE(
-				expression.callee.object,
-				expression.arguments.slice(1),
-				expression.arguments[0]
-			);
-		} else if (expression.callee.type === "FunctionExpression") {
-			// (function(…) { }(…))
-			this._walkIIFE(expression.callee, expression.arguments, null);
-		} else if (expression.callee.type === "Import") {
-			let result = this.hooks.importCall.call(expression);
-			if (result === true) return;
-
-			if (expression.arguments) this.walkExpressions(expression.arguments);
-		} else {
-			const callee = this.evaluateExpression(expression.callee);
-			if (callee.isIdentifier()) {
-				const callHook = this.hooks.call.get(callee.identifier);
-				if (callHook !== undefined) {
-					let result = callHook.call(expression);
-					if (result === true) return;
-				}
-				let identifier = callee.identifier.replace(/\.[^.]+$/, "");
-				if (identifier !== callee.identifier) {
-					const callAnyHook = this.hooks.callAnyMember.get(identifier);
-					if (callAnyHook !== undefined) {
-						let result = callAnyHook.call(expression);
-						if (result === true) return;
-					}
-				}
-			}
-
-			if (expression.callee) this.walkExpression(expression.callee);
-			if (expression.arguments) this.walkExpressions(expression.arguments);
-		}
-	}
-
-	walkMemberExpression(expression) {
-		const exprName = this.getNameForExpression(expression);
-		if (exprName && exprName.free) {
-			const expressionHook = this.hooks.expression.get(exprName.name);
-			if (expressionHook !== undefined) {
-				const result = expressionHook.call(expression);
-				if (result === true) return;
-			}
-			const expressionAnyMemberHook = this.hooks.expressionAnyMember.get(
-				exprName.nameGeneral
-			);
-			if (expressionAnyMemberHook !== undefined) {
-				const result = expressionAnyMemberHook.call(expression);
-				if (result === true) return;
-			}
-		}
-		this.walkExpression(expression.object);
-		if (expression.computed === true) this.walkExpression(expression.property);
-	}
-
-	walkThisExpression(expression) {
-		const expressionHook = this.hooks.expression.get("this");
-		if (expressionHook !== undefined) {
-			expressionHook.call(expression);
-		}
-	}
-
-	walkIdentifier(expression) {
-		if (!this.scope.definitions.has(expression.name)) {
-			const hook = this.hooks.expression.get(
-				this.scope.renames.get(expression.name) || expression.name
-			);
-			if (hook !== undefined) {
-				const result = hook.call(expression);
-				if (result === true) return;
-			}
-		}
-	}
 
-	inScope(params, fn) {
-		const oldScope = this.scope;
-		this.scope = {
-			topLevelScope: oldScope.topLevelScope,
-			inTry: false,
-			inShorthand: false,
-			isStrict: oldScope.isStrict,
-			definitions: oldScope.definitions.createChild(),
-			renames: oldScope.renames.createChild()
-		};
-
-		this.scope.renames.set("this", null);
-
-		for (const param of params) {
-			if (typeof param !== "string") {
-				this.enterPattern(param, param => {
-					this.scope.renames.set(param, null);
-					this.scope.definitions.add(param);
-				});
-			} else if (param) {
-				this.scope.renames.set(param, null);
-				this.scope.definitions.add(param);
-			}
-		}
-
-		fn();
-		this.scope = oldScope;
-	}
-
-	detectStrictMode(statements) {
-		const isStrict =
-			statements.length >= 1 &&
-			statements[0].type === "ExpressionStatement" &&
-			statements[0].expression.type === "Literal" &&
-			statements[0].expression.value === "use strict";
-		if (isStrict) {
-			this.scope.isStrict = true;
-		}
-	}
-
-	enterPattern(pattern, onIdent) {
-		if (!pattern) return;
-		switch (pattern.type) {
-			case "ArrayPattern":
-				this.enterArrayPattern(pattern, onIdent);
-				break;
-			case "AssignmentPattern":
-				this.enterAssignmentPattern(pattern, onIdent);
-				break;
-			case "Identifier":
-				this.enterIdentifier(pattern, onIdent);
-				break;
-			case "ObjectPattern":
-				this.enterObjectPattern(pattern, onIdent);
-				break;
-			case "RestElement":
-				this.enterRestElement(pattern, onIdent);
-				break;
-		}
-	}
-
-	enterIdentifier(pattern, onIdent) {
-		onIdent(pattern.name, pattern);
-	}
-
-	enterObjectPattern(pattern, onIdent) {
-		for (
-			let propIndex = 0, len = pattern.properties.length;
-			propIndex < len;
-			propIndex++
-		) {
-			const prop = pattern.properties[propIndex];
-			this.enterPattern(prop.value, onIdent);
-		}
-	}
-
-	enterArrayPattern(pattern, onIdent) {
-		for (
-			let elementIndex = 0, len = pattern.elements.length;
-			elementIndex < len;
-			elementIndex++
-		) {
-			const element = pattern.elements[elementIndex];
-			this.enterPattern(element, onIdent);
-		}
-	}
-
-	enterRestElement(pattern, onIdent) {
-		this.enterPattern(pattern.argument, onIdent);
-	}
-
-	enterAssignmentPattern(pattern, onIdent) {
-		this.enterPattern(pattern.left, onIdent);
-	}
-
-	evaluateExpression(expression) {
-		try {
-			const hook = this.hooks.evaluate.get(expression.type);
-			if (hook !== undefined) {
-				const result = hook.call(expression);
-				if (result !== undefined) return result;
-			}
-		} catch (e) {
-			console.warn(e);
-			// ignore error
-		}
-		return new BasicEvaluatedExpression().setRange(expression.range);
-	}
-
-	parseString(expression) {
-		switch (expression.type) {
-			case "BinaryExpression":
-				if (expression.operator === "+") {
-					return (
-						this.parseString(expression.left) +
-						this.parseString(expression.right)
-					);
-				}
-				break;
-			case "Literal":
-				return expression.value + "";
-		}
-		throw new Error(
-			expression.type + " is not supported as parameter for require"
-		);
-	}
-
-	parseCalculatedString(expression) {
-		switch (expression.type) {
-			case "BinaryExpression":
-				if (expression.operator === "+") {
-					const left = this.parseCalculatedString(expression.left);
-					const right = this.parseCalculatedString(expression.right);
-					if (left.code) {
-						return {
-							range: left.range,
-							value: left.value,
-							code: true,
-							conditional: false
-						};
-					} else if (right.code) {
-						return {
-							range: [
-								left.range[0],
-								right.range ? right.range[1] : left.range[1]
-							],
-							value: left.value + right.value,
-							code: true,
-							conditional: false
-						};
-					} else {
-						return {
-							range: [left.range[0], right.range[1]],
-							value: left.value + right.value,
-							code: false,
-							conditional: false
-						};
-					}
-				}
-				break;
-			case "ConditionalExpression": {
-				const consequent = this.parseCalculatedString(expression.consequent);
-				const alternate = this.parseCalculatedString(expression.alternate);
-				const items = [];
-				if (consequent.conditional) {
-					items.push(...consequent.conditional);
-				} else if (!consequent.code) {
-					items.push(consequent);
-				} else {
-					break;
-				}
-				if (alternate.conditional) {
-					items.push(...alternate.conditional);
-				} else if (!alternate.code) {
-					items.push(alternate);
-				} else {
-					break;
-				}
-				return {
-					range: undefined,
-					value: "",
-					code: true,
-					conditional: items
-				};
-			}
-			case "Literal":
-				return {
-					range: expression.range,
-					value: expression.value + "",
-					code: false,
-					conditional: false
-				};
-		}
-		return {
-			range: undefined,
-			value: "",
-			code: true,
-			conditional: false
-		};
-	}
-
-	parse(source, initialState) {
-		let ast;
-		let comments;
-		if (typeof source === "object" && source !== null) {
-			ast = source;
-			comments = source.comments;
-		} else {
-			comments = [];
-			ast = Parser.parse(source, {
-				sourceType: this.sourceType,
-				onComment: comments
-			});
-		}
-
-		const oldScope = this.scope;
-		const oldState = this.state;
-		const oldComments = this.comments;
-		this.scope = {
-			topLevelScope: true,
-			inTry: false,
-			inShorthand: false,
-			isStrict: false,
-			definitions: new StackedSetMap(),
-			renames: new StackedSetMap()
-		};
-		const state = (this.state = initialState || {});
-		this.comments = comments;
-		if (this.hooks.program.call(ast, comments) === undefined) {
-			this.detectStrictMode(ast.body);
-			this.prewalkStatements(ast.body);
-			this.walkStatements(ast.body);
-		}
-		this.scope = oldScope;
-		this.state = oldState;
-		this.comments = oldComments;
-		return state;
-	}
-
-	evaluate(source) {
-		const ast = Parser.parse("(" + source + ")", {
-			sourceType: this.sourceType,
-			locations: false
-		});
-		if (ast.body.length !== 1 || ast.body[0].type !== "ExpressionStatement") {
-			throw new Error("evaluate: Source is not a expression");
-		}
-		return this.evaluateExpression(ast.body[0].expression);
-	}
-
-	getComments(range) {
-		return this.comments.filter(
-			comment => comment.range[0] >= range[0] && comment.range[1] <= range[1]
-		);
-	}
-
-	parseCommentOptions(range) {
-		const comments = this.getComments(range);
-		if (comments.length === 0) {
-			return EMPTY_COMMENT_OPTIONS;
-		}
-		let options = {};
-		let errors = [];
-		for (const comment of comments) {
-			const { value } = comment;
-			if (value && webpackCommentRegExp.test(value)) {
-				// try compile only if webpack options comment is present
-				try {
-					const val = vm.runInNewContext(`(function(){return {${value}};})()`);
-					Object.assign(options, val);
-				} catch (e) {
-					e.comment = comment;
-					errors.push(e);
-				}
-			}
-		}
-		return { options, errors };
-	}
-
-	getNameForExpression(expression) {
-		let expr = expression;
-		const exprName = [];
-		while (
-			expr.type === "MemberExpression" &&
-			expr.property.type === (expr.computed ? "Literal" : "Identifier")
-		) {
-			exprName.push(expr.computed ? expr.property.value : expr.property.name);
-			expr = expr.object;
-		}
-		let free;
-		if (expr.type === "Identifier") {
-			free = !this.scope.definitions.has(expr.name);
-			exprName.push(this.scope.renames.get(expr.name) || expr.name);
-		} else if (
-			expr.type === "ThisExpression" &&
-			this.scope.renames.get("this")
-		) {
-			free = true;
-			exprName.push(this.scope.renames.get("this"));
-		} else if (expr.type === "ThisExpression") {
-			free = this.scope.topLevelScope;
-			exprName.push("this");
-		} else {
-			return null;
-		}
-		let prefix = "";
-		for (let i = exprName.length - 1; i >= 2; i--) {
-			prefix += exprName[i] + ".";
-		}
-		if (exprName.length > 1) {
-			prefix += exprName[1];
-		}
-		const name = prefix ? prefix + "." + exprName[0] : exprName[0];
-		const nameGeneral = prefix;
-		return {
-			name,
-			nameGeneral,
-			free
-		};
-	}
-
-	static parse(code, options) {
-		const type = options ? options.sourceType : "module";
-		const parserOptions = Object.assign(
-			Object.create(null),
-			defaultParserOptions,
-			options
-		);
-
-		if (type === "auto") {
-			parserOptions.sourceType = "module";
-		}
-
-		let ast;
-		let error;
-		let threw = false;
-		try {
-			ast = acorn.parse(code, parserOptions);
-		} catch (e) {
-			error = e;
-			threw = true;
-		}
-
-		if (threw && type === "auto") {
-			parserOptions.sourceType = "script";
-			if (Array.isArray(parserOptions.onComment)) {
-				parserOptions.onComment.length = 0;
-			}
-			try {
-				ast = acorn.parse(code, parserOptions);
-				threw = false;
-			} catch (e) {
-				threw = true;
-			}
-		}
-
-		if (threw) {
-			throw error;
-		}
+"use strict";
 
-		return ast;
+/** @typedef {import("./config/defaults").WebpackOptionsNormalizedWithDefaults} WebpackOptions */
+/** @typedef {import("./Compilation")} Compilation */
+/** @typedef {import("./NormalModule")} NormalModule */
+
+/** @typedef {Record} PreparsedAst */
+
+/**
+ * Defines the parser state base type used by this module.
+ * @typedef {object} ParserStateBase
+ * @property {string | Buffer} source
+ * @property {NormalModule} current
+ * @property {NormalModule} module
+ * @property {Compilation} compilation
+ * @property {WebpackOptions} options
+ */
+
+/** @typedef {ParserStateBase & Record} ParserState */
+
+class Parser {
+	/* istanbul ignore next */
+	/**
+	 * Parses the provided source and updates the parser state.
+	 * @abstract
+	 * @param {string | Buffer | PreparsedAst} source the source to parse
+	 * @param {ParserState} state the parser state
+	 * @returns {ParserState} the parser state
+	 */
+	parse(source, state) {
+		const AbstractMethodError = require("./errors/AbstractMethodError");
+
+		throw new AbstractMethodError();
 	}
 }
 
-// TODO remove in webpack 5
-Object.defineProperty(Parser.prototype, "getCommentOptions", {
-	configurable: false,
-	value: util.deprecate(function(range) {
-		return this.parseCommentOptions(range).options;
-	}, "Parser.getCommentOptions: Use Parser.parseCommentOptions(range) instead")
-});
-
 module.exports = Parser;
diff --git a/lib/ParserHelpers.js b/lib/ParserHelpers.js
deleted file mode 100644
index 5248f12fe55..00000000000
--- a/lib/ParserHelpers.js
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-const path = require("path");
-
-const BasicEvaluatedExpression = require("./BasicEvaluatedExpression");
-const ConstDependency = require("./dependencies/ConstDependency");
-const UnsupportedFeatureWarning = require("./UnsupportedFeatureWarning");
-
-const ParserHelpers = exports;
-
-ParserHelpers.addParsedVariableToModule = (parser, name, expression) => {
-	if (!parser.state.current.addVariable) return false;
-	var deps = [];
-	parser.parse(expression, {
-		current: {
-			addDependency: dep => {
-				dep.userRequest = name;
-				deps.push(dep);
-			}
-		},
-		module: parser.state.module
-	});
-	parser.state.current.addVariable(name, expression, deps);
-	return true;
-};
-
-ParserHelpers.requireFileAsExpression = (context, pathToModule) => {
-	var moduleJsPath = path.relative(context, pathToModule);
-	if (!/^[A-Z]:/i.test(moduleJsPath)) {
-		moduleJsPath = "./" + moduleJsPath.replace(/\\/g, "/");
-	}
-	return "require(" + JSON.stringify(moduleJsPath) + ")";
-};
-
-ParserHelpers.toConstantDependency = (parser, value) => {
-	return function constDependency(expr) {
-		var dep = new ConstDependency(value, expr.range, false);
-		dep.loc = expr.loc;
-		parser.state.current.addDependency(dep);
-		return true;
-	};
-};
-
-ParserHelpers.toConstantDependencyWithWebpackRequire = (parser, value) => {
-	return function constDependencyWithWebpackRequire(expr) {
-		var dep = new ConstDependency(value, expr.range, true);
-		dep.loc = expr.loc;
-		parser.state.current.addDependency(dep);
-		return true;
-	};
-};
-
-ParserHelpers.evaluateToString = value => {
-	return function stringExpression(expr) {
-		return new BasicEvaluatedExpression().setString(value).setRange(expr.range);
-	};
-};
-
-ParserHelpers.evaluateToBoolean = value => {
-	return function booleanExpression(expr) {
-		return new BasicEvaluatedExpression()
-			.setBoolean(value)
-			.setRange(expr.range);
-	};
-};
-
-ParserHelpers.evaluateToIdentifier = (identifier, truthy) => {
-	return function identifierExpression(expr) {
-		let evex = new BasicEvaluatedExpression()
-			.setIdentifier(identifier)
-			.setRange(expr.range);
-		if (truthy === true) {
-			evex = evex.setTruthy();
-		} else if (truthy === false) {
-			evex = evex.setFalsy();
-		}
-		return evex;
-	};
-};
-
-ParserHelpers.expressionIsUnsupported = (parser, message) => {
-	return function unsupportedExpression(expr) {
-		var dep = new ConstDependency("(void 0)", expr.range, false);
-		dep.loc = expr.loc;
-		parser.state.current.addDependency(dep);
-		if (!parser.state.module) return;
-		parser.state.module.warnings.push(
-			new UnsupportedFeatureWarning(parser.state.module, message, expr.loc)
-		);
-		return true;
-	};
-};
-
-ParserHelpers.skipTraversal = function skipTraversal() {
-	return true;
-};
-
-ParserHelpers.approve = function approve() {
-	return true;
-};
diff --git a/lib/PlatformPlugin.js b/lib/PlatformPlugin.js
new file mode 100644
index 00000000000..2ce5083e11c
--- /dev/null
+++ b/lib/PlatformPlugin.js
@@ -0,0 +1,42 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Authors Ivan Kopeykin @vankop
+*/
+
+"use strict";
+
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./config/target").PlatformTargetProperties} PlatformTargetProperties */
+
+const PLUGIN_NAME = "PlatformPlugin";
+
+/**
+ * Should be used only for "target === false" or
+ * when you want to overwrite platform target properties
+ */
+class PlatformPlugin {
+	/**
+	 * Creates an instance of PlatformPlugin.
+	 * @param {Partial} platform target properties
+	 */
+	constructor(platform) {
+		/** @type {Partial} */
+		this.platform = platform;
+	}
+
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
+	apply(compiler) {
+		compiler.hooks.environment.tap(PLUGIN_NAME, () => {
+			compiler.platform = {
+				...compiler.platform,
+				...this.platform
+			};
+		});
+	}
+}
+
+module.exports = PlatformPlugin;
diff --git a/lib/PrefetchPlugin.js b/lib/PrefetchPlugin.js
index cc9d17c0c86..7830a51b7f2 100644
--- a/lib/PrefetchPlugin.js
+++ b/lib/PrefetchPlugin.js
@@ -2,22 +2,41 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
+
 const PrefetchDependency = require("./dependencies/PrefetchDependency");
 
+/** @typedef {import("./Compiler")} Compiler */
+
+const PLUGIN_NAME = "PrefetchPlugin";
+
 class PrefetchPlugin {
+	/**
+	 * Creates an instance of PrefetchPlugin.
+	 * @param {string} context context or request if context is not set
+	 * @param {string=} request request
+	 */
 	constructor(context, request) {
-		if (!request) {
-			this.request = context;
-		} else {
+		if (request) {
+			/** @type {string | null} */
 			this.context = context;
+			/** @type {string} */
 			this.request = request;
+		} else {
+			this.context = null;
+			this.request = context;
 		}
 	}
 
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
 	apply(compiler) {
 		compiler.hooks.compilation.tap(
-			"PrefetchPlugin",
+			PLUGIN_NAME,
 			(compilation, { normalModuleFactory }) => {
 				compilation.dependencyFactories.set(
 					PrefetchDependency,
@@ -25,13 +44,16 @@ class PrefetchPlugin {
 				);
 			}
 		);
-		compiler.hooks.make.tapAsync("PrefetchPlugin", (compilation, callback) => {
-			compilation.prefetch(
+		compiler.hooks.make.tapAsync(PLUGIN_NAME, (compilation, callback) => {
+			compilation.addModuleChain(
 				this.context || compiler.context,
 				new PrefetchDependency(this.request),
-				callback
+				(err) => {
+					callback(err);
+				}
 			);
 		});
 	}
 }
+
 module.exports = PrefetchPlugin;
diff --git a/lib/ProgressPlugin.js b/lib/ProgressPlugin.js
index 32cd6d2cb20..4ebf631002f 100644
--- a/lib/ProgressPlugin.js
+++ b/lib/ProgressPlugin.js
@@ -2,245 +2,910 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
-const createDefaultHandler = profile => {
-	let lineCaretPosition = 0;
-	let lastState;
-	let lastStateTime;
+const Compiler = require("./Compiler");
+const MultiCompiler = require("./MultiCompiler");
+const NormalModule = require("./NormalModule");
+const cli = require("./cli");
+const { contextify } = require("./util/identifier");
+const memoize = require("./util/memoize");
 
-	const defaultHandler = (percentage, msg, ...args) => {
-		let state = msg;
-		const details = args;
-		if (percentage < 1) {
-			percentage = Math.floor(percentage * 100);
-			msg = `${percentage}% ${msg}`;
-			if (percentage < 100) {
-				msg = ` ${msg}`;
-			}
-			if (percentage < 10) {
-				msg = ` ${msg}`;
-			}
-			for (let detail of details) {
-				if (!detail) continue;
-				if (detail.length > 40) {
-					detail = `…${detail.substr(detail.length - 39)}`;
+const getColors = memoize(() =>
+	cli.createColors({ useColor: cli.isColorSupported() })
+);
+
+const BAR_LENGTH = 25;
+const BLOCK_CHAR = "━";
+const BULLET_ICON = "●";
+
+/** @typedef {import("tapable").Tap} Tap */
+/**
+ * Defines the hook type used by this module.
+ * @template T, R, AdditionalOptions
+ * @typedef {import("tapable").Hook} Hook
+ */
+/** @typedef {import("../declarations/plugins/ProgressPlugin").ProgressPluginArgument} ProgressPluginArgument */
+/** @typedef {import("../declarations/plugins/ProgressPlugin").ProgressPluginOptions} ProgressPluginOptions */
+/** @typedef {import("./Dependency")} Dependency */
+/** @typedef {import("./Entrypoint").EntryOptions} EntryOptions */
+/** @typedef {import("./Module")} Module */
+/** @typedef {import("./logging/Logger").Logger} Logger */
+/** @typedef {import("./cli").Colors} Colors */
+
+/**
+ * Returns median.
+ * @param {number} a a
+ * @param {number} b b
+ * @param {number} c c
+ * @returns {number} median
+ */
+const median3 = (a, b, c) => a + b + c - Math.max(a, b, c) - Math.min(a, b, c);
+
+/** @typedef {(percentage: number, msg: string, ...args: string[]) => void} HandlerFn */
+
+/**
+ * @param {Logger} logger logger
+ * @param {{ value: string | undefined, time: number }[]} lastStateInfo mutable state
+ * @param {number} percentage percentage
+ * @param {string} msg msg
+ * @param {string[]} args args
+ */
+const reportProfile = (logger, lastStateInfo, percentage, msg, args) => {
+	if (percentage === 0) {
+		lastStateInfo.length = 0;
+	}
+	const fullState = [msg, ...args];
+	const state = fullState.map((s) => s.replace(/\d+\/\d+ /g, ""));
+	const now = Date.now();
+	const len = Math.max(state.length, lastStateInfo.length);
+	for (let i = len; i >= 0; i--) {
+		const stateItem = i < state.length ? state[i] : undefined;
+		const lastStateItem =
+			i < lastStateInfo.length ? lastStateInfo[i] : undefined;
+		if (lastStateItem) {
+			if (stateItem !== lastStateItem.value) {
+				const diff = now - lastStateItem.time;
+				if (lastStateItem.value) {
+					let reportState = lastStateItem.value;
+					if (i > 0) {
+						reportState = `${lastStateInfo[i - 1].value} > ${reportState}`;
+					}
+					const stateMsg = `${" | ".repeat(i)}${diff} ms ${reportState}`;
+					const d = diff;
+					// This depends on timing so we ignore it for coverage
+					/* eslint-disable no-lone-blocks */
+					/* istanbul ignore next */
+					{
+						if (d > 10000) {
+							logger.error(stateMsg);
+						} else if (d > 1000) {
+							logger.warn(stateMsg);
+						} else if (d > 10) {
+							logger.info(stateMsg);
+						} else if (d > 5) {
+							logger.log(stateMsg);
+						} else {
+							logger.debug(stateMsg);
+						}
+					}
+					/* eslint-enable no-lone-blocks */
+				}
+				if (stateItem === undefined) {
+					lastStateInfo.length = i;
+				} else {
+					lastStateItem.value = stateItem;
+					lastStateItem.time = now;
+					lastStateInfo.length = i + 1;
 				}
-				msg += ` ${detail}`;
 			}
+		} else {
+			lastStateInfo[i] = {
+				value: stateItem,
+				time: now
+			};
 		}
-		if (profile) {
-			state = state.replace(/^\d+\/\d+\s+/, "");
-			if (percentage === 0) {
-				lastState = null;
-				lastStateTime = Date.now();
-			} else if (state !== lastState || percentage === 1) {
-				const now = Date.now();
-				if (lastState) {
-					const stateMsg = `${now - lastStateTime}ms ${lastState}`;
-					goToLineStart(stateMsg);
-					process.stderr.write(stateMsg + "\n");
-					lineCaretPosition = 0;
-				}
-				lastState = state;
-				lastStateTime = now;
+	}
+};
+
+/**
+ * @param {number} ms milliseconds
+ * @returns {string} human readable duration
+ */
+const formatTime = (ms) => {
+	if (ms < 1000) return `${Math.round(ms)}ms`;
+	const seconds = Math.floor(ms / 1000);
+	if (seconds < 60) return `${seconds}s`;
+	const minutes = Math.floor(seconds / 60);
+	if (minutes < 60) return `${minutes}m ${seconds % 60}s`;
+	const hours = Math.floor(minutes / 60);
+	return `${hours}h ${minutes % 60}m`;
+};
+
+/**
+ * @param {string} name progress bar name
+ * @param {string} color progress bar color
+ * @param {number} width progress bar width in characters
+ * @returns {(percentage: number) => string} bar renderer
+ */
+const createReportBar = (name, color, width) => {
+	const c = getColors();
+	const colorFn = color in c ? c[/** @type {keyof Colors} */ (color)] : c.green;
+	// The bar only changes when the filled width does, so cache it between steps.
+	let lastFilled = -1;
+	let lastBar = "";
+
+	return (percentage) => {
+		const filled = Math.round(percentage * width);
+		if (filled === lastFilled) return lastBar;
+		lastFilled = filled;
+		const filledStr = BLOCK_CHAR.repeat(filled);
+		const emptyStr = BLOCK_CHAR.repeat(width - filled);
+		lastBar = `${[BULLET_ICON, name, filledStr].map(colorFn).join(" ")}${c.white(
+			emptyStr
+		)}`;
+		return lastBar;
+	};
+};
+
+/** @typedef {Required, boolean | "auto">>} ProgressBarOptions */
+
+/** @type {ProgressBarOptions} */
+const DEFAULT_PROGRESS_BAR = {
+	name: "Build",
+	color: "green",
+	width: BAR_LENGTH
+};
+
+/** @typedef {{ progressBar?: ProgressBarOptions | false, estimatedTime?: boolean, phaseTimings?: boolean }} DefaultHandlerOptions */
+
+/**
+ * Creates a default handler.
+ * @param {boolean | null | undefined} profile need profile
+ * @param {Logger} logger logger
+ * @param {DefaultHandlerOptions=} options display options
+ * @returns {HandlerFn} default handler
+ */
+const createDefaultHandler = (profile, logger, options = {}) => {
+	const {
+		progressBar = false,
+		estimatedTime = false,
+		phaseTimings = false
+	} = options;
+	const trackTime = estimatedTime || phaseTimings;
+
+	/** @type {{ value: string | undefined, time: number }[]} */
+	const lastStateInfo = [];
+	let buildStart = 0;
+	// Exponential moving average of the estimated total build time (0 = unset).
+	let smoothedTotal = 0;
+	/** @type {Map} accumulated ms per phase (msg) */
+	const phaseTimes = new Map();
+	/** @type {string | undefined} */
+	let currentPhase;
+	let currentPhaseStart = 0;
+	let summaryReported = false;
+	/** @type {((percentage: number) => string) | undefined} */
+	let reportBar;
+
+	/**
+	 * @param {number} now current timestamp
+	 * @returns {void}
+	 */
+	const reportPhaseSummary = (now) => {
+		if (!phaseTimings || summaryReported || phaseTimes.size === 0) return;
+		summaryReported = true;
+		// Flush the phase still running at completion.
+		if (currentPhase !== undefined) {
+			phaseTimes.set(
+				currentPhase,
+				(phaseTimes.get(currentPhase) || 0) + (now - currentPhaseStart)
+			);
+		}
+		const total = now - buildStart;
+		logger.info(`Build completed in ${formatTime(total)}`);
+		logger.info("Phase breakdown:");
+		for (const [phase, duration] of phaseTimes) {
+			if (duration > 0) {
+				const percent = total > 0 ? Math.round((duration / total) * 100) : 0;
+				logger.info(`  ${phase}: ${formatTime(duration)} (${percent}%)`);
 			}
 		}
-		goToLineStart(msg);
-		process.stderr.write(msg);
 	};
 
-	const goToLineStart = nextMessage => {
-		let str = "";
-		for (; lineCaretPosition > nextMessage.length; lineCaretPosition--) {
-			str += "\b \b";
+	/** @type {HandlerFn} */
+	const defaultHandler = (percentage, msg, ...args) => {
+		if (profile) {
+			reportProfile(logger, lastStateInfo, percentage, msg, args);
 		}
-		for (var i = 0; i < lineCaretPosition; i++) {
-			str += "\b";
+
+		const now = trackTime ? Date.now() : 0;
+
+		if (trackTime && percentage === 0) {
+			buildStart = now;
+			smoothedTotal = 0;
+			summaryReported = false;
+			phaseTimes.clear();
+			currentPhase = undefined;
+			currentPhaseStart = now;
+		}
+
+		// Accumulate time under the previous phase whenever the top-level message changes.
+		if (phaseTimings && msg && msg !== currentPhase) {
+			if (currentPhase !== undefined) {
+				phaseTimes.set(
+					currentPhase,
+					(phaseTimes.get(currentPhase) || 0) + (now - currentPhaseStart)
+				);
+			}
+			currentPhase = msg;
+			currentPhaseStart = now;
+		}
+
+		/** @type {string | undefined} */
+		let eta;
+		if (estimatedTime && percentage > 0.05 && percentage < 1) {
+			const elapsed = now - buildStart;
+			const rawTotal = elapsed / percentage;
+			smoothedTotal =
+				smoothedTotal > 0 ? smoothedTotal * 0.7 + rawTotal * 0.3 : rawTotal;
+			const remaining = smoothedTotal - elapsed;
+			if (remaining > 0) eta = `ETA: ${formatTime(remaining)}`;
+		}
+
+		if (progressBar) {
+			// Build the bar renderer once, then reuse it across every step.
+			if (reportBar === undefined) {
+				reportBar = createReportBar(
+					progressBar.name,
+					progressBar.color,
+					progressBar.width
+				);
+			}
+			const c = getColors();
+			/** @type {string} */
+			const currentBar = reportBar(percentage);
+			const suffix = eta ? ` ${eta}` : "";
+
+			if (percentage === 1) {
+				logger.status();
+				reportPhaseSummary(now);
+			} else if (msg) {
+				logger.status(
+					`${currentBar} (${Math.floor(percentage * 100)}%)${suffix}`,
+					`\n${[msg, ...args].map(c.gray).join(" ")}`
+				);
+			} else {
+				logger.status(
+					`${currentBar} (${Math.floor(percentage * 100)}%)${suffix}`
+				);
+			}
+			return;
+		}
+
+		if (eta) {
+			logger.status(`${Math.floor(percentage * 100)}%`, msg, ...args, eta);
+		} else {
+			logger.status(`${Math.floor(percentage * 100)}%`, msg, ...args);
+		}
+		if (percentage === 1 || (!msg && args.length === 0)) {
+			if (percentage === 1) reportPhaseSummary(now);
+			logger.status();
 		}
-		lineCaretPosition = nextMessage.length;
-		if (str) process.stderr.write(str);
 	};
 
 	return defaultHandler;
 };
 
+/**
+ * Defines the report progress callback.
+ * @callback ReportProgress
+ * @param {number} p percentage
+ * @param {...string} args additional arguments
+ * @returns {void}
+ */
+
+/** @type {WeakMap} */
+const progressReporters = new WeakMap();
+
+const PLUGIN_NAME = "ProgressPlugin";
+
+/** @type {Required>} */
+const DEFAULT_OPTIONS = {
+	profile: false,
+	modulesCount: 5000,
+	dependenciesCount: 10000,
+	modules: true,
+	dependencies: true,
+	activeModules: false,
+	entries: true,
+	percentBy: null,
+	progressBar: false,
+	estimatedTime: false,
+	phaseTimings: false
+};
+
+/**
+ * Whether progress can be rendered in place (interactive TTY). The bar is only
+ * useful here; in append-only output every update becomes a new line.
+ * @param {Compiler | MultiCompiler} compiler compiler
+ * @returns {boolean} true when output is an interactive terminal
+ */
+const isInteractive = (compiler) => {
+	const c =
+		compiler instanceof MultiCompiler ? compiler.compilers[0] : compiler;
+	const infrastructureLogging =
+		c && c.options && c.options.infrastructureLogging;
+	if (
+		infrastructureLogging &&
+		typeof infrastructureLogging.appendOnly === "boolean"
+	) {
+		return !infrastructureLogging.appendOnly;
+	}
+	const stream =
+		(infrastructureLogging && infrastructureLogging.stream) || process.stderr;
+	return (
+		Boolean(/** @type {NodeJS.WriteStream} */ (stream).isTTY) &&
+		process.env.TERM !== "dumb"
+	);
+};
+
 class ProgressPlugin {
-	constructor(options) {
+	/**
+	 * Returns a progress reporter, if any.
+	 * @param {Compiler} compiler the current compiler
+	 * @returns {ReportProgress | undefined} a progress reporter, if any
+	 */
+	static getReporter(compiler) {
+		return progressReporters.get(compiler);
+	}
+
+	/**
+	 * Creates an instance of ProgressPlugin.
+	 * @param {ProgressPluginArgument} options options
+	 */
+	constructor(options = {}) {
 		if (typeof options === "function") {
 			options = {
 				handler: options
 			};
 		}
-		options = options || {};
-		this.profile = options.profile;
-		this.handler = options.handler;
+
+		/** @type {ProgressPluginOptions} */
+		this.options = options;
+
+		const merged = { ...DEFAULT_OPTIONS, ...options };
+		/** @type {boolean | null} */
+		this.profile = merged.profile;
+		/** @type {HandlerFn | undefined} */
+		this.handler = merged.handler;
+		/** @type {number} */
+		this.modulesCount = merged.modulesCount;
+		/** @type {number} */
+		this.dependenciesCount = merged.dependenciesCount;
+		/** @type {boolean} */
+		this.showEntries = merged.entries;
+		/** @type {boolean} */
+		this.showModules = merged.modules;
+		/** @type {boolean} */
+		this.showDependencies = merged.dependencies;
+		/** @type {boolean} */
+		this.showActiveModules = merged.activeModules;
+		this.percentBy = merged.percentBy;
+
+		const progressBar = merged.progressBar;
+		/** @type {ProgressBarOptions | false | "auto"} */
+		this.progressBar =
+			progressBar === "auto"
+				? "auto"
+				: progressBar
+					? {
+							...DEFAULT_PROGRESS_BAR,
+							...(progressBar === true ? {} : progressBar)
+						}
+					: false;
+		/** @type {boolean} */
+		this.estimatedTime = merged.estimatedTime;
+		/** @type {boolean} */
+		this.phaseTimings = merged.phaseTimings;
 	}
 
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler | MultiCompiler} compiler webpack compiler
+	 * @returns {void}
+	 */
 	apply(compiler) {
-		const handler = this.handler || createDefaultHandler(this.profile);
-		if (compiler.compilers) {
-			const states = new Array(compiler.compilers.length);
-			compiler.compilers.forEach((compiler, idx) => {
-				new ProgressPlugin((p, msg, ...args) => {
-					states[idx] = [p, msg, ...args];
-					handler(
-						states
-							.map(state => (state && state[0]) || 0)
-							.reduce((a, b) => a + b) / states.length,
-						`[${idx}] ${msg}`,
-						...args
+		let progressBar = this.progressBar;
+		// webpack@6 (opt-in today via `experiments.futureDefaults`) turns an unset
+		// progressBar into the auto bar; an explicit `false` is still respected.
+		if (progressBar === false && this.options.progressBar === undefined) {
+			const c =
+				compiler instanceof MultiCompiler ? compiler.compilers[0] : compiler;
+			if (c && c.options && c.options.experiments.futureDefaults) {
+				progressBar = "auto";
+			}
+		}
+		if (progressBar === "auto") {
+			progressBar = isInteractive(compiler)
+				? { ...DEFAULT_PROGRESS_BAR }
+				: false;
+		}
+		const handler =
+			this.handler ||
+			createDefaultHandler(
+				this.profile,
+				compiler.getInfrastructureLogger("webpack.Progress"),
+				{
+					progressBar,
+					estimatedTime: this.estimatedTime,
+					phaseTimings: this.phaseTimings
+				}
+			);
+		if (compiler instanceof MultiCompiler) {
+			this._applyOnMultiCompiler(compiler, handler);
+		} else if (compiler instanceof Compiler) {
+			this._applyOnCompiler(compiler, handler);
+		}
+	}
+
+	/**
+	 * Apply on multi compiler.
+	 * @param {MultiCompiler} compiler webpack multi-compiler
+	 * @param {HandlerFn} handler function that executes for every progress step
+	 * @returns {void}
+	 */
+	_applyOnMultiCompiler(compiler, handler) {
+		const states = compiler.compilers.map(
+			() => /** @type {[number, ...string[]]} */ ([0])
+		);
+		for (const [idx, item] of compiler.compilers.entries()) {
+			new ProgressPlugin((p, msg, ...args) => {
+				states[idx] = [p, msg, ...args];
+				let sum = 0;
+				for (const [p] of states) sum += p;
+				handler(sum / states.length, `[${idx}] ${msg}`, ...args);
+			}).apply(item);
+		}
+	}
+
+	/**
+	 * Processes the provided compiler.
+	 * @param {Compiler} compiler webpack compiler
+	 * @param {HandlerFn} handler function that executes for every progress step
+	 * @returns {void}
+	 */
+	_applyOnCompiler(compiler, handler) {
+		compiler.hooks.validate.tap(PLUGIN_NAME, () => {
+			compiler.validate(
+				() => require("../schemas/plugins/ProgressPlugin.json"),
+				this.options,
+				{
+					name: "Progress Plugin",
+					baseDataPath: "options"
+				},
+				(options) => require("../schemas/plugins/ProgressPlugin.check")(options)
+			);
+		});
+
+		const showEntries = this.showEntries;
+		const showModules = this.showModules;
+		const showDependencies = this.showDependencies;
+		const showActiveModules = this.showActiveModules;
+		let lastActiveModule = "";
+		let currentLoader = "";
+		let lastModulesCount = 0;
+		let lastDependenciesCount = 0;
+		let lastEntriesCount = 0;
+		let modulesCount = 0;
+		let dependenciesCount = 0;
+		let entriesCount = 1;
+		let doneModules = 0;
+		let doneDependencies = 0;
+		let doneEntries = 0;
+		/** @type {Set} */
+		const activeModules = new Set();
+		let lastUpdate = 0;
+
+		const updateThrottled = () => {
+			if (lastUpdate + 500 < Date.now()) update();
+		};
+
+		const update = () => {
+			/** @type {string[]} */
+			const items = [];
+			const percentByModules =
+				doneModules /
+				Math.max(lastModulesCount || this.modulesCount || 1, modulesCount);
+			const percentByEntries =
+				doneEntries /
+				Math.max(lastEntriesCount || this.dependenciesCount || 1, entriesCount);
+			const percentByDependencies =
+				doneDependencies /
+				Math.max(lastDependenciesCount || 1, dependenciesCount);
+			/** @type {number} */
+			let percentageFactor;
+
+			switch (this.percentBy) {
+				case "entries":
+					percentageFactor = percentByEntries;
+					break;
+				case "dependencies":
+					percentageFactor = percentByDependencies;
+					break;
+				case "modules":
+					percentageFactor = percentByModules;
+					break;
+				default:
+					percentageFactor = median3(
+						percentByModules,
+						percentByEntries,
+						percentByDependencies
 					);
-				}).apply(compiler);
-			});
-		} else {
-			let lastModulesCount = 0;
-			let moduleCount = 500;
-			let doneModules = 0;
-			const activeModules = [];
-
-			const update = module => {
-				handler(
-					0.1 + (doneModules / Math.max(lastModulesCount, moduleCount)) * 0.6,
-					"building modules",
-					`${doneModules}/${moduleCount} modules`,
-					`${activeModules.length} active`,
-					activeModules[activeModules.length - 1]
+			}
+
+			const percentage = 0.1 + percentageFactor * 0.55;
+
+			if (currentLoader) {
+				items.push(
+					`import loader ${contextify(
+						compiler.context,
+						currentLoader,
+						compiler.root
+					)}`
 				);
-			};
+			} else {
+				/** @type {string[]} */
+				const statItems = [];
+				if (showEntries) {
+					statItems.push(`${doneEntries}/${entriesCount} entries`);
+				}
+				if (showDependencies) {
+					statItems.push(
+						`${doneDependencies}/${dependenciesCount} dependencies`
+					);
+				}
+				if (showModules) {
+					statItems.push(`${doneModules}/${modulesCount} modules`);
+				}
+				if (showActiveModules) {
+					statItems.push(`${activeModules.size} active`);
+				}
+				if (statItems.length > 0) {
+					items.push(statItems.join(" "));
+				}
+				if (showActiveModules) {
+					items.push(lastActiveModule);
+				}
+			}
+			handler(percentage, "building", ...items);
+			lastUpdate = Date.now();
+		};
+
+		const factorizeAdd = () => {
+			dependenciesCount++;
+			if (dependenciesCount < 50 || dependenciesCount % 100 === 0) {
+				updateThrottled();
+			}
+		};
 
-			const moduleDone = module => {
-				doneModules++;
+		const factorizeDone = () => {
+			doneDependencies++;
+			if (doneDependencies < 50 || doneDependencies % 100 === 0) {
+				updateThrottled();
+			}
+		};
+
+		const moduleAdd = () => {
+			modulesCount++;
+			if (modulesCount < 50 || modulesCount % 100 === 0) updateThrottled();
+		};
+
+		// only used when showActiveModules is set
+		/**
+		 * Processes the provided module.
+		 * @param {Module} module the module
+		 */
+		const moduleBuild = (module) => {
+			const ident = module.identifier();
+			if (ident) {
+				activeModules.add(ident);
+				lastActiveModule = ident;
+				update();
+			}
+		};
+
+		/**
+		 * Processes the provided entry.
+		 * @param {Dependency} entry entry dependency
+		 * @param {EntryOptions} options options object
+		 */
+		const entryAdd = (entry, options) => {
+			entriesCount++;
+			if (entriesCount < 5 || entriesCount % 10 === 0) updateThrottled();
+		};
+
+		/**
+		 * Processes the provided module.
+		 * @param {Module} module the module
+		 */
+		const moduleDone = (module) => {
+			doneModules++;
+			if (showActiveModules) {
 				const ident = module.identifier();
 				if (ident) {
-					const idx = activeModules.indexOf(ident);
-					if (idx >= 0) activeModules.splice(idx, 1);
-				}
-				update();
-			};
-			compiler.hooks.compilation.tap("ProgressPlugin", compilation => {
-				if (compilation.compiler.isChild()) return;
-				lastModulesCount = moduleCount;
-				moduleCount = 0;
-				doneModules = 0;
-				handler(0, "compiling");
-				compilation.hooks.buildModule.tap("ProgressPlugin", module => {
-					moduleCount++;
-					const ident = module.identifier();
-					if (ident) {
-						activeModules.push(ident);
+					activeModules.delete(ident);
+					if (lastActiveModule === ident) {
+						lastActiveModule = "";
+						for (const m of activeModules) {
+							lastActiveModule = m;
+						}
+						update();
+						return;
 					}
-					update();
-				});
-				compilation.hooks.failedModule.tap("ProgressPlugin", moduleDone);
-				compilation.hooks.succeedModule.tap("ProgressPlugin", moduleDone);
-				const hooks = {
-					finishModules: "finish module graph",
-					seal: "sealing",
-					optimizeDependenciesBasic: "basic dependencies optimization",
-					optimizeDependencies: "dependencies optimization",
-					optimizeDependenciesAdvanced: "advanced dependencies optimization",
-					afterOptimizeDependencies: "after dependencies optimization",
-					optimize: "optimizing",
-					optimizeModulesBasic: "basic module optimization",
-					optimizeModules: "module optimization",
-					optimizeModulesAdvanced: "advanced module optimization",
-					afterOptimizeModules: "after module optimization",
-					optimizeChunksBasic: "basic chunk optimization",
-					optimizeChunks: "chunk optimization",
-					optimizeChunksAdvanced: "advanced chunk optimization",
-					afterOptimizeChunks: "after chunk optimization",
-					optimizeTree: "module and chunk tree optimization",
-					afterOptimizeTree: "after module and chunk tree optimization",
-					optimizeChunkModulesBasic: "basic chunk modules optimization",
-					optimizeChunkModules: "chunk modules optimization",
-					optimizeChunkModulesAdvanced: "advanced chunk modules optimization",
-					afterOptimizeChunkModules: "after chunk modules optimization",
-					reviveModules: "module reviving",
-					optimizeModuleOrder: "module order optimization",
-					advancedOptimizeModuleOrder: "advanced module order optimization",
-					beforeModuleIds: "before module ids",
-					moduleIds: "module ids",
-					optimizeModuleIds: "module id optimization",
-					afterOptimizeModuleIds: "module id optimization",
-					reviveChunks: "chunk reviving",
-					optimizeChunkOrder: "chunk order optimization",
-					beforeChunkIds: "before chunk ids",
-					optimizeChunkIds: "chunk id optimization",
-					afterOptimizeChunkIds: "after chunk id optimization",
-					recordModules: "record modules",
-					recordChunks: "record chunks",
-					beforeHash: "hashing",
-					afterHash: "after hashing",
-					recordHash: "record hash",
-					beforeModuleAssets: "module assets processing",
-					beforeChunkAssets: "chunk assets processing",
-					additionalChunkAssets: "additional chunk assets processing",
-					record: "recording",
-					additionalAssets: "additional asset processing",
-					optimizeChunkAssets: "chunk asset optimization",
-					afterOptimizeChunkAssets: "after chunk asset optimization",
-					optimizeAssets: "asset optimization",
-					afterOptimizeAssets: "after asset optimization",
-					afterSeal: "after seal"
-				};
-				const numberOfHooks = Object.keys(hooks).length;
-				Object.keys(hooks).forEach((name, idx) => {
-					const title = hooks[name];
-					const percentage = (idx / numberOfHooks) * 0.25 + 0.7;
-					compilation.hooks[name].intercept({
-						name: "ProgressPlugin",
-						context: true,
-						call: () => {
-							handler(percentage, title);
-						},
-						tap: (context, tap) => {
-							if (context) {
-								// p is percentage from 0 to 1
-								// args is any number of messages in a hierarchical matter
-								context.reportProgress = (p, ...args) => {
-									handler(percentage, title, tap.name, ...args);
-								};
+				}
+			}
+			if (doneModules < 50 || doneModules % 100 === 0) updateThrottled();
+		};
+
+		/**
+		 * Processes the provided entry.
+		 * @param {Dependency} entry entry dependency
+		 * @param {EntryOptions} options options object
+		 */
+		const entryDone = (entry, options) => {
+			doneEntries++;
+			update();
+		};
+
+		compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
+			if (compilation.compiler.isChild()) return;
+			// Carry the previous compilation's counts into the next as an estimate
+			// (helps watch rebuilds); not persisted, to avoid cache invalidation.
+			lastModulesCount = modulesCount;
+			lastEntriesCount = entriesCount;
+			lastDependenciesCount = dependenciesCount;
+			modulesCount = dependenciesCount = entriesCount = 0;
+			doneModules = doneDependencies = doneEntries = 0;
+
+			compilation.factorizeQueue.hooks.added.tap(PLUGIN_NAME, factorizeAdd);
+			compilation.factorizeQueue.hooks.result.tap(PLUGIN_NAME, factorizeDone);
+
+			compilation.addModuleQueue.hooks.added.tap(PLUGIN_NAME, moduleAdd);
+			compilation.processDependenciesQueue.hooks.result.tap(
+				PLUGIN_NAME,
+				moduleDone
+			);
+
+			if (showActiveModules) {
+				compilation.hooks.buildModule.tap(PLUGIN_NAME, moduleBuild);
+			}
+
+			compilation.hooks.addEntry.tap(PLUGIN_NAME, entryAdd);
+			compilation.hooks.failedEntry.tap(PLUGIN_NAME, entryDone);
+			compilation.hooks.succeedEntry.tap(PLUGIN_NAME, entryDone);
+
+			// @ts-expect-error avoid dynamic require if bundled with webpack
+			if (typeof __webpack_require__ !== "function") {
+				/** @type {Set} */
+				const requiredLoaders = new Set();
+				NormalModule.getCompilationHooks(compilation).beforeLoaders.tap(
+					PLUGIN_NAME,
+					(loaders) => {
+						for (const loader of loaders) {
+							if (
+								loader.type !== "module" &&
+								!requiredLoaders.has(loader.loader)
+							) {
+								requiredLoaders.add(loader.loader);
+								currentLoader = loader.loader;
+								update();
+								require(loader.loader);
 							}
-							handler(percentage, title, tap.name);
 						}
-					});
+						if (currentLoader) {
+							currentLoader = "";
+							update();
+						}
+					}
+				);
+			}
+
+			const hooks = {
+				finishModules: "finish module graph",
+				seal: "plugins",
+				optimizeDependencies: "dependencies optimization",
+				afterOptimizeDependencies: "after dependencies optimization",
+				beforeChunks: "chunk graph",
+				afterChunks: "after chunk graph",
+				optimize: "optimizing",
+				optimizeModules: "module optimization",
+				afterOptimizeModules: "after module optimization",
+				optimizeChunks: "chunk optimization",
+				afterOptimizeChunks: "after chunk optimization",
+				optimizeTree: "module and chunk tree optimization",
+				afterOptimizeTree: "after module and chunk tree optimization",
+				optimizeChunkModules: "chunk modules optimization",
+				afterOptimizeChunkModules: "after chunk modules optimization",
+				reviveModules: "module reviving",
+				beforeModuleIds: "before module ids",
+				moduleIds: "module ids",
+				optimizeModuleIds: "module id optimization",
+				afterOptimizeModuleIds: "module id optimization",
+				reviveChunks: "chunk reviving",
+				beforeChunkIds: "before chunk ids",
+				chunkIds: "chunk ids",
+				optimizeChunkIds: "chunk id optimization",
+				afterOptimizeChunkIds: "after chunk id optimization",
+				recordModules: "record modules",
+				recordChunks: "record chunks",
+				beforeModuleHash: "module hashing",
+				beforeCodeGeneration: "code generation",
+				beforeRuntimeRequirements: "runtime requirements",
+				beforeHash: "hashing",
+				afterHash: "after hashing",
+				recordHash: "record hash",
+				beforeModuleAssets: "module assets processing",
+				beforeChunkAssets: "chunk assets processing",
+				processAssets: "asset processing",
+				afterProcessAssets: "after asset optimization",
+				record: "recording",
+				afterSeal: "after seal"
+			};
+			const numberOfHooks = Object.keys(hooks).length;
+			for (const [idx, name] of Object.keys(hooks).entries()) {
+				const title = hooks[/** @type {keyof typeof hooks} */ (name)];
+				const percentage = (idx / numberOfHooks) * 0.25 + 0.7;
+				compilation.hooks[/** @type {keyof typeof hooks} */ (name)].intercept({
+					name: PLUGIN_NAME,
+					call() {
+						handler(percentage, "sealing", title);
+					},
+					done() {
+						progressReporters.set(compiler, undefined);
+						handler(percentage, "sealing", title);
+					},
+					result() {
+						handler(percentage, "sealing", title);
+					},
+					error() {
+						handler(percentage, "sealing", title);
+					},
+					tap(tap) {
+						// p is percentage from 0 to 1
+						// args is any number of messages in a hierarchical matter
+						progressReporters.set(compilation.compiler, (p, ...args) => {
+							handler(percentage, "sealing", title, tap.name, ...args);
+						});
+						handler(percentage, "sealing", title, tap.name);
+					}
 				});
-			});
-			compiler.hooks.emit.intercept({
-				name: "ProgressPlugin",
-				context: true,
-				call: () => {
-					handler(0.95, "emitting");
+			}
+		});
+		compiler.hooks.make.intercept({
+			name: PLUGIN_NAME,
+			call() {
+				handler(0.1, "building");
+			},
+			done() {
+				handler(0.65, "building");
+			}
+		});
+		/**
+		 * Processes the provided hook.
+		 * @template {Hook} T
+		 * @param {T} hook hook
+		 * @param {number} progress progress from 0 to 1
+		 * @param {string} category category
+		 * @param {string} name name
+		 */
+		const interceptHook = (hook, progress, category, name) => {
+			hook.intercept({
+				name: PLUGIN_NAME,
+				call() {
+					handler(progress, category, name);
 				},
-				tap: (context, tap) => {
-					if (context) {
-						context.reportProgress = (p, ...args) => {
-							handler(0.95, "emitting", tap.name, ...args);
-						};
-					}
-					handler(0.95, "emitting", tap.name);
-				}
-			});
-			compiler.hooks.afterEmit.intercept({
-				name: "ProgressPlugin",
-				context: true,
-				call: () => {
-					handler(0.98, "after emitting");
+				done() {
+					progressReporters.set(compiler, undefined);
+					handler(progress, category, name);
 				},
-				tap: (context, tap) => {
-					if (context) {
-						context.reportProgress = (p, ...args) => {
-							handler(0.98, "after emitting", tap.name, ...args);
-						};
-					}
-					handler(0.98, "after emitting", tap.name);
+				result() {
+					handler(progress, category, name);
+				},
+				error() {
+					handler(progress, category, name);
+				},
+				/**
+				 * Processes the provided tap.
+				 * @param {Tap} tap tap
+				 */
+				tap(tap) {
+					progressReporters.set(compiler, (p, ...args) => {
+						handler(progress, category, name, tap.name, ...args);
+					});
+					handler(progress, category, name, tap.name);
 				}
 			});
-			compiler.hooks.done.tap("ProgressPlugin", () => {
+		};
+		compiler.cache.hooks.endIdle.intercept({
+			name: PLUGIN_NAME,
+			call() {
+				handler(0, "");
+			}
+		});
+		interceptHook(compiler.cache.hooks.endIdle, 0.01, "cache", "end idle");
+		compiler.hooks.beforeRun.intercept({
+			name: PLUGIN_NAME,
+			call() {
+				handler(0, "");
+			}
+		});
+		interceptHook(compiler.hooks.beforeRun, 0.01, "setup", "before run");
+		interceptHook(compiler.hooks.run, 0.02, "setup", "run");
+		interceptHook(compiler.hooks.watchRun, 0.03, "setup", "watch run");
+		interceptHook(
+			compiler.hooks.normalModuleFactory,
+			0.04,
+			"setup",
+			"normal module factory"
+		);
+		interceptHook(
+			compiler.hooks.contextModuleFactory,
+			0.05,
+			"setup",
+			"context module factory"
+		);
+		interceptHook(
+			compiler.hooks.beforeCompile,
+			0.06,
+			"setup",
+			"before compile"
+		);
+		interceptHook(compiler.hooks.compile, 0.07, "setup", "compile");
+		interceptHook(compiler.hooks.thisCompilation, 0.08, "setup", "compilation");
+		interceptHook(compiler.hooks.compilation, 0.09, "setup", "compilation");
+		interceptHook(compiler.hooks.finishMake, 0.69, "building", "finish");
+		interceptHook(compiler.hooks.emit, 0.95, "emitting", "emit");
+		interceptHook(compiler.hooks.afterEmit, 0.98, "emitting", "after emit");
+		interceptHook(compiler.hooks.done, 0.99, "done", "plugins");
+		compiler.hooks.done.intercept({
+			name: PLUGIN_NAME,
+			done() {
+				handler(0.99, "");
+			}
+		});
+		interceptHook(
+			compiler.cache.hooks.storeBuildDependencies,
+			0.99,
+			"cache",
+			"store build dependencies"
+		);
+		interceptHook(compiler.cache.hooks.shutdown, 0.99, "cache", "shutdown");
+		interceptHook(compiler.cache.hooks.beginIdle, 0.99, "cache", "begin idle");
+		interceptHook(
+			compiler.hooks.watchClose,
+			0.99,
+			"end",
+			"closing watch compilation"
+		);
+		compiler.cache.hooks.beginIdle.intercept({
+			name: PLUGIN_NAME,
+			done() {
 				handler(1, "");
-			});
-		}
+			}
+		});
+		compiler.cache.hooks.shutdown.intercept({
+			name: PLUGIN_NAME,
+			done() {
+				handler(1, "");
+			}
+		});
 	}
 }
+
+ProgressPlugin.defaultOptions = DEFAULT_OPTIONS;
+
+ProgressPlugin.createDefaultHandler = createDefaultHandler;
+
 module.exports = ProgressPlugin;
diff --git a/lib/ProvidePlugin.js b/lib/ProvidePlugin.js
index b09d8c864da..5a5498f91ec 100644
--- a/lib/ProvidePlugin.js
+++ b/lib/ProvidePlugin.js
@@ -2,85 +2,123 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
-const ParserHelpers = require("./ParserHelpers");
+const {
+	JAVASCRIPT_MODULE_TYPE_AUTO,
+	JAVASCRIPT_MODULE_TYPE_DYNAMIC,
+	JAVASCRIPT_MODULE_TYPE_ESM
+} = require("./ModuleTypeConstants");
 const ConstDependency = require("./dependencies/ConstDependency");
+const ProvidedDependency = require("./dependencies/ProvidedDependency");
+const { approve } = require("./javascript/JavascriptParserHelpers");
+
+/** @typedef {import("../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */
+/** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */
+/** @typedef {import("./javascript/JavascriptParser").Range} Range */
 
-const NullFactory = require("./NullFactory");
+const PLUGIN_NAME = "ProvidePlugin";
 
 class ProvidePlugin {
+	/**
+	 * Creates an instance of ProvidePlugin.
+	 * @param {Record} definitions the provided identifiers
+	 */
 	constructor(definitions) {
+		/** @type {Record} */
 		this.definitions = definitions;
 	}
 
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
 	apply(compiler) {
 		const definitions = this.definitions;
 		compiler.hooks.compilation.tap(
-			"ProvidePlugin",
+			PLUGIN_NAME,
 			(compilation, { normalModuleFactory }) => {
-				compilation.dependencyFactories.set(ConstDependency, new NullFactory());
 				compilation.dependencyTemplates.set(
 					ConstDependency,
 					new ConstDependency.Template()
 				);
+				compilation.dependencyFactories.set(
+					ProvidedDependency,
+					normalModuleFactory
+				);
+				compilation.dependencyTemplates.set(
+					ProvidedDependency,
+					new ProvidedDependency.Template()
+				);
+				/**
+				 * Handles the hook callback for this code path.
+				 * @param {JavascriptParser} parser the parser
+				 * @param {JavascriptParserOptions} parserOptions options
+				 * @returns {void}
+				 */
 				const handler = (parser, parserOptions) => {
-					Object.keys(definitions).forEach(name => {
-						var request = [].concat(definitions[name]);
-						var splittedName = name.split(".");
+					for (const name of Object.keys(definitions)) {
+						const request = [
+							...(Array.isArray(definitions[name])
+								? definitions[name]
+								: [definitions[name]])
+						];
+						const splittedName = name.split(".");
 						if (splittedName.length > 0) {
-							splittedName.slice(1).forEach((_, i) => {
+							for (const [i, _] of splittedName.slice(1).entries()) {
 								const name = splittedName.slice(0, i + 1).join(".");
-								parser.hooks.canRename
-									.for(name)
-									.tap("ProvidePlugin", ParserHelpers.approve);
-							});
-						}
-						parser.hooks.expression.for(name).tap("ProvidePlugin", expr => {
-							let nameIdentifier = name;
-							const scopedName = name.includes(".");
-							let expression = `require(${JSON.stringify(request[0])})`;
-							if (scopedName) {
-								nameIdentifier = `__webpack_provided_${name.replace(
-									/\./g,
-									"_dot_"
-								)}`;
-							}
-							if (request.length > 1) {
-								expression += request
-									.slice(1)
-									.map(r => `[${JSON.stringify(r)}]`)
-									.join("");
-							}
-							if (
-								!ParserHelpers.addParsedVariableToModule(
-									parser,
-									nameIdentifier,
-									expression
-								)
-							) {
-								return false;
-							}
-							if (scopedName) {
-								ParserHelpers.toConstantDependency(parser, nameIdentifier)(
-									expr
-								);
+								parser.hooks.canRename.for(name).tap(PLUGIN_NAME, approve);
 							}
+						}
+
+						parser.hooks.expression.for(name).tap(PLUGIN_NAME, (expr) => {
+							const nameIdentifier = name.includes(".")
+								? `__webpack_provided_${name.replace(/\./g, "_dot_")}`
+								: name;
+							const dep = new ProvidedDependency(
+								request[0],
+								nameIdentifier,
+								request.slice(1),
+								/** @type {Range} */ (expr.range)
+							);
+							dep.loc = parser.getLocation(expr);
+							parser.state.module.addDependency(dep);
 							return true;
 						});
-					});
+
+						parser.hooks.call.for(name).tap(PLUGIN_NAME, (expr) => {
+							const nameIdentifier = name.includes(".")
+								? `__webpack_provided_${name.replace(/\./g, "_dot_")}`
+								: name;
+							const dep = new ProvidedDependency(
+								request[0],
+								nameIdentifier,
+								request.slice(1),
+								/** @type {Range} */ (expr.callee.range)
+							);
+							dep.loc = parser.getLocation(expr.callee);
+							parser.state.module.addDependency(dep);
+							parser.walkExpressions(expr.arguments);
+							return true;
+						});
+					}
 				};
 				normalModuleFactory.hooks.parser
-					.for("javascript/auto")
-					.tap("ProvidePlugin", handler);
+					.for(JAVASCRIPT_MODULE_TYPE_AUTO)
+					.tap(PLUGIN_NAME, handler);
 				normalModuleFactory.hooks.parser
-					.for("javascript/dynamic")
-					.tap("ProvidePlugin", handler);
+					.for(JAVASCRIPT_MODULE_TYPE_DYNAMIC)
+					.tap(PLUGIN_NAME, handler);
 				normalModuleFactory.hooks.parser
-					.for("javascript/esm")
-					.tap("ProvidePlugin", handler);
+					.for(JAVASCRIPT_MODULE_TYPE_ESM)
+					.tap(PLUGIN_NAME, handler);
 			}
 		);
 	}
 }
+
 module.exports = ProvidePlugin;
diff --git a/lib/RawModule.js b/lib/RawModule.js
index ab3fd3ab5bd..4dac5722f07 100644
--- a/lib/RawModule.js
+++ b/lib/RawModule.js
@@ -2,38 +2,114 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
-const Module = require("./Module");
 const { OriginalSource, RawSource } = require("webpack-sources");
+const Module = require("./Module");
+const {
+	JAVASCRIPT_TYPE,
+	JAVASCRIPT_TYPES
+} = require("./ModuleSourceTypeConstants");
+const { JAVASCRIPT_MODULE_TYPE_DYNAMIC } = require("./ModuleTypeConstants");
+const makeSerializable = require("./util/makeSerializable");
 
-module.exports = class RawModule extends Module {
-	constructor(source, identifier, readableIdentifier) {
-		super("javascript/dynamic", null);
+/** @typedef {import("./config/defaults").WebpackOptionsNormalizedWithDefaults} WebpackOptions */
+/** @typedef {import("./Compilation")} Compilation */
+/** @typedef {import("./Dependency").UpdateHashContext} UpdateHashContext */
+/** @typedef {import("./Generator").SourceTypes} SourceTypes */
+/** @typedef {import("./Module").BuildCallback} BuildCallback */
+/** @typedef {import("./Module").CodeGenerationContext} CodeGenerationContext */
+/** @typedef {import("./Module").CodeGenerationResult} CodeGenerationResult */
+/** @typedef {import("./Module").NeedBuildCallback} NeedBuildCallback */
+/** @typedef {import("./Module").NeedBuildContext} NeedBuildContext */
+/** @typedef {import("./Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */
+/** @typedef {import("./Module").Sources} Sources */
+/** @typedef {import("./ModuleGraph")} ModuleGraph */
+/** @typedef {import("./ModuleGraphConnection").ConnectionState} ConnectionState */
+/** @typedef {import("./RequestShortener")} RequestShortener */
+/** @typedef {import("./ResolverFactory").ResolverWithOptions} ResolverWithOptions */
+/** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext<[string, string, string, ReadOnlyRuntimeRequirements | null]>} ObjectDeserializerContext */
+/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext<[string, string, string, ReadOnlyRuntimeRequirements | null]>} ObjectSerializerContext */
+/** @typedef {import("./util/Hash")} Hash */
+/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
+
+class RawModule extends Module {
+	/**
+	 * Creates an instance of RawModule.
+	 * @param {string} source source code
+	 * @param {string} identifier unique identifier
+	 * @param {string=} readableIdentifier readable identifier
+	 * @param {ReadOnlyRuntimeRequirements=} runtimeRequirements runtime requirements needed for the source code
+	 */
+	constructor(source, identifier, readableIdentifier, runtimeRequirements) {
+		super(JAVASCRIPT_MODULE_TYPE_DYNAMIC, null);
+		/** @type {string} */
 		this.sourceStr = source;
+		/** @type {string} */
 		this.identifierStr = identifier || this.sourceStr;
+		/** @type {string} */
 		this.readableIdentifierStr = readableIdentifier || this.identifierStr;
-		this.built = false;
+		/** @type {ReadOnlyRuntimeRequirements | null} */
+		this.runtimeRequirements = runtimeRequirements || null;
 	}
 
+	/**
+	 * Returns the source types this module can generate.
+	 * @returns {SourceTypes} types available (do not mutate)
+	 */
+	getSourceTypes() {
+		return JAVASCRIPT_TYPES;
+	}
+
+	/**
+	 * Returns the unique identifier used to reference this module.
+	 * @returns {string} a unique identifier of the module
+	 */
 	identifier() {
 		return this.identifierStr;
 	}
 
-	size() {
-		return this.sourceStr.length;
+	/**
+	 * Returns the estimated size for the requested source type.
+	 * @param {string=} type the source type for which the size should be estimated
+	 * @returns {number} the estimated size of the module (must be non-zero)
+	 */
+	size(type) {
+		return Math.max(1, this.sourceStr.length);
 	}
 
+	/**
+	 * Returns a human-readable identifier for this module.
+	 * @param {RequestShortener} requestShortener the request shortener
+	 * @returns {string} a user readable identifier of the module
+	 */
 	readableIdentifier(requestShortener) {
-		return requestShortener.shorten(this.readableIdentifierStr);
+		return /** @type {string} */ (
+			requestShortener.shorten(this.readableIdentifierStr)
+		);
 	}
 
-	needRebuild() {
-		return false;
+	/**
+	 * Checks whether the module needs to be rebuilt for the current build state.
+	 * @param {NeedBuildContext} context context info
+	 * @param {NeedBuildCallback} callback callback function, returns true, if the module needs a rebuild
+	 * @returns {void}
+	 */
+	needBuild(context, callback) {
+		return callback(null, !this.buildMeta);
 	}
 
-	build(options, compilations, resolver, fs, callback) {
-		this.built = true;
+	/**
+	 * Builds the module using the provided compilation context.
+	 * @param {WebpackOptions} options webpack options
+	 * @param {Compilation} compilation the compilation
+	 * @param {ResolverWithOptions} resolver the resolver
+	 * @param {InputFileSystem} fs the file system
+	 * @param {BuildCallback} callback callback function
+	 * @returns {void}
+	 */
+	build(options, compilation, resolver, fs, callback) {
 		this.buildMeta = {};
 		this.buildInfo = {
 			cacheable: true
@@ -41,16 +117,80 @@ module.exports = class RawModule extends Module {
 		callback();
 	}
 
-	source() {
-		if (this.useSourceMap) {
-			return new OriginalSource(this.sourceStr, this.identifier());
+	/**
+	 * Gets side effects connection state.
+	 * @param {ModuleGraph} moduleGraph the module graph
+	 * @returns {ConnectionState} how this module should be connected to referencing modules when consumed for side-effects only
+	 */
+	getSideEffectsConnectionState(moduleGraph) {
+		if (this.factoryMeta !== undefined) {
+			if (this.factoryMeta.sideEffectFree) return false;
+			if (this.factoryMeta.sideEffectFree === false) return true;
+		}
+		return true;
+	}
+
+	/**
+	 * Generates code and runtime requirements for this module.
+	 * @param {CodeGenerationContext} context context for code generation
+	 * @returns {CodeGenerationResult} result
+	 */
+	codeGeneration(context) {
+		/** @type {Sources} */
+		const sources = new Map();
+		if (this.useSourceMap || this.useSimpleSourceMap) {
+			sources.set(
+				JAVASCRIPT_TYPE,
+				new OriginalSource(this.sourceStr, this.identifier())
+			);
 		} else {
-			return new RawSource(this.sourceStr);
+			sources.set(JAVASCRIPT_TYPE, new RawSource(this.sourceStr));
 		}
+		return { sources, runtimeRequirements: this.runtimeRequirements };
 	}
 
-	updateHash(hash) {
+	/**
+	 * Updates the hash with the data contributed by this instance.
+	 * @param {Hash} hash the hash used to track dependencies
+	 * @param {UpdateHashContext} context context
+	 * @returns {void}
+	 */
+	updateHash(hash, context) {
 		hash.update(this.sourceStr);
-		super.updateHash(hash);
+		super.updateHash(hash, context);
 	}
-};
+
+	/**
+	 * Serializes this instance into the provided serializer context.
+	 * @param {ObjectSerializerContext} context context
+	 */
+	serialize(context) {
+		context
+			.write(this.sourceStr)
+			.write(this.identifierStr)
+			.write(this.readableIdentifierStr)
+			.write(this.runtimeRequirements);
+
+		super.serialize(context);
+	}
+
+	/**
+	 * Restores this instance from the provided deserializer context.
+	 * @param {ObjectDeserializerContext} context context
+	 */
+	deserialize(context) {
+		this.sourceStr = context.read();
+		const c1 = context.rest;
+		this.identifierStr = c1.read();
+		const c2 = c1.rest;
+		this.readableIdentifierStr = c2.read();
+		const c3 = c2.rest;
+		this.runtimeRequirements = c3.read();
+
+		super.deserialize(c3.rest);
+	}
+}
+
+makeSerializable(RawModule, "webpack/lib/RawModule");
+
+module.exports = RawModule;
diff --git a/lib/RecordIdsPlugin.js b/lib/RecordIdsPlugin.js
index 88c6a9a3c76..179a7dda6bb 100644
--- a/lib/RecordIdsPlugin.js
+++ b/lib/RecordIdsPlugin.js
@@ -2,155 +2,162 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
+const { compareNumbers } = require("./util/comparators");
 const identifierUtils = require("./util/identifier");
 
-/** @typedef {import("./Compiler")} Compiler */
 /** @typedef {import("./Chunk")} Chunk */
+/** @typedef {import("./Compiler")} Compiler */
 /** @typedef {import("./Module")} Module */
 
 /**
- * @typedef {Object} RecordsChunks
+ * Defines the records chunks type used by this module.
+ * @typedef {object} RecordsChunks
  * @property {Record=} byName
  * @property {Record=} bySource
  * @property {number[]=} usedIds
  */
 
 /**
- * @typedef {Object} RecordsModules
+ * Defines the records modules type used by this module.
+ * @typedef {object} RecordsModules
  * @property {Record=} byIdentifier
- * @property {Record=} bySource
- * @property {Record=} usedIds
+ * @property {number[]=} usedIds
  */
 
 /**
- * @typedef {Object} Records
+ * Defines the records type used by this module.
+ * @typedef {object} Records
  * @property {RecordsChunks=} chunks
  * @property {RecordsModules=} modules
  */
 
+/**
+ * Defines the record ids plugin options type used by this module.
+ * @typedef {object} RecordIdsPluginOptions
+ * @property {boolean=} portableIds true, when ids need to be portable
+ */
+
+/** @typedef {Set} UsedIds */
+
+const PLUGIN_NAME = "RecordIdsPlugin";
+
 class RecordIdsPlugin {
 	/**
-	 * @param {Object} options Options object
-	 * @param {boolean=} options.portableIds true, when ids need to be portable
+	 * Creates an instance of RecordIdsPlugin.
+	 * @param {RecordIdsPluginOptions=} options object
 	 */
 	constructor(options) {
+		/** @type {RecordIdsPluginOptions} */
 		this.options = options || {};
 	}
 
 	/**
+	 * Applies the plugin by registering its hooks on the compiler.
 	 * @param {Compiler} compiler the Compiler
 	 * @returns {void}
 	 */
 	apply(compiler) {
 		const portableIds = this.options.portableIds;
-		compiler.hooks.compilation.tap("RecordIdsPlugin", compilation => {
-			compilation.hooks.recordModules.tap(
-				"RecordIdsPlugin",
-				/**
-				 * @param {Module[]} modules the modules array
-				 * @param {Records} records the records object
-				 * @returns {void}
-				 */
-				(modules, records) => {
-					if (!records.modules) records.modules = {};
-					if (!records.modules.byIdentifier) records.modules.byIdentifier = {};
-					if (!records.modules.usedIds) records.modules.usedIds = {};
+
+		const makePathsRelative =
+			identifierUtils.makePathsRelative.bindContextCache(
+				compiler.context,
+				compiler.root
+			);
+
+		/**
+		 * Gets module identifier.
+		 * @param {Module} module the module
+		 * @returns {string} the (portable) identifier
+		 */
+		const getModuleIdentifier = (module) => {
+			if (portableIds) {
+				return makePathsRelative(module.identifier());
+			}
+			return module.identifier();
+		};
+
+		compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
+			compilation.hooks.recordModules.tap(PLUGIN_NAME, (modules, records) => {
+				const chunkGraph = compilation.chunkGraph;
+				if (!records.modules) records.modules = {};
+				if (!records.modules.byIdentifier) records.modules.byIdentifier = {};
+				/** @type {UsedIds} */
+				const usedIds = new Set();
+				for (const module of modules) {
+					const moduleId = chunkGraph.getModuleId(module);
+					if (typeof moduleId !== "number") continue;
+					const identifier = getModuleIdentifier(module);
+					records.modules.byIdentifier[identifier] = moduleId;
+					usedIds.add(moduleId);
+				}
+				records.modules.usedIds = [...usedIds].sort(compareNumbers);
+			});
+			compilation.hooks.reviveModules.tap(PLUGIN_NAME, (modules, records) => {
+				if (!records.modules) return;
+				if (records.modules.byIdentifier) {
+					const chunkGraph = compilation.chunkGraph;
+					/** @type {UsedIds} */
+					const usedIds = new Set();
 					for (const module of modules) {
-						if (typeof module.id !== "number") continue;
-						const identifier = portableIds
-							? identifierUtils.makePathsRelative(
-									compiler.context,
-									module.identifier(),
-									compilation.cache
-							  )
-							: module.identifier();
-						records.modules.byIdentifier[identifier] = module.id;
-						records.modules.usedIds[module.id] = module.id;
+						const moduleId = chunkGraph.getModuleId(module);
+						if (moduleId !== null) continue;
+						const identifier = getModuleIdentifier(module);
+						const id = records.modules.byIdentifier[identifier];
+						if (id === undefined) continue;
+						if (usedIds.has(id)) continue;
+						usedIds.add(id);
+						chunkGraph.setModuleId(module, id);
 					}
 				}
-			);
-			compilation.hooks.reviveModules.tap(
-				"RecordIdsPlugin",
-				/**
-				 * @param {Module[]} modules the modules array
-				 * @param {Records} records the records object
-				 * @returns {void}
-				 */
-				(modules, records) => {
-					if (!records.modules) return;
-					if (records.modules.byIdentifier) {
-						/** @type {Set} */
-						const usedIds = new Set();
-						for (const module of modules) {
-							if (module.id !== null) continue;
-							const identifier = portableIds
-								? identifierUtils.makePathsRelative(
-										compiler.context,
-										module.identifier(),
-										compilation.cache
-								  )
-								: module.identifier();
-							const id = records.modules.byIdentifier[identifier];
-							if (id === undefined) continue;
-							if (usedIds.has(id)) continue;
-							usedIds.add(id);
-							module.id = id;
-						}
-					}
-					if (Array.isArray(records.modules.usedIds)) {
-						compilation.usedModuleIds = new Set(records.modules.usedIds);
-					}
+				if (Array.isArray(records.modules.usedIds)) {
+					compilation.usedModuleIds = new Set(records.modules.usedIds);
 				}
-			);
+			});
 
-			/**
-			 * @param {Module} module the module
-			 * @returns {string} the (portable) identifier
-			 */
-			const getModuleIdentifier = module => {
-				if (portableIds) {
-					return identifierUtils.makePathsRelative(
-						compiler.context,
-						module.identifier(),
-						compilation.cache
-					);
-				}
-				return module.identifier();
-			};
+			/** @typedef {string[]} ChunkSources */
 
 			/**
+			 * Gets chunk sources.
 			 * @param {Chunk} chunk the chunk
-			 * @returns {string[]} sources of the chunk
+			 * @returns {ChunkSources} sources of the chunk
 			 */
-			const getChunkSources = chunk => {
-				/** @type {string[]} */
+			const getChunkSources = (chunk) => {
+				/** @type {ChunkSources} */
 				const sources = [];
 				for (const chunkGroup of chunk.groupsIterable) {
 					const index = chunkGroup.chunks.indexOf(chunk);
-					for (const origin of chunkGroup.origins) {
-						if (origin.module) {
-							if (origin.request) {
-								sources.push(
-									`${index} ${getModuleIdentifier(origin.module)} ${
-										origin.request
-									}`
-								);
-							} else if (typeof origin.loc === "string") {
-								sources.push(
-									`${index} ${getModuleIdentifier(origin.module)} ${origin.loc}`
-								);
-							} else if (
-								origin.loc &&
-								typeof origin.loc === "object" &&
-								origin.loc.start
-							) {
-								sources.push(
-									`${index} ${getModuleIdentifier(
-										origin.module
-									)} ${JSON.stringify(origin.loc.start)}`
-								);
+					if (chunkGroup.name) {
+						sources.push(`${index} ${chunkGroup.name}`);
+					} else {
+						for (const origin of chunkGroup.origins) {
+							if (origin.module) {
+								if (origin.request) {
+									sources.push(
+										`${index} ${getModuleIdentifier(origin.module)} ${
+											origin.request
+										}`
+									);
+								} else if (typeof origin.loc === "string") {
+									sources.push(
+										`${index} ${getModuleIdentifier(origin.module)} ${
+											origin.loc
+										}`
+									);
+								} else if (
+									origin.loc &&
+									typeof origin.loc === "object" &&
+									"start" in origin.loc
+								) {
+									sources.push(
+										`${index} ${getModuleIdentifier(
+											origin.module
+										)} ${JSON.stringify(origin.loc.start)}`
+									);
+								}
 							}
 						}
 					}
@@ -158,73 +165,61 @@ class RecordIdsPlugin {
 				return sources;
 			};
 
-			compilation.hooks.recordChunks.tap(
-				"RecordIdsPlugin",
-				/**
-				 * @param {Chunk[]} chunks the chunks array
-				 * @param {Records} records the records object
-				 * @returns {void}
-				 */
-				(chunks, records) => {
-					if (!records.chunks) records.chunks = {};
-					if (!records.chunks.byName) records.chunks.byName = {};
-					if (!records.chunks.bySource) records.chunks.bySource = {};
-					/** @type {Set} */
-					const usedIds = new Set();
+			compilation.hooks.recordChunks.tap(PLUGIN_NAME, (chunks, records) => {
+				if (!records.chunks) records.chunks = {};
+				if (!records.chunks.byName) records.chunks.byName = {};
+				if (!records.chunks.bySource) records.chunks.bySource = {};
+				/** @type {UsedIds} */
+				const usedIds = new Set();
+				for (const chunk of chunks) {
+					if (typeof chunk.id !== "number") continue;
+					const name = chunk.name;
+					if (name) records.chunks.byName[name] = chunk.id;
+					const sources = getChunkSources(chunk);
+					for (const source of sources) {
+						records.chunks.bySource[source] = chunk.id;
+					}
+					usedIds.add(chunk.id);
+				}
+				records.chunks.usedIds = [...usedIds].sort(compareNumbers);
+			});
+			compilation.hooks.reviveChunks.tap(PLUGIN_NAME, (chunks, records) => {
+				if (!records.chunks) return;
+				/** @type {UsedIds} */
+				const usedIds = new Set();
+				if (records.chunks.byName) {
 					for (const chunk of chunks) {
-						if (typeof chunk.id !== "number") continue;
-						const name = chunk.name;
-						if (name) records.chunks.byName[name] = chunk.id;
-						const sources = getChunkSources(chunk);
-						for (const source of sources) {
-							records.chunks.bySource[source] = chunk.id;
-						}
-						usedIds.add(chunk.id);
+						if (chunk.id !== null) continue;
+						if (!chunk.name) continue;
+						const id = records.chunks.byName[chunk.name];
+						if (id === undefined) continue;
+						if (usedIds.has(id)) continue;
+						usedIds.add(id);
+						chunk.id = id;
+						chunk.ids = [id];
 					}
-					records.chunks.usedIds = Array.from(usedIds).sort();
 				}
-			);
-			compilation.hooks.reviveChunks.tap(
-				"RecordIdsPlugin",
-				/**
-				 * @param {Chunk[]} chunks the chunks array
-				 * @param {Records} records the records object
-				 * @returns {void}
-				 */
-				(chunks, records) => {
-					if (!records.chunks) return;
-					/** @type {Set} */
-					const usedIds = new Set();
-					if (records.chunks.byName) {
-						for (const chunk of chunks) {
-							if (chunk.id !== null) continue;
-							if (!chunk.name) continue;
-							const id = records.chunks.byName[chunk.name];
+				if (records.chunks.bySource) {
+					for (const chunk of chunks) {
+						if (chunk.id !== null) continue;
+						const sources = getChunkSources(chunk);
+						for (const source of sources) {
+							const id = records.chunks.bySource[source];
 							if (id === undefined) continue;
 							if (usedIds.has(id)) continue;
 							usedIds.add(id);
 							chunk.id = id;
+							chunk.ids = [id];
+							break;
 						}
 					}
-					if (records.chunks.bySource) {
-						for (const chunk of chunks) {
-							const sources = getChunkSources(chunk);
-							for (const source of sources) {
-								const id = records.chunks.bySource[source];
-								if (id === undefined) continue;
-								if (usedIds.has(id)) continue;
-								usedIds.add(id);
-								chunk.id = id;
-								break;
-							}
-						}
-					}
-					if (Array.isArray(records.chunks.usedIds)) {
-						compilation.usedChunkIds = new Set(records.chunks.usedIds);
-					}
 				}
-			);
+				if (Array.isArray(records.chunks.usedIds)) {
+					compilation.usedChunkIds = new Set(records.chunks.usedIds);
+				}
+			});
 		});
 	}
 }
+
 module.exports = RecordIdsPlugin;
diff --git a/lib/RemovedPluginError.js b/lib/RemovedPluginError.js
deleted file mode 100644
index 626c3b4fb2c..00000000000
--- a/lib/RemovedPluginError.js
+++ /dev/null
@@ -1,11 +0,0 @@
-"use strict";
-
-const WebpackError = require("./WebpackError");
-
-module.exports = class RemovedPluginError extends WebpackError {
-	constructor(message) {
-		super(message);
-
-		Error.captureStackTrace(this, this.constructor);
-	}
-};
diff --git a/lib/RequestShortener.js b/lib/RequestShortener.js
index 7b007816a8c..ebda7b229d2 100644
--- a/lib/RequestShortener.js
+++ b/lib/RequestShortener.js
@@ -2,81 +2,42 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
-"use strict";
 
-const path = require("path");
-const NORMALIZE_SLASH_DIRECTION_REGEXP = /\\/g;
-const PATH_CHARS_REGEXP = /[-[\]{}()*+?.,\\^$|#\s]/g;
-const SEPARATOR_REGEXP = /[/\\]$/;
-const FRONT_OR_BACK_BANG_REGEXP = /^!|!$/g;
-const INDEX_JS_REGEXP = /\/index.js(!|\?|\(query\))/g;
-const MATCH_RESOURCE_REGEXP = /!=!/;
+"use strict";
 
-const normalizeBackSlashDirection = request => {
-	return request.replace(NORMALIZE_SLASH_DIRECTION_REGEXP, "/");
-};
+const { contextify } = require("./util/identifier");
 
-const createRegExpForPath = path => {
-	const regexpTypePartial = path.replace(PATH_CHARS_REGEXP, "\\$&");
-	return new RegExp(`(^|!)${regexpTypePartial}`, "g");
-};
+/** @typedef {import("./util/identifier").AssociatedObjectForCache} AssociatedObjectForCache */
 
+/**
+ * Shortens absolute or verbose request strings so diagnostics and stats output
+ * can be rendered relative to a chosen base directory.
+ */
 class RequestShortener {
-	constructor(directory) {
-		directory = normalizeBackSlashDirection(directory);
-		if (SEPARATOR_REGEXP.test(directory)) {
-			directory = directory.substr(0, directory.length - 1);
-		}
-
-		if (directory) {
-			this.currentDirectoryRegExp = createRegExpForPath(directory);
-		}
-
-		const dirname = path.dirname(directory);
-		const endsWithSeparator = SEPARATOR_REGEXP.test(dirname);
-		const parentDirectory = endsWithSeparator
-			? dirname.substr(0, dirname.length - 1)
-			: dirname;
-		if (parentDirectory && parentDirectory !== directory) {
-			this.parentDirectoryRegExp = createRegExpForPath(parentDirectory);
-		}
-
-		if (__dirname.length >= 2) {
-			const buildins = normalizeBackSlashDirection(path.join(__dirname, ".."));
-			const buildinsAsModule =
-				this.currentDirectoryRegExp &&
-				this.currentDirectoryRegExp.test(buildins);
-			this.buildinsAsModule = buildinsAsModule;
-			this.buildinsRegExp = createRegExpForPath(buildins);
-		}
-
-		this.cache = new Map();
+	/**
+	 * Binds a context-aware shortening function to the provided directory and
+	 * optional cache owner.
+	 * @param {string} dir the directory
+	 * @param {AssociatedObjectForCache=} associatedObjectForCache an object to which the cache will be attached
+	 */
+	constructor(dir, associatedObjectForCache) {
+		this.contextify = contextify.bindContextCache(
+			dir,
+			associatedObjectForCache
+		);
 	}
 
+	/**
+	 * Returns a request string rewritten relative to the configured directory
+	 * when one is provided.
+	 * @param {string | undefined | null} request the request to shorten
+	 * @returns {string | undefined | null} the shortened request
+	 */
 	shorten(request) {
-		if (!request) return request;
-		const cacheEntry = this.cache.get(request);
-		if (cacheEntry !== undefined) {
-			return cacheEntry;
-		}
-		let result = normalizeBackSlashDirection(request);
-		if (this.buildinsAsModule && this.buildinsRegExp) {
-			result = result.replace(this.buildinsRegExp, "!(webpack)");
-		}
-		if (this.currentDirectoryRegExp) {
-			result = result.replace(this.currentDirectoryRegExp, "!.");
-		}
-		if (this.parentDirectoryRegExp) {
-			result = result.replace(this.parentDirectoryRegExp, "!..");
-		}
-		if (!this.buildinsAsModule && this.buildinsRegExp) {
-			result = result.replace(this.buildinsRegExp, "!(webpack)");
+		if (!request) {
+			return request;
 		}
-		result = result.replace(INDEX_JS_REGEXP, "$1");
-		result = result.replace(FRONT_OR_BACK_BANG_REGEXP, "");
-		result = result.replace(MATCH_RESOURCE_REGEXP, " = ");
-		this.cache.set(request, result);
-		return result;
+		return this.contextify(request);
 	}
 }
 
diff --git a/lib/RequireJsStuffPlugin.js b/lib/RequireJsStuffPlugin.js
deleted file mode 100644
index 84793c8a997..00000000000
--- a/lib/RequireJsStuffPlugin.js
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const ParserHelpers = require("./ParserHelpers");
-const ConstDependency = require("./dependencies/ConstDependency");
-const NullFactory = require("./NullFactory");
-
-module.exports = class RequireJsStuffPlugin {
-	apply(compiler) {
-		compiler.hooks.compilation.tap(
-			"RequireJsStuffPlugin",
-			(compilation, { normalModuleFactory }) => {
-				compilation.dependencyFactories.set(ConstDependency, new NullFactory());
-				compilation.dependencyTemplates.set(
-					ConstDependency,
-					new ConstDependency.Template()
-				);
-				const handler = (parser, parserOptions) => {
-					if (
-						typeof parserOptions.requireJs !== "undefined" &&
-						!parserOptions.requireJs
-					)
-						return;
-
-					parser.hooks.call
-						.for("require.config")
-						.tap(
-							"RequireJsStuffPlugin",
-							ParserHelpers.toConstantDependency(parser, "undefined")
-						);
-					parser.hooks.call
-						.for("requirejs.config")
-						.tap(
-							"RequireJsStuffPlugin",
-							ParserHelpers.toConstantDependency(parser, "undefined")
-						);
-
-					parser.hooks.expression
-						.for("require.version")
-						.tap(
-							"RequireJsStuffPlugin",
-							ParserHelpers.toConstantDependency(
-								parser,
-								JSON.stringify("0.0.0")
-							)
-						);
-					parser.hooks.expression
-						.for("requirejs.onError")
-						.tap(
-							"RequireJsStuffPlugin",
-							ParserHelpers.toConstantDependencyWithWebpackRequire(
-								parser,
-								"__webpack_require__.oe"
-							)
-						);
-				};
-				normalModuleFactory.hooks.parser
-					.for("javascript/auto")
-					.tap("RequireJsStuffPlugin", handler);
-				normalModuleFactory.hooks.parser
-					.for("javascript/dynamic")
-					.tap("RequireJsStuffPlugin", handler);
-			}
-		);
-	}
-};
diff --git a/lib/ResolverFactory.js b/lib/ResolverFactory.js
index 47028ee7ffe..51c1243bb8c 100644
--- a/lib/ResolverFactory.js
+++ b/lib/ResolverFactory.js
@@ -1,64 +1,161 @@
 /*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Tobias Koppers @sokra
- */
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
 "use strict";
 
-const { Tapable, HookMap, SyncHook, SyncWaterfallHook } = require("tapable");
 const Factory = require("enhanced-resolve").ResolverFactory;
+const { HookMap, SyncHook, SyncWaterfallHook } = require("tapable");
+const {
+	cachedCleverMerge,
+	removeOperations,
+	resolveByProperty
+} = require("./util/cleverMerge");
+
+/** @typedef {import("enhanced-resolve").ResolveOptions} ResolveOptions */
+/** @typedef {import("enhanced-resolve").Resolver} Resolver */
+/** @typedef {import("../declarations/WebpackOptions").ResolveOptions} WebpackResolveOptions */
+/** @typedef {import("../declarations/WebpackOptions").ResolvePluginInstance} ResolvePluginInstance */
+
+/** @typedef {WebpackResolveOptions & { dependencyType?: string, resolveToContext?: boolean }} ResolveOptionsWithDependencyType */
+/**
+ * Defines the with options type used by this module.
+ * @typedef {object} WithOptions
+ * @property {(options: Partial) => ResolverWithOptions} withOptions create a resolver with additional/different options
+ */
+
+/** @typedef {Resolver & WithOptions} ResolverWithOptions */
+
+// need to be hoisted on module level for caching identity
+/** @type {ResolveOptionsWithDependencyType} */
+const EMPTY_RESOLVE_OPTIONS = {};
+
+/**
+ * Convert to resolve options.
+ * @param {ResolveOptionsWithDependencyType} resolveOptionsWithDepType enhanced options
+ * @returns {ResolveOptions} merged options
+ */
+const convertToResolveOptions = (resolveOptionsWithDepType) => {
+	const { dependencyType, plugins, ...remaining } = resolveOptionsWithDepType;
+
+	// check type compat
+	/** @type {Partial} */
+	const partialOptions = {
+		...remaining,
+		plugins:
+			plugins &&
+			/** @type {ResolvePluginInstance[]} */ (
+				plugins.filter((item) => item !== "...")
+			)
+	};
+
+	if (!partialOptions.fileSystem) {
+		throw new Error(
+			"fileSystem is missing in resolveOptions, but it's required for enhanced-resolve"
+		);
+	}
+	// These weird types validate that we checked all non-optional properties
+	const options =
+		/** @type {Partial & Pick} */ (
+			partialOptions
+		);
+
+	return /** @type {ResolveOptions} */ (
+		removeOperations(
+			resolveByProperty(options, "byDependency", dependencyType),
+			// Keep the `unsafeCache` because it can be a `Proxy`
+			["unsafeCache"]
+		)
+	);
+};
 
-module.exports = class ResolverFactory extends Tapable {
+/**
+ * Represents the resolver factory runtime component.
+ * @typedef {object} ResolverCache
+ * @property {WeakMap} direct
+ * @property {Map} stringified
+ */
+
+module.exports = class ResolverFactory {
 	constructor() {
-		super();
-		this.hooks = {
+		this.hooks = Object.freeze({
+			/** @type {HookMap>} */
 			resolveOptions: new HookMap(
 				() => new SyncWaterfallHook(["resolveOptions"])
 			),
-			resolver: new HookMap(() => new SyncHook(["resolver", "resolveOptions"]))
-		};
-		this._pluginCompat.tap("ResolverFactory", options => {
-			let match;
-			match = /^resolve-options (.+)$/.exec(options.name);
-			if (match) {
-				this.hooks.resolveOptions.tap(
-					match[1],
-					options.fn.name || "unnamed compat plugin",
-					options.fn
-				);
-				return true;
-			}
-			match = /^resolver (.+)$/.exec(options.name);
-			if (match) {
-				this.hooks.resolver.tap(
-					match[1],
-					options.fn.name || "unnamed compat plugin",
-					options.fn
-				);
-				return true;
-			}
+			/** @type {HookMap>} */
+			resolver: new HookMap(
+				() => new SyncHook(["resolver", "resolveOptions", "userResolveOptions"])
+			)
 		});
-		this.cache1 = new WeakMap();
-		this.cache2 = new Map();
+		/** @type {Map} */
+		this.cache = new Map();
 	}
 
-	get(type, resolveOptions) {
-		const cachedResolver = this.cache1.get(resolveOptions);
-		if (cachedResolver) return cachedResolver();
-		const ident = `${type}|${JSON.stringify(resolveOptions)}`;
-		const resolver = this.cache2.get(ident);
-		if (resolver) return resolver;
+	/**
+	 * Returns the resolver.
+	 * @param {string} type type of resolver
+	 * @param {ResolveOptionsWithDependencyType=} resolveOptions options
+	 * @returns {ResolverWithOptions} the resolver
+	 */
+	get(type, resolveOptions = EMPTY_RESOLVE_OPTIONS) {
+		let typedCaches = this.cache.get(type);
+		if (!typedCaches) {
+			typedCaches = {
+				direct: new WeakMap(),
+				stringified: new Map()
+			};
+			this.cache.set(type, typedCaches);
+		}
+		const cachedResolver = typedCaches.direct.get(resolveOptions);
+		if (cachedResolver) {
+			return cachedResolver;
+		}
+		const ident = JSON.stringify(resolveOptions);
+		const resolver = typedCaches.stringified.get(ident);
+		if (resolver) {
+			typedCaches.direct.set(resolveOptions, resolver);
+			return resolver;
+		}
 		const newResolver = this._create(type, resolveOptions);
-		this.cache2.set(ident, newResolver);
+		typedCaches.direct.set(resolveOptions, newResolver);
+		typedCaches.stringified.set(ident, newResolver);
 		return newResolver;
 	}
 
-	_create(type, resolveOptions) {
-		resolveOptions = this.hooks.resolveOptions.for(type).call(resolveOptions);
-		const resolver = Factory.createResolver(resolveOptions);
+	/**
+	 * Returns the resolver.
+	 * @param {string} type type of resolver
+	 * @param {ResolveOptionsWithDependencyType} resolveOptionsWithDepType options
+	 * @returns {ResolverWithOptions} the resolver
+	 */
+	_create(type, resolveOptionsWithDepType) {
+		/** @type {ResolveOptionsWithDependencyType} */
+		const originalResolveOptions = { ...resolveOptionsWithDepType };
+
+		const resolveOptions = convertToResolveOptions(
+			this.hooks.resolveOptions.for(type).call(resolveOptionsWithDepType)
+		);
+		const resolver = /** @type {ResolverWithOptions} */ (
+			Factory.createResolver(resolveOptions)
+		);
 		if (!resolver) {
 			throw new Error("No resolver created");
 		}
-		this.hooks.resolver.for(type).call(resolver, resolveOptions);
+		/** @type {WeakMap, ResolverWithOptions>} */
+		const childCache = new WeakMap();
+		resolver.withOptions = (options) => {
+			const cacheEntry = childCache.get(options);
+			if (cacheEntry !== undefined) return cacheEntry;
+			const mergedOptions = cachedCleverMerge(originalResolveOptions, options);
+			const resolver = this.get(type, mergedOptions);
+			childCache.set(options, resolver);
+			return resolver;
+		};
+		this.hooks.resolver
+			.for(type)
+			.call(resolver, resolveOptions, originalResolveOptions);
 		return resolver;
 	}
 };
diff --git a/lib/RuleSet.js b/lib/RuleSet.js
deleted file mode 100644
index abc44c1a9b4..00000000000
--- a/lib/RuleSet.js
+++ /dev/null
@@ -1,567 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-/*
-: 
-: []
-: {
-	resource: {
-		test: ,
-		include: ,
-		exclude: ,
-	},
-	resource: , -> resource.test
-	test: , -> resource.test
-	include: , -> resource.include
-	exclude: , -> resource.exclude
-	resourceQuery: ,
-	compiler: ,
-	issuer: ,
-	use: "loader", -> use[0].loader
-	loader: <>, -> use[0].loader
-	loaders: <>, -> use
-	options: {}, -> use[0].options,
-	query: {}, -> options
-	parser: {},
-	use: [
-		"loader" -> use[x].loader
-	],
-	use: [
-		{
-			loader: "loader",
-			options: {}
-		}
-	],
-	rules: [
-		
-	],
-	oneOf: [
-		
-	]
-}
-
-: /regExp/
-: function(arg) {}
-: "starting"
-: [] // or
-: { and: [] }
-: { or: [] }
-: { not: [] }
-: { test: , include: , exclude:  }
-
-
-normalized:
-
-{
-	resource: function(),
-	resourceQuery: function(),
-	compiler: function(),
-	issuer: function(),
-	use: [
-		{
-			loader: string,
-			options: string,
-			: 
-		}
-	],
-	rules: [],
-	oneOf: [],
-	: ,
-}
-
-*/
-
-"use strict";
-
-const notMatcher = matcher => {
-	return function(str) {
-		return !matcher(str);
-	};
-};
-
-const orMatcher = items => {
-	return function(str) {
-		for (let i = 0; i < items.length; i++) {
-			if (items[i](str)) return true;
-		}
-		return false;
-	};
-};
-
-const andMatcher = items => {
-	return function(str) {
-		for (let i = 0; i < items.length; i++) {
-			if (!items[i](str)) return false;
-		}
-		return true;
-	};
-};
-
-module.exports = class RuleSet {
-	constructor(rules) {
-		this.references = Object.create(null);
-		this.rules = RuleSet.normalizeRules(rules, this.references, "ref-");
-	}
-
-	static normalizeRules(rules, refs, ident) {
-		if (Array.isArray(rules)) {
-			return rules.map((rule, idx) => {
-				return RuleSet.normalizeRule(rule, refs, `${ident}-${idx}`);
-			});
-		} else if (rules) {
-			return [RuleSet.normalizeRule(rules, refs, ident)];
-		} else {
-			return [];
-		}
-	}
-
-	static normalizeRule(rule, refs, ident) {
-		if (typeof rule === "string") {
-			return {
-				use: [
-					{
-						loader: rule
-					}
-				]
-			};
-		}
-		if (!rule) {
-			throw new Error("Unexcepted null when object was expected as rule");
-		}
-		if (typeof rule !== "object") {
-			throw new Error(
-				"Unexcepted " +
-					typeof rule +
-					" when object was expected as rule (" +
-					rule +
-					")"
-			);
-		}
-
-		const newRule = {};
-		let useSource;
-		let resourceSource;
-		let condition;
-
-		const checkUseSource = newSource => {
-			if (useSource && useSource !== newSource) {
-				throw new Error(
-					RuleSet.buildErrorMessage(
-						rule,
-						new Error(
-							"Rule can only have one result source (provided " +
-								newSource +
-								" and " +
-								useSource +
-								")"
-						)
-					)
-				);
-			}
-			useSource = newSource;
-		};
-
-		const checkResourceSource = newSource => {
-			if (resourceSource && resourceSource !== newSource) {
-				throw new Error(
-					RuleSet.buildErrorMessage(
-						rule,
-						new Error(
-							"Rule can only have one resource source (provided " +
-								newSource +
-								" and " +
-								resourceSource +
-								")"
-						)
-					)
-				);
-			}
-			resourceSource = newSource;
-		};
-
-		if (rule.test || rule.include || rule.exclude) {
-			checkResourceSource("test + include + exclude");
-			condition = {
-				test: rule.test,
-				include: rule.include,
-				exclude: rule.exclude
-			};
-			try {
-				newRule.resource = RuleSet.normalizeCondition(condition);
-			} catch (error) {
-				throw new Error(RuleSet.buildErrorMessage(condition, error));
-			}
-		}
-
-		if (rule.resource) {
-			checkResourceSource("resource");
-			try {
-				newRule.resource = RuleSet.normalizeCondition(rule.resource);
-			} catch (error) {
-				throw new Error(RuleSet.buildErrorMessage(rule.resource, error));
-			}
-		}
-
-		if (rule.realResource) {
-			try {
-				newRule.realResource = RuleSet.normalizeCondition(rule.realResource);
-			} catch (error) {
-				throw new Error(RuleSet.buildErrorMessage(rule.realResource, error));
-			}
-		}
-
-		if (rule.resourceQuery) {
-			try {
-				newRule.resourceQuery = RuleSet.normalizeCondition(rule.resourceQuery);
-			} catch (error) {
-				throw new Error(RuleSet.buildErrorMessage(rule.resourceQuery, error));
-			}
-		}
-
-		if (rule.compiler) {
-			try {
-				newRule.compiler = RuleSet.normalizeCondition(rule.compiler);
-			} catch (error) {
-				throw new Error(RuleSet.buildErrorMessage(rule.compiler, error));
-			}
-		}
-
-		if (rule.issuer) {
-			try {
-				newRule.issuer = RuleSet.normalizeCondition(rule.issuer);
-			} catch (error) {
-				throw new Error(RuleSet.buildErrorMessage(rule.issuer, error));
-			}
-		}
-
-		if (rule.loader && rule.loaders) {
-			throw new Error(
-				RuleSet.buildErrorMessage(
-					rule,
-					new Error(
-						"Provided loader and loaders for rule (use only one of them)"
-					)
-				)
-			);
-		}
-
-		const loader = rule.loaders || rule.loader;
-		if (typeof loader === "string" && !rule.options && !rule.query) {
-			checkUseSource("loader");
-			newRule.use = RuleSet.normalizeUse(loader.split("!"), ident);
-		} else if (typeof loader === "string" && (rule.options || rule.query)) {
-			checkUseSource("loader + options/query");
-			newRule.use = RuleSet.normalizeUse(
-				{
-					loader: loader,
-					options: rule.options,
-					query: rule.query
-				},
-				ident
-			);
-		} else if (loader && (rule.options || rule.query)) {
-			throw new Error(
-				RuleSet.buildErrorMessage(
-					rule,
-					new Error(
-						"options/query cannot be used with loaders (use options for each array item)"
-					)
-				)
-			);
-		} else if (loader) {
-			checkUseSource("loaders");
-			newRule.use = RuleSet.normalizeUse(loader, ident);
-		} else if (rule.options || rule.query) {
-			throw new Error(
-				RuleSet.buildErrorMessage(
-					rule,
-					new Error(
-						"options/query provided without loader (use loader + options)"
-					)
-				)
-			);
-		}
-
-		if (rule.use) {
-			checkUseSource("use");
-			newRule.use = RuleSet.normalizeUse(rule.use, ident);
-		}
-
-		if (rule.rules) {
-			newRule.rules = RuleSet.normalizeRules(
-				rule.rules,
-				refs,
-				`${ident}-rules`
-			);
-		}
-
-		if (rule.oneOf) {
-			newRule.oneOf = RuleSet.normalizeRules(
-				rule.oneOf,
-				refs,
-				`${ident}-oneOf`
-			);
-		}
-
-		const keys = Object.keys(rule).filter(key => {
-			return ![
-				"resource",
-				"resourceQuery",
-				"compiler",
-				"test",
-				"include",
-				"exclude",
-				"issuer",
-				"loader",
-				"options",
-				"query",
-				"loaders",
-				"use",
-				"rules",
-				"oneOf"
-			].includes(key);
-		});
-		for (const key of keys) {
-			newRule[key] = rule[key];
-		}
-
-		if (Array.isArray(newRule.use)) {
-			for (const item of newRule.use) {
-				if (item.ident) {
-					refs[item.ident] = item.options;
-				}
-			}
-		}
-
-		return newRule;
-	}
-
-	static buildErrorMessage(condition, error) {
-		const conditionAsText = JSON.stringify(
-			condition,
-			(key, value) => {
-				return value === undefined ? "undefined" : value;
-			},
-			2
-		);
-		return error.message + " in " + conditionAsText;
-	}
-
-	static normalizeUse(use, ident) {
-		if (typeof use === "function") {
-			return data => RuleSet.normalizeUse(use(data), ident);
-		}
-		if (Array.isArray(use)) {
-			return use
-				.map((item, idx) => RuleSet.normalizeUse(item, `${ident}-${idx}`))
-				.reduce((arr, items) => arr.concat(items), []);
-		}
-		return [RuleSet.normalizeUseItem(use, ident)];
-	}
-
-	static normalizeUseItemString(useItemString) {
-		const idx = useItemString.indexOf("?");
-		if (idx >= 0) {
-			return {
-				loader: useItemString.substr(0, idx),
-				options: useItemString.substr(idx + 1)
-			};
-		}
-		return {
-			loader: useItemString,
-			options: undefined
-		};
-	}
-
-	static normalizeUseItem(item, ident) {
-		if (typeof item === "string") {
-			return RuleSet.normalizeUseItemString(item);
-		}
-
-		const newItem = {};
-
-		if (item.options && item.query) {
-			throw new Error("Provided options and query in use");
-		}
-
-		if (!item.loader) {
-			throw new Error("No loader specified");
-		}
-
-		newItem.options = item.options || item.query;
-
-		if (typeof newItem.options === "object" && newItem.options) {
-			if (newItem.options.ident) {
-				newItem.ident = newItem.options.ident;
-			} else {
-				newItem.ident = ident;
-			}
-		}
-
-		const keys = Object.keys(item).filter(function(key) {
-			return !["options", "query"].includes(key);
-		});
-
-		for (const key of keys) {
-			newItem[key] = item[key];
-		}
-
-		return newItem;
-	}
-
-	static normalizeCondition(condition) {
-		if (!condition) throw new Error("Expected condition but got falsy value");
-		if (typeof condition === "string") {
-			return str => str.indexOf(condition) === 0;
-		}
-		if (typeof condition === "function") {
-			return condition;
-		}
-		if (condition instanceof RegExp) {
-			return condition.test.bind(condition);
-		}
-		if (Array.isArray(condition)) {
-			const items = condition.map(c => RuleSet.normalizeCondition(c));
-			return orMatcher(items);
-		}
-		if (typeof condition !== "object") {
-			throw Error(
-				"Unexcepted " +
-					typeof condition +
-					" when condition was expected (" +
-					condition +
-					")"
-			);
-		}
-
-		const matchers = [];
-		Object.keys(condition).forEach(key => {
-			const value = condition[key];
-			switch (key) {
-				case "or":
-				case "include":
-				case "test":
-					if (value) matchers.push(RuleSet.normalizeCondition(value));
-					break;
-				case "and":
-					if (value) {
-						const items = value.map(c => RuleSet.normalizeCondition(c));
-						matchers.push(andMatcher(items));
-					}
-					break;
-				case "not":
-				case "exclude":
-					if (value) {
-						const matcher = RuleSet.normalizeCondition(value);
-						matchers.push(notMatcher(matcher));
-					}
-					break;
-				default:
-					throw new Error("Unexcepted property " + key + " in condition");
-			}
-		});
-		if (matchers.length === 0) {
-			throw new Error("Excepted condition but got " + condition);
-		}
-		if (matchers.length === 1) {
-			return matchers[0];
-		}
-		return andMatcher(matchers);
-	}
-
-	exec(data) {
-		const result = [];
-		this._run(
-			data,
-			{
-				rules: this.rules
-			},
-			result
-		);
-		return result;
-	}
-
-	_run(data, rule, result) {
-		// test conditions
-		if (rule.resource && !data.resource) return false;
-		if (rule.realResource && !data.realResource) return false;
-		if (rule.resourceQuery && !data.resourceQuery) return false;
-		if (rule.compiler && !data.compiler) return false;
-		if (rule.issuer && !data.issuer) return false;
-		if (rule.resource && !rule.resource(data.resource)) return false;
-		if (rule.realResource && !rule.realResource(data.realResource))
-			return false;
-		if (data.issuer && rule.issuer && !rule.issuer(data.issuer)) return false;
-		if (
-			data.resourceQuery &&
-			rule.resourceQuery &&
-			!rule.resourceQuery(data.resourceQuery)
-		) {
-			return false;
-		}
-		if (data.compiler && rule.compiler && !rule.compiler(data.compiler)) {
-			return false;
-		}
-
-		// apply
-		const keys = Object.keys(rule).filter(key => {
-			return ![
-				"resource",
-				"realResource",
-				"resourceQuery",
-				"compiler",
-				"issuer",
-				"rules",
-				"oneOf",
-				"use",
-				"enforce"
-			].includes(key);
-		});
-		for (const key of keys) {
-			result.push({
-				type: key,
-				value: rule[key]
-			});
-		}
-
-		if (rule.use) {
-			const process = use => {
-				if (typeof use === "function") {
-					process(use(data));
-				} else if (Array.isArray(use)) {
-					use.forEach(process);
-				} else {
-					result.push({
-						type: "use",
-						value: use,
-						enforce: rule.enforce
-					});
-				}
-			};
-			process(rule.use);
-		}
-
-		if (rule.rules) {
-			for (let i = 0; i < rule.rules.length; i++) {
-				this._run(data, rule.rules[i], result);
-			}
-		}
-
-		if (rule.oneOf) {
-			for (let i = 0; i < rule.oneOf.length; i++) {
-				if (this._run(data, rule.oneOf[i], result)) break;
-			}
-		}
-
-		return true;
-	}
-
-	findOptionsByIdent(ident) {
-		const options = this.references[ident];
-		if (!options) {
-			throw new Error("Can't find options with ident '" + ident + "'");
-		}
-		return options;
-	}
-};
diff --git a/lib/RuntimeGlobals.js b/lib/RuntimeGlobals.js
new file mode 100644
index 00000000000..e8138c59328
--- /dev/null
+++ b/lib/RuntimeGlobals.js
@@ -0,0 +1,521 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+/**
+ * the AMD define function
+ */
+module.exports.amdDefine = "__webpack_require__.amdD";
+
+/**
+ * the AMD options
+ */
+module.exports.amdOptions = "__webpack_require__.amdO";
+
+/**
+ * analyzable single-chunk `import()` that keeps `ensureChunk` timing and deduplication:
+ * resolves immediately for an installed chunk, else runs the literal import and installs it
+ * Signature: (chunkId, () => Promise) => Promise
+ */
+module.exports.analyzableChunkImport = "__webpack_require__.ei";
+
+/**
+ * Creates an async module. The body function must be a async function.
+ * "module.exports" will be decorated with an AsyncModulePromise.
+ * The body function will be called.
+ * To handle async dependencies correctly do this: "([a, b, c] = await handleDependencies([a, b, c]));".
+ * If "hasAwaitAfterDependencies" is truthy, "handleDependencies()" must be called at the end of the body function.
+ * Signature: function(
+ * module: Module,
+ * body: (handleDependencies: (deps: AsyncModulePromise[]) => Promise & () => void,
+ * hasAwaitAfterDependencies?: boolean
+ * ) => void
+ */
+module.exports.asyncModule = "__webpack_require__.a";
+
+/**
+ * The internal symbol that asyncModule is using.
+ */
+module.exports.asyncModuleDoneSymbol = "__webpack_require__.aD";
+
+/**
+ * The internal symbol that asyncModule is using.
+ */
+module.exports.asyncModuleExportSymbol = "__webpack_require__.aE";
+
+/**
+ * Drives an async module body written as a generator function (used when the
+ * target environment supports generators but not `async`/`await`).
+ * It runs the generator, resolving each `yield`ed dependency promise, so the
+ * body reads like a plain `async` function without emitting `async`/`await`.
+ * Signature: function(body: GeneratorFunction) => (handleDependencies, result) => void
+ */
+module.exports.asyncModuleGenerator = "__webpack_require__.aG";
+
+/**
+ * the baseURI of current document
+ */
+module.exports.baseURI = "__webpack_require__.b";
+
+/**
+ * global callback functions for installing chunks
+ */
+module.exports.chunkCallback = "webpackChunk";
+
+/**
+ * the chunk name of the chunk with the runtime
+ */
+module.exports.chunkName = "__webpack_require__.cn";
+
+/**
+ * compatibility get default export
+ */
+module.exports.compatGetDefaultExport = "__webpack_require__.n";
+
+/**
+ * compile a wasm module from id and hash, returning WebAssembly.Module
+ */
+module.exports.compileWasm = "__webpack_require__.vs";
+
+/**
+ * wrap a concatenated module body as a lazy accessor, returning its exports
+ */
+module.exports.concatenationWrap = "__webpack_require__.cw";
+
+/**
+ * apply the `new` return-value rule to a concatenated module's exports
+ */
+module.exports.constructRequire = "__webpack_require__.cr";
+
+/**
+ * create a fake namespace object
+ */
+module.exports.createFakeNamespaceObject = "__webpack_require__.t";
+
+/**
+ * function to promote a string to a TrustedScript using webpack's Trusted
+ * Types policy
+ * Arguments: (script: string) => TrustedScript
+ */
+module.exports.createScript = "__webpack_require__.ts";
+
+/**
+ * function to promote a string to a TrustedScriptURL using webpack's Trusted
+ * Types policy
+ * Arguments: (url: string) => TrustedScriptURL
+ */
+module.exports.createScriptUrl = "__webpack_require__.tu";
+
+module.exports.cssInjectStyle = "__webpack_require__.is";
+
+/**
+ * The current scope when getting a module from a remote
+ */
+module.exports.currentRemoteGetScope = "__webpack_require__.R";
+
+/**
+ * resolve async transitive dependencies for deferred module
+ */
+module.exports.deferredModuleAsyncTransitiveDependencies =
+	"__webpack_require__.zT";
+
+/**
+ * the internal symbol for getting the async transitive dependencies for deferred module
+ */
+module.exports.deferredModuleAsyncTransitiveDependenciesSymbol =
+	"__webpack_require__.zS";
+
+/**
+ * the exported property define getters function
+ */
+module.exports.definePropertyGetters = "__webpack_require__.d";
+
+/**
+ * the chunk ensure function
+ */
+module.exports.ensureChunk = "__webpack_require__.e";
+
+/**
+ * an object with handlers to ensure a chunk
+ */
+module.exports.ensureChunkHandlers = "__webpack_require__.f";
+
+/**
+ * a runtime requirement if ensureChunkHandlers should include loading of chunk needed for entries
+ */
+module.exports.ensureChunkIncludeEntries =
+	"__webpack_require__.f (include entries)";
+
+/**
+ * the module id of the entry point
+ */
+module.exports.entryModuleId = "__webpack_require__.s";
+
+/**
+ * esm module id
+ */
+module.exports.esmId = "__webpack_esm_id__";
+
+/**
+ * esm module ids
+ */
+module.exports.esmIds = "__webpack_esm_ids__";
+
+/**
+ * esm modules
+ */
+module.exports.esmModules = "__webpack_esm_modules__";
+
+/**
+ * esm runtime
+ */
+module.exports.esmRuntime = "__webpack_esm_runtime__";
+
+/**
+ * the internal exports object
+ */
+module.exports.exports = "__webpack_exports__";
+
+/**
+ * method to install a chunk that was loaded somehow
+ * Signature: ({ id, ids, modules, runtime }) => void
+ */
+module.exports.externalInstallChunk = "__webpack_require__.C";
+
+/**
+ * the filename of the css part of the chunk
+ */
+module.exports.getChunkCssFilename = "__webpack_require__.k";
+
+/**
+ * the filename of the script part of the chunk
+ */
+module.exports.getChunkScriptFilename = "__webpack_require__.u";
+
+/**
+ * the filename of the css part of the hot update chunk
+ */
+module.exports.getChunkUpdateCssFilename = "__webpack_require__.hk";
+
+/**
+ * the filename of the script part of the hot update chunk
+ */
+module.exports.getChunkUpdateScriptFilename = "__webpack_require__.hu";
+
+/**
+ * function returning the CSS collected while rendering without a DOM as a
+ * single string; empty when the styles went into the document instead
+ * Arguments: () => string
+ */
+module.exports.getCssServerStyles = "__webpack_require__.cs";
+
+/**
+ * the webpack hash
+ */
+module.exports.getFullHash = "__webpack_require__.h";
+
+/**
+ * function to return webpack's Trusted Types policy
+ * Arguments: () => TrustedTypePolicy
+ */
+module.exports.getTrustedTypesPolicy = "__webpack_require__.tt";
+
+/**
+ * the filename of the HMR manifest
+ */
+module.exports.getUpdateManifestFilename = "__webpack_require__.hmrF";
+
+/**
+ * the object url of the worklet bootstrap script (sets up `self`, `location` and
+ * a throwing `importScripts` before worklet chunks are added)
+ */
+module.exports.getWorkletBootstrap = "__webpack_require__.wb";
+
+/**
+ * the global object
+ */
+module.exports.global = "__webpack_require__.g";
+
+/**
+ * harmony module decorator
+ */
+module.exports.harmonyModuleDecorator = "__webpack_require__.hmd";
+
+/**
+ * a flag when a module/chunk/tree has css modules
+ */
+module.exports.hasCssModules = "has css modules";
+
+/**
+ * a flag when a chunk has a fetch priority
+ */
+module.exports.hasFetchPriority = "has fetch priority";
+
+/**
+ * the shorthand for Object.prototype.hasOwnProperty
+ * using of it decreases the compiled bundle size
+ */
+module.exports.hasOwnProperty = "__webpack_require__.o";
+
+/**
+ * function downloading the update manifest
+ */
+module.exports.hmrDownloadManifest = "__webpack_require__.hmrM";
+
+/**
+ * array with handler functions to download chunk updates
+ */
+module.exports.hmrDownloadUpdateHandlers = "__webpack_require__.hmrC";
+
+/**
+ * array with handler functions when a module should be invalidated
+ */
+module.exports.hmrInvalidateModuleHandlers = "__webpack_require__.hmrI";
+
+/**
+ * object with all hmr module data for all modules
+ */
+module.exports.hmrModuleData = "__webpack_require__.hmrD";
+
+/**
+ * the prefix for storing state of runtime modules when hmr is enabled
+ */
+module.exports.hmrRuntimeStatePrefix = "__webpack_require__.hmrS";
+
+/**
+ * The sharing init sequence function (only runs once per share scope).
+ * Has one argument, the name of the share scope.
+ * Creates a share scope if not existing
+ */
+module.exports.initializeSharing = "__webpack_require__.I";
+
+/**
+ * instantiate a wasm instance from module exports object, id, hash and importsObject
+ */
+module.exports.instantiateWasm = "__webpack_require__.v";
+
+/**
+ * interceptor for module executions
+ */
+module.exports.interceptModuleExecution = "__webpack_require__.i";
+
+/**
+ * function to load a script tag.
+ * Arguments: (url: string, done: (event) => void), key?: string | number, chunkId?: string | number) => void
+ * done function is called when loading has finished or timeout occurred.
+ * It will attach to existing script tags with data-webpack == uniqueName + ":" + key or src == url.
+ */
+module.exports.loadScript = "__webpack_require__.l";
+
+/**
+ * make a deferred namespace object
+ */
+module.exports.makeDeferredNamespaceObject = "__webpack_require__.z";
+
+/**
+ * define compatibility on export
+ */
+module.exports.makeNamespaceObject = "__webpack_require__.r";
+
+/**
+ * make a optimized deferred namespace object
+ */
+module.exports.makeOptimizedDeferredNamespaceObject = "__webpack_require__.zO";
+
+/**
+ * the internal module object
+ */
+module.exports.module = "module";
+
+/**
+ * the module cache
+ */
+module.exports.moduleCache = "__webpack_require__.c";
+
+/**
+ * the module functions
+ */
+module.exports.moduleFactories = "__webpack_require__.m";
+
+/**
+ * the module functions, with only write access
+ */
+module.exports.moduleFactoriesAddOnly = "__webpack_require__.m (add only)";
+
+/**
+ * the internal module object
+ */
+module.exports.moduleId = "module.id";
+
+/**
+ * the internal module object
+ */
+module.exports.moduleLoaded = "module.loaded";
+
+/**
+ * node.js module decorator
+ */
+module.exports.nodeModuleDecorator = "__webpack_require__.nmd";
+
+/**
+ * register deferred code, which will run when certain
+ * chunks are loaded.
+ * Signature: (chunkIds: Id[], fn: () => any, priority: int >= 0 = 0) => any
+ * Returned value will be returned directly when all chunks are already loaded
+ * When (priority & 1) it will wait for all other handlers with lower priority to
+ * be executed before itself is executed
+ */
+module.exports.onChunksLoaded = "__webpack_require__.O";
+
+/**
+ * the asset prefetch function: injects a
+ * `` for an asset module URL
+ */
+module.exports.prefetchAsset = "__webpack_require__.PA";
+
+/**
+ * the chunk prefetch function
+ */
+module.exports.prefetchChunk = "__webpack_require__.E";
+
+/**
+ * an object with handlers to prefetch a chunk
+ */
+module.exports.prefetchChunkHandlers = "__webpack_require__.F";
+
+/**
+ * the asset preload function: injects a
+ * `` for an asset module URL
+ */
+module.exports.preloadAsset = "__webpack_require__.LA";
+
+/**
+ * the chunk preload function
+ */
+module.exports.preloadChunk = "__webpack_require__.G";
+
+/**
+ * an object with handlers to preload a chunk
+ */
+module.exports.preloadChunkHandlers = "__webpack_require__.H";
+
+/**
+ * the bundle public path
+ */
+module.exports.publicPath = "__webpack_require__.p";
+
+/**
+ * a RelativeURL class when relative URLs are used
+ */
+module.exports.relativeUrl = "__webpack_require__.U";
+
+/**
+ * the internal require function
+ */
+module.exports.require = "__webpack_require__";
+
+/**
+ * access to properties of the internal require function/object
+ */
+module.exports.requireScope = "__webpack_require__.*";
+
+/**
+ * runtime need to return the exports of the last entry module
+ */
+module.exports.returnExportsFromRuntime = "return-exports-from-runtime";
+
+/**
+ * the runtime id of the current runtime
+ */
+module.exports.runtimeId = "__webpack_require__.j";
+
+/**
+ * the script nonce
+ */
+module.exports.scriptNonce = "__webpack_require__.nc";
+
+/**
+ * set .name to "default" for anonymous default exports per ES spec
+ */
+module.exports.setAnonymousDefaultName = "__webpack_require__.dn";
+
+/**
+ * an object with all share scopes
+ */
+module.exports.shareScopeMap = "__webpack_require__.S";
+
+/**
+ * startup signal from runtime
+ * This will be called when the runtime chunk has been loaded.
+ */
+module.exports.startup = "__webpack_require__.x";
+
+/**
+ * marker requirement that adds the startup runtime module which fires
+ * `` for `new URL(..., import.meta.url)`
+ * assets with `webpackPrefetch` / `webpackPreload` hints, before any
+ * chunk module evaluates
+ */
+module.exports.startupAssetHints = "__webpack_require__.SAH";
+
+/**
+ * method to startup an entrypoint with needed chunks.
+ * Signature: (moduleId: Id, chunkIds: Id[]) => any.
+ * Returns the exports of the module or a Promise
+ */
+module.exports.startupEntrypoint = "__webpack_require__.X";
+
+/**
+ * Describes how this item operation behaves.
+ * @deprecated
+ * creating a default startup function with the entry modules
+ */
+module.exports.startupNoDefault = "__webpack_require__.x (no default handler)";
+
+/**
+ * startup signal from runtime but only used to add logic after the startup
+ */
+module.exports.startupOnlyAfter = "__webpack_require__.x (only after)";
+
+/**
+ * startup signal from runtime but only used to add sync logic before the startup
+ */
+module.exports.startupOnlyBefore = "__webpack_require__.x (only before)";
+
+/**
+ * the System polyfill object
+ */
+module.exports.system = "__webpack_require__.System";
+
+/**
+ * the System.register context object
+ */
+module.exports.systemContext = "__webpack_require__.y";
+
+/**
+ * top-level this need to be the exports object
+ */
+module.exports.thisAsExports = "top-level-this-exports";
+
+/**
+ * to binary helper, convert base64 to Uint8Array
+ */
+module.exports.toBinary = "__webpack_require__.tb";
+
+/**
+ * the uncaught error handler for the webpack runtime
+ */
+module.exports.uncaughtErrorHandler = "__webpack_require__.oe";
+
+/**
+ * an object containing all installed WebAssembly.Instance export objects keyed by module id
+ */
+module.exports.wasmInstances = "__webpack_require__.w";
+
+/**
+ * the Worker constructor for universal targets (global Worker or worker_threads)
+ */
+module.exports.worker = "__webpack_require__.wc";
diff --git a/lib/RuntimeModule.js b/lib/RuntimeModule.js
new file mode 100644
index 00000000000..c3445bfbe9a
--- /dev/null
+++ b/lib/RuntimeModule.js
@@ -0,0 +1,259 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const { RawSource } = require("webpack-sources");
+const OriginalSource = require("webpack-sources").OriginalSource;
+const Module = require("./Module");
+const {
+	JAVASCRIPT_TYPES,
+	RUNTIME_TYPES
+} = require("./ModuleSourceTypeConstants");
+const { WEBPACK_MODULE_TYPE_RUNTIME } = require("./ModuleTypeConstants");
+
+/** @typedef {import("./config/defaults").WebpackOptionsNormalizedWithDefaults} WebpackOptions */
+/** @typedef {import("./Chunk")} Chunk */
+/** @typedef {import("./ChunkGraph")} ChunkGraph */
+/** @typedef {import("./Compilation")} Compilation */
+/** @typedef {import("./Dependency").UpdateHashContext} UpdateHashContext */
+/** @typedef {import("./Generator").SourceTypes} SourceTypes */
+/** @typedef {import("./Module").BuildMeta} BuildMeta */
+/** @typedef {import("./Module").BuildInfo} BuildInfo */
+/** @typedef {import("./Module").BuildCallback} BuildCallback */
+/** @typedef {import("./Module").CodeGenerationContext} CodeGenerationContext */
+/** @typedef {import("./Module").CodeGenerationResult} CodeGenerationResult */
+/** @typedef {import("./Module").NeedBuildCallback} NeedBuildCallback */
+/** @typedef {import("./Module").NeedBuildContext} NeedBuildContext */
+/** @typedef {import("./Module").Sources} Sources */
+/** @typedef {import("./RequestShortener")} RequestShortener */
+/** @typedef {import("./ResolverFactory").ResolverWithOptions} ResolverWithOptions */
+/** @typedef {import("./util/Hash")} Hash */
+/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
+/** @typedef {import("./Module").BasicSourceTypes} BasicSourceTypes */
+
+class RuntimeModule extends Module {
+	/**
+	 * Creates an instance of RuntimeModule.
+	 * @param {string} name a readable name
+	 * @param {number=} stage an optional stage
+	 */
+	constructor(name, stage = 0) {
+		super(WEBPACK_MODULE_TYPE_RUNTIME);
+		/** @type {string} */
+		this.name = name;
+		/** @type {number} */
+		this.stage = stage;
+		/** @type {BuildMeta} */
+		this.buildMeta = {};
+		/** @type {BuildInfo} */
+		this.buildInfo = {};
+		/** @type {Compilation | undefined} */
+		this.compilation = undefined;
+		/** @type {Chunk | undefined} */
+		this.chunk = undefined;
+		/** @type {ChunkGraph | undefined} */
+		this.chunkGraph = undefined;
+		/** @type {boolean} */
+		this.fullHash = false;
+		/** @type {boolean} */
+		this.dependentHash = false;
+		/** @type {string | undefined | null} */
+		this._cachedGeneratedCode = undefined;
+	}
+
+	/**
+	 * Processes the provided compilation.
+	 * @param {Compilation} compilation the compilation
+	 * @param {Chunk} chunk the chunk
+	 * @param {ChunkGraph} chunkGraph the chunk graph
+	 * @returns {void}
+	 */
+	attach(compilation, chunk, chunkGraph = compilation.chunkGraph) {
+		this.compilation = compilation;
+		this.chunk = chunk;
+		this.chunkGraph = chunkGraph;
+	}
+
+	/**
+	 * Returns the unique identifier used to reference this module.
+	 * @returns {string} a unique identifier of the module
+	 */
+	identifier() {
+		return `webpack/runtime/${this.name}`;
+	}
+
+	/**
+	 * Returns a human-readable identifier for this module.
+	 * @param {RequestShortener} requestShortener the request shortener
+	 * @returns {string} a user readable identifier of the module
+	 */
+	readableIdentifier(requestShortener) {
+		return `webpack/runtime/${this.name}`;
+	}
+
+	/**
+	 * Checks whether the module needs to be rebuilt for the current build state.
+	 * @param {NeedBuildContext} context context info
+	 * @param {NeedBuildCallback} callback callback function, returns true, if the module needs a rebuild
+	 * @returns {void}
+	 */
+	needBuild(context, callback) {
+		return callback(null, false);
+	}
+
+	/**
+	 * Builds the module using the provided compilation context.
+	 * @param {WebpackOptions} options webpack options
+	 * @param {Compilation} compilation the compilation
+	 * @param {ResolverWithOptions} resolver the resolver
+	 * @param {InputFileSystem} fs the file system
+	 * @param {BuildCallback} callback callback function
+	 * @returns {void}
+	 */
+	build(options, compilation, resolver, fs, callback) {
+		// do nothing
+		// should not be called as runtime modules are added later to the compilation
+		callback();
+	}
+
+	/**
+	 * Updates the hash with the data contributed by this instance.
+	 * @param {Hash} hash the hash used to track dependencies
+	 * @param {UpdateHashContext} context context
+	 * @returns {void}
+	 */
+	updateHash(hash, context) {
+		hash.update(this.name);
+		hash.update(`${this.stage}`);
+		try {
+			const code =
+				this.fullHash || this.dependentHash
+					? // Do not use getGeneratedCode here, because i. e. compilation hash might be not
+						// ready at this point. We will cache it later instead.
+						this.generate()
+					: this.getGeneratedCode();
+			if (code !== null && code !== undefined) {
+				hash.update(code);
+			}
+		} catch (err) {
+			hash.update(/** @type {Error} */ (err).message);
+		}
+		super.updateHash(hash, context);
+	}
+
+	/**
+	 * Returns the source types this module can generate.
+	 * @returns {SourceTypes} types available (do not mutate)
+	 */
+	getSourceTypes() {
+		return RUNTIME_TYPES;
+	}
+
+	/**
+	 * Basic source types are high-level categories like javascript, css, webassembly, etc.
+	 * We only have built-in knowledge about the javascript basic type here; other basic types may be
+	 * added or changed over time by generators and do not need to be handled or detected here.
+	 *
+	 * Some modules, e.g. RemoteModule, may return non-basic source types like "remote" and "share-init"
+	 * from getSourceTypes(), but their generated output is still JavaScript, i.e. their basic type is JS.
+	 * @returns {BasicSourceTypes} types available (do not mutate)
+	 */
+	getSourceBasicTypes() {
+		return JAVASCRIPT_TYPES;
+	}
+
+	/**
+	 * Generates code and runtime requirements for this module.
+	 * @param {CodeGenerationContext} context context for code generation
+	 * @returns {CodeGenerationResult} result
+	 */
+	codeGeneration(context) {
+		/** @type {Sources} */
+		const sources = new Map();
+		const generatedCode = this.getGeneratedCode();
+		if (generatedCode) {
+			sources.set(
+				WEBPACK_MODULE_TYPE_RUNTIME,
+				this.useSourceMap || this.useSimpleSourceMap
+					? new OriginalSource(generatedCode, this.identifier())
+					: new RawSource(generatedCode)
+			);
+		}
+		return {
+			sources,
+			runtimeRequirements: null
+		};
+	}
+
+	/**
+	 * Returns the estimated size for the requested source type.
+	 * @param {string=} type the source type for which the size should be estimated
+	 * @returns {number} the estimated size of the module (must be non-zero)
+	 */
+	size(type) {
+		try {
+			const source = this.getGeneratedCode();
+			return source ? source.length : 0;
+		} catch (_err) {
+			return 0;
+		}
+	}
+
+	/* istanbul ignore next */
+	/**
+	 * Generates runtime code for this runtime module.
+	 * @abstract
+	 * @returns {string | null} runtime code
+	 */
+	generate() {
+		const AbstractMethodError = require("./errors/AbstractMethodError");
+
+		throw new AbstractMethodError();
+	}
+
+	/**
+	 * Gets generated code.
+	 * @returns {string | null} runtime code
+	 */
+	getGeneratedCode() {
+		if (this._cachedGeneratedCode) {
+			return this._cachedGeneratedCode;
+		}
+		return (this._cachedGeneratedCode = this.generate());
+	}
+
+	/**
+	 * Returns true, if the runtime module should get it's own scope.
+	 * When false, `generate()` must emit complete statements ending with `;`
+	 * so a following runtime IIFE is not parsed as a call (ASI).
+	 * @returns {boolean} true, if the runtime module should get it's own scope
+	 */
+	shouldIsolate() {
+		return true;
+	}
+}
+
+/**
+ * Runtime modules without any dependencies to other runtime modules
+ */
+RuntimeModule.STAGE_NORMAL = 0;
+
+/**
+ * Runtime modules with simple dependencies on other runtime modules
+ */
+RuntimeModule.STAGE_BASIC = 5;
+
+/**
+ * Runtime modules which attach to handlers of other runtime modules
+ */
+RuntimeModule.STAGE_ATTACH = 10;
+
+/**
+ * Runtime modules which trigger actions on bootstrap
+ */
+RuntimeModule.STAGE_TRIGGER = 20;
+
+module.exports = RuntimeModule;
diff --git a/lib/RuntimePlugin.js b/lib/RuntimePlugin.js
new file mode 100644
index 00000000000..febaaed81b8
--- /dev/null
+++ b/lib/RuntimePlugin.js
@@ -0,0 +1,706 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const RuntimeGlobals = require("./RuntimeGlobals");
+const {
+	getPresentKinds,
+	usesFullHashDigest
+} = require("./TemplatedPathPlugin");
+const CssModulesPlugin = require("./css/CssModulesPlugin");
+const RuntimeRequirementsDependency = require("./dependencies/RuntimeRequirementsDependency");
+const JavascriptModulesPlugin = require("./javascript/JavascriptModulesPlugin");
+const StringXor = require("./util/StringXor");
+const lazyModule = require("./util/lazyModule");
+
+/** @typedef {import("../declarations/WebpackOptions").LibraryOptions} LibraryOptions */
+/** @typedef {import("./Chunk")} Chunk */
+/** @typedef {import("./Module").RuntimeRequirements} RuntimeRequirements */
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./RuntimeModule")} RuntimeModule */
+
+// Each runtime module below is loaded by `_attachPendingRuntimeModules`, so only
+// the builds that emit one pay for it — most reach ~10 of these 30.
+const getCssServerStylesRuntimeModule = lazyModule(() =>
+	require("./css/CssServerStylesRuntimeModule")
+);
+const getAsyncModuleGeneratorRuntimeModule = lazyModule(() =>
+	require("./runtime/AsyncModuleGeneratorRuntimeModule")
+);
+const getAsyncModuleRuntimeModule = lazyModule(() =>
+	require("./runtime/AsyncModuleRuntimeModule")
+);
+const getAutoPublicPathRuntimeModule = lazyModule(() =>
+	require("./runtime/AutoPublicPathRuntimeModule")
+);
+const getBaseUriRuntimeModule = lazyModule(() =>
+	require("./runtime/BaseUriRuntimeModule")
+);
+const getCompatGetDefaultExportRuntimeModule = lazyModule(() =>
+	require("./runtime/CompatGetDefaultExportRuntimeModule")
+);
+const getCompatRuntimeModule = lazyModule(() =>
+	require("./runtime/CompatRuntimeModule")
+);
+const getConcatenationWrapRuntimeModule = lazyModule(() =>
+	require("./runtime/ConcatenationWrapRuntimeModule")
+);
+const getConstructRequireRuntimeModule = lazyModule(() =>
+	require("./runtime/ConstructRequireRuntimeModule")
+);
+const getCreateFakeNamespaceObjectRuntimeModule = lazyModule(() =>
+	require("./runtime/CreateFakeNamespaceObjectRuntimeModule")
+);
+const getCreateScriptRuntimeModule = lazyModule(() =>
+	require("./runtime/CreateScriptRuntimeModule")
+);
+const getCreateScriptUrlRuntimeModule = lazyModule(() =>
+	require("./runtime/CreateScriptUrlRuntimeModule")
+);
+const getDefinePropertyGettersRuntimeModule = lazyModule(() =>
+	require("./runtime/DefinePropertyGettersRuntimeModule")
+);
+const getEnsureChunkRuntimeModule = lazyModule(() =>
+	require("./runtime/EnsureChunkRuntimeModule")
+);
+const getGetChunkFilenameRuntimeModule = lazyModule(() =>
+	require("./runtime/GetChunkFilenameRuntimeModule")
+);
+const getGetMainFilenameRuntimeModule = lazyModule(() =>
+	require("./runtime/GetMainFilenameRuntimeModule")
+);
+const getGetTrustedTypesPolicyRuntimeModule = lazyModule(() =>
+	require("./runtime/GetTrustedTypesPolicyRuntimeModule")
+);
+const getGetWorkletBootstrapRuntimeModule = lazyModule(() =>
+	require("./runtime/GetWorkletBootstrapRuntimeModule")
+);
+const getGlobalRuntimeModule = lazyModule(() =>
+	require("./runtime/GlobalRuntimeModule")
+);
+const getHasOwnPropertyRuntimeModule = lazyModule(() =>
+	require("./runtime/HasOwnPropertyRuntimeModule")
+);
+const getLoadScriptRuntimeModule = lazyModule(() =>
+	require("./runtime/LoadScriptRuntimeModule")
+);
+const getMakeDeferredNamespaceObjectRuntimeModule = lazyModule(
+	() =>
+		require("./runtime/MakeDeferredNamespaceObjectRuntime")
+			.MakeDeferredNamespaceObjectRuntimeModule
+);
+const getMakeOptimizedDeferredNamespaceObjectRuntimeModule = lazyModule(
+	() =>
+		require("./runtime/MakeDeferredNamespaceObjectRuntime")
+			.MakeOptimizedDeferredNamespaceObjectRuntimeModule
+);
+const getMakeNamespaceObjectRuntimeModule = lazyModule(() =>
+	require("./runtime/MakeNamespaceObjectRuntimeModule")
+);
+const getNonceRuntimeModule = lazyModule(() =>
+	require("./runtime/NonceRuntimeModule")
+);
+const getOnChunksLoadedRuntimeModule = lazyModule(() =>
+	require("./runtime/OnChunksLoadedRuntimeModule")
+);
+const getPublicPathRuntimeModule = lazyModule(() =>
+	require("./runtime/PublicPathRuntimeModule")
+);
+const getRelativeUrlRuntimeModule = lazyModule(() =>
+	require("./runtime/RelativeUrlRuntimeModule")
+);
+const getRuntimeIdRuntimeModule = lazyModule(() =>
+	require("./runtime/RuntimeIdRuntimeModule")
+);
+const getSetAnonymousDefaultNameRuntimeModule = lazyModule(() =>
+	require("./runtime/SetAnonymousDefaultNameRuntimeModule")
+);
+const getSystemContextRuntimeModule = lazyModule(() =>
+	require("./runtime/SystemContextRuntimeModule")
+);
+const getToBinaryRuntimeModule = lazyModule(() =>
+	require("./runtime/ToBinaryRuntimeModule")
+);
+const getWorkerRuntimeModule = lazyModule(() =>
+	require("./runtime/WorkerRuntimeModule")
+);
+const getShareRuntimeModule = lazyModule(() =>
+	require("./sharing/ShareRuntimeModule")
+);
+
+const GLOBALS_ON_REQUIRE = [
+	RuntimeGlobals.chunkName,
+	RuntimeGlobals.runtimeId,
+	RuntimeGlobals.compatGetDefaultExport,
+	RuntimeGlobals.createFakeNamespaceObject,
+	RuntimeGlobals.createScript,
+	RuntimeGlobals.createScriptUrl,
+	RuntimeGlobals.getTrustedTypesPolicy,
+	RuntimeGlobals.definePropertyGetters,
+	RuntimeGlobals.ensureChunk,
+	RuntimeGlobals.entryModuleId,
+	RuntimeGlobals.getCssServerStyles,
+	RuntimeGlobals.getFullHash,
+	RuntimeGlobals.global,
+	RuntimeGlobals.makeNamespaceObject,
+	RuntimeGlobals.moduleCache,
+	RuntimeGlobals.moduleFactories,
+	RuntimeGlobals.moduleFactoriesAddOnly,
+	RuntimeGlobals.interceptModuleExecution,
+	RuntimeGlobals.publicPath,
+	RuntimeGlobals.baseURI,
+	RuntimeGlobals.prefetchAsset,
+	RuntimeGlobals.preloadAsset,
+	RuntimeGlobals.relativeUrl,
+	// TODO webpack 6 - rename to nonce, because we use it for CSS too
+	RuntimeGlobals.scriptNonce,
+	RuntimeGlobals.uncaughtErrorHandler,
+	RuntimeGlobals.asyncModule,
+	RuntimeGlobals.asyncModuleGenerator,
+	RuntimeGlobals.wasmInstances,
+	RuntimeGlobals.instantiateWasm,
+	RuntimeGlobals.shareScopeMap,
+	RuntimeGlobals.initializeSharing,
+	RuntimeGlobals.loadScript,
+	RuntimeGlobals.setAnonymousDefaultName,
+	RuntimeGlobals.systemContext,
+	RuntimeGlobals.onChunksLoaded,
+	RuntimeGlobals.makeOptimizedDeferredNamespaceObject,
+	RuntimeGlobals.makeDeferredNamespaceObject,
+	RuntimeGlobals.concatenationWrap,
+	RuntimeGlobals.constructRequire
+];
+
+const MODULE_DEPENDENCIES = {
+	[RuntimeGlobals.moduleLoaded]: [RuntimeGlobals.module],
+	[RuntimeGlobals.moduleId]: [RuntimeGlobals.module]
+};
+
+const TREE_DEPENDENCIES = {
+	[RuntimeGlobals.definePropertyGetters]: [RuntimeGlobals.hasOwnProperty],
+	[RuntimeGlobals.compatGetDefaultExport]: [
+		RuntimeGlobals.definePropertyGetters
+	],
+	[RuntimeGlobals.createFakeNamespaceObject]: [
+		RuntimeGlobals.definePropertyGetters,
+		RuntimeGlobals.makeNamespaceObject,
+		RuntimeGlobals.require
+	],
+	[RuntimeGlobals.makeOptimizedDeferredNamespaceObject]: [
+		RuntimeGlobals.require
+	],
+	[RuntimeGlobals.makeDeferredNamespaceObject]: [
+		RuntimeGlobals.createFakeNamespaceObject,
+		RuntimeGlobals.require
+	],
+	[RuntimeGlobals.initializeSharing]: [RuntimeGlobals.shareScopeMap],
+	[RuntimeGlobals.shareScopeMap]: [RuntimeGlobals.hasOwnProperty],
+	[RuntimeGlobals.constructRequire]: [RuntimeGlobals.require]
+};
+
+// Tree runtime requirements whose only effect is attaching a zero-arg runtime
+// module to the chunk. Conditional / argument-taking ones stay as explicit taps.
+/** @type {[string, () => Promise RuntimeModule>][]} */
+const SIMPLE_TREE_RUNTIME_MODULES = [
+	[RuntimeGlobals.definePropertyGetters, getDefinePropertyGettersRuntimeModule],
+	[RuntimeGlobals.makeNamespaceObject, getMakeNamespaceObjectRuntimeModule],
+	[
+		RuntimeGlobals.createFakeNamespaceObject,
+		getCreateFakeNamespaceObjectRuntimeModule
+	],
+	[RuntimeGlobals.hasOwnProperty, getHasOwnPropertyRuntimeModule],
+	[
+		RuntimeGlobals.compatGetDefaultExport,
+		getCompatGetDefaultExportRuntimeModule
+	],
+	[RuntimeGlobals.concatenationWrap, getConcatenationWrapRuntimeModule],
+	[RuntimeGlobals.constructRequire, getConstructRequireRuntimeModule],
+	[
+		RuntimeGlobals.setAnonymousDefaultName,
+		getSetAnonymousDefaultNameRuntimeModule
+	],
+	[RuntimeGlobals.runtimeId, getRuntimeIdRuntimeModule],
+	[RuntimeGlobals.global, getGlobalRuntimeModule],
+	[RuntimeGlobals.shareScopeMap, getShareRuntimeModule],
+	[RuntimeGlobals.relativeUrl, getRelativeUrlRuntimeModule],
+	[RuntimeGlobals.worker, getWorkerRuntimeModule],
+	[RuntimeGlobals.onChunksLoaded, getOnChunksLoadedRuntimeModule],
+	[RuntimeGlobals.scriptNonce, getNonceRuntimeModule],
+	[RuntimeGlobals.toBinary, getToBinaryRuntimeModule]
+];
+
+/**
+ * @param {string} template path template
+ * @returns {boolean} true when it references the compilation `[fullhash]`/`[hash]`
+ */
+const usesFullHash = (template) => {
+	const kinds = getPresentKinds(template);
+	return kinds.has("fullhash") || kinds.has("hash");
+};
+
+const PLUGIN_NAME = "RuntimePlugin";
+
+class RuntimePlugin {
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the Compiler
+	 * @returns {void}
+	 */
+	apply(compiler) {
+		compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
+			const globalChunkLoading = compilation.outputOptions.chunkLoading;
+			/**
+			 * Checks whether this runtime plugin is chunk loading disabled for chunk.
+			 * @param {Chunk} chunk chunk
+			 * @returns {boolean} true, when chunk loading is disabled for the chunk
+			 */
+			const isChunkLoadingDisabledForChunk = (chunk) => {
+				const options = chunk.getEntryOptions();
+				const chunkLoading =
+					options && options.chunkLoading !== undefined
+						? options.chunkLoading
+						: globalChunkLoading;
+				return chunkLoading === false;
+			};
+			compilation.dependencyTemplates.set(
+				RuntimeRequirementsDependency,
+				new RuntimeRequirementsDependency.Template()
+			);
+			for (const req of GLOBALS_ON_REQUIRE) {
+				compilation.hooks.runtimeRequirementInModule
+					.for(req)
+					.tap(PLUGIN_NAME, (module, set) => {
+						set.add(RuntimeGlobals.requireScope);
+					});
+				compilation.hooks.runtimeRequirementInTree
+					.for(req)
+					.tap(PLUGIN_NAME, (module, set) => {
+						set.add(RuntimeGlobals.requireScope);
+					});
+			}
+			for (const req of Object.keys(TREE_DEPENDENCIES)) {
+				const deps =
+					TREE_DEPENDENCIES[/** @type {keyof TREE_DEPENDENCIES} */ (req)];
+				compilation.hooks.runtimeRequirementInTree
+					.for(req)
+					.tap(PLUGIN_NAME, (chunk, set) => {
+						for (const dep of deps) set.add(dep);
+					});
+			}
+			for (const req of Object.keys(MODULE_DEPENDENCIES)) {
+				const deps =
+					MODULE_DEPENDENCIES[/** @type {keyof MODULE_DEPENDENCIES} */ (req)];
+				compilation.hooks.runtimeRequirementInModule
+					.for(req)
+					.tap(PLUGIN_NAME, (chunk, set) => {
+						for (const dep of deps) set.add(dep);
+					});
+			}
+			for (const [
+				runtimeGlobal,
+				getRuntimeModule
+			] of SIMPLE_TREE_RUNTIME_MODULES) {
+				compilation.hooks.runtimeRequirementInTree
+					.for(runtimeGlobal)
+					.tap(PLUGIN_NAME, (chunk) => {
+						compilation.addLazyRuntimeModule(
+							chunk,
+							getRuntimeModule,
+							(Ctor) => new Ctor()
+						);
+						return true;
+					});
+			}
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.makeOptimizedDeferredNamespaceObject)
+				.tap(PLUGIN_NAME, (chunk, runtimeRequirement) => {
+					const asyncModule = runtimeRequirement.has(
+						RuntimeGlobals.asyncModule
+					);
+					compilation.addLazyRuntimeModule(
+						chunk,
+						getMakeOptimizedDeferredNamespaceObjectRuntimeModule,
+						(Ctor) => new Ctor(asyncModule)
+					);
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.makeDeferredNamespaceObject)
+				.tap(PLUGIN_NAME, (chunk, runtimeRequirement) => {
+					const asyncModule = runtimeRequirement.has(
+						RuntimeGlobals.asyncModule
+					);
+					compilation.addLazyRuntimeModule(
+						chunk,
+						getMakeDeferredNamespaceObjectRuntimeModule,
+						(Ctor) => new Ctor(asyncModule)
+					);
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.publicPath)
+				.tap(PLUGIN_NAME, (chunk, set) => {
+					const { outputOptions } = compilation;
+					const { publicPath: globalPublicPath, scriptType } = outputOptions;
+					const entryOptions = chunk.getEntryOptions();
+					const publicPath =
+						entryOptions && entryOptions.publicPath !== undefined
+							? entryOptions.publicPath
+							: globalPublicPath;
+
+					if (publicPath === "auto") {
+						// A worklet chunk reads `import.meta.url`, so it needs neither the
+						// worker-scope detection nor the `globalThis` polyfill.
+						if (
+							scriptType !== "module" &&
+							!(entryOptions && entryOptions.worklet) &&
+							!outputOptions.environment.globalThis
+						) {
+							set.add(RuntimeGlobals.global);
+						}
+
+						compilation.addLazyRuntimeModule(
+							chunk,
+							getAutoPublicPathRuntimeModule,
+							(Ctor) => new Ctor()
+						);
+					} else {
+						const fullHash =
+							typeof publicPath !== "string" || usesFullHash(publicPath);
+
+						compilation.addLazyRuntimeModule(
+							chunk,
+							getPublicPathRuntimeModule,
+							(Ctor) => {
+								const module = new Ctor(publicPath);
+								if (fullHash) module.fullHash = true;
+								return module;
+							}
+						);
+					}
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.asyncModule)
+				.tap(PLUGIN_NAME, (chunk) => {
+					const experiments = compilation.options.experiments;
+					compilation.addLazyRuntimeModule(
+						chunk,
+						getAsyncModuleRuntimeModule,
+						(Ctor) => new Ctor(experiments.deferImport)
+					);
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.asyncModuleGenerator)
+				.tap(PLUGIN_NAME, (chunk, set) => {
+					set.add(RuntimeGlobals.asyncModule);
+					compilation.addLazyRuntimeModule(
+						chunk,
+						getAsyncModuleGeneratorRuntimeModule,
+						(Ctor) => new Ctor()
+					);
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.systemContext)
+				.tap(PLUGIN_NAME, (chunk) => {
+					const entryOptions = chunk.getEntryOptions();
+					const libraryType =
+						entryOptions && entryOptions.library !== undefined
+							? entryOptions.library.type
+							: /** @type {LibraryOptions} */
+								(compilation.outputOptions.library).type;
+
+					if (libraryType === "system") {
+						compilation.addLazyRuntimeModule(
+							chunk,
+							getSystemContextRuntimeModule,
+							(Ctor) => new Ctor()
+						);
+					}
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.getChunkScriptFilename)
+				.tap(PLUGIN_NAME, (chunk, set, { chunkGraph }) => {
+					if (
+						typeof compilation.outputOptions.chunkFilename === "string" &&
+						usesFullHash(compilation.outputOptions.chunkFilename)
+					) {
+						set.add(RuntimeGlobals.getFullHash);
+					}
+					const hmr = set.has(RuntimeGlobals.hmrDownloadUpdateHandlers);
+					const fullHashDigest =
+						typeof compilation.outputOptions.chunkFilename === "string" &&
+						usesFullHashDigest(compilation.outputOptions.chunkFilename);
+					compilation.addLazyRuntimeModule(
+						chunk,
+						getGetChunkFilenameRuntimeModule,
+						(Ctor) =>
+							new Ctor(
+								"javascript",
+								"javascript",
+								RuntimeGlobals.getChunkScriptFilename,
+								(/** @type {Chunk} */ chunk) =>
+									JavascriptModulesPlugin.chunkHasJs(chunk, chunkGraph) &&
+									JavascriptModulesPlugin.getChunkFilenameTemplate(
+										chunk,
+										compilation.outputOptions
+									),
+								hmr,
+								fullHashDigest
+							)
+					);
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.getWorkletBootstrap)
+				.tap(PLUGIN_NAME, (chunk, set) => {
+					set.add(RuntimeGlobals.baseURI);
+					compilation.addLazyRuntimeModule(
+						chunk,
+						getGetWorkletBootstrapRuntimeModule,
+						(Ctor) => new Ctor()
+					);
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.getChunkCssFilename)
+				.tap(PLUGIN_NAME, (chunk, set, { chunkGraph }) => {
+					if (
+						typeof compilation.outputOptions.cssChunkFilename === "string" &&
+						usesFullHash(compilation.outputOptions.cssChunkFilename)
+					) {
+						set.add(RuntimeGlobals.getFullHash);
+					}
+					const hmr = set.has(RuntimeGlobals.hmrDownloadUpdateHandlers);
+					const fullHashDigest =
+						typeof compilation.outputOptions.cssChunkFilename === "string" &&
+						usesFullHashDigest(compilation.outputOptions.cssChunkFilename);
+					compilation.addLazyRuntimeModule(
+						chunk,
+						getGetChunkFilenameRuntimeModule,
+						(Ctor) =>
+							new Ctor(
+								"css",
+								"css",
+								RuntimeGlobals.getChunkCssFilename,
+								(/** @type {Chunk} */ chunk) => {
+									const cssModulePlugin = CssModulesPlugin;
+
+									return (
+										cssModulePlugin.chunkHasCss(chunk, chunkGraph) &&
+										cssModulePlugin.getChunkFilenameTemplate(
+											chunk,
+											compilation.outputOptions
+										)
+									);
+								},
+								hmr,
+								fullHashDigest
+							)
+					);
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.getChunkUpdateScriptFilename)
+				.tap(PLUGIN_NAME, (chunk, set) => {
+					if (usesFullHash(compilation.outputOptions.hotUpdateChunkFilename)) {
+						set.add(RuntimeGlobals.getFullHash);
+					}
+					const fullHashDigest =
+						typeof compilation.outputOptions.hotUpdateChunkFilename ===
+							"string" &&
+						usesFullHashDigest(
+							compilation.outputOptions.hotUpdateChunkFilename
+						);
+					compilation.addLazyRuntimeModule(
+						chunk,
+						getGetChunkFilenameRuntimeModule,
+						(Ctor) =>
+							new Ctor(
+								"javascript",
+								"javascript update",
+								RuntimeGlobals.getChunkUpdateScriptFilename,
+								(/** @type {Chunk} */ _chunk) =>
+									compilation.outputOptions.hotUpdateChunkFilename,
+								true,
+								fullHashDigest
+							)
+					);
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.getCssServerStyles)
+				.tap(PLUGIN_NAME, (chunk, set) => {
+					// The registry lives on the global, reached via the polyfill when
+					// the target has no `globalThis`.
+					if (!compilation.outputOptions.environment.globalThis) {
+						set.add(RuntimeGlobals.global);
+					}
+					compilation.addLazyRuntimeModule(
+						chunk,
+						getCssServerStylesRuntimeModule,
+						(Ctor) => new Ctor()
+					);
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.getUpdateManifestFilename)
+				.tap(PLUGIN_NAME, (chunk, set) => {
+					if (usesFullHash(compilation.outputOptions.hotUpdateMainFilename)) {
+						set.add(RuntimeGlobals.getFullHash);
+					}
+					compilation.addLazyRuntimeModule(
+						chunk,
+						getGetMainFilenameRuntimeModule,
+						(Ctor) =>
+							new Ctor(
+								"update manifest",
+								RuntimeGlobals.getUpdateManifestFilename,
+								compilation.outputOptions.hotUpdateMainFilename
+							)
+					);
+					return true;
+				});
+			/** @type {WeakSet} */
+			const ensureChunkModuleAdded = new WeakSet();
+			/**
+			 * The module carries the handler map as well as `ensureChunk`, and an
+			 * analyzable `import()` needs the map without the function around it.
+			 * @param {Chunk} chunk the chunk
+			 * @param {RuntimeRequirements} set the tree's requirements, filled in place
+			 */
+			const addEnsureChunkRuntimeModule = (chunk, set) => {
+				if (ensureChunkModuleAdded.has(chunk)) return;
+				ensureChunkModuleAdded.add(chunk);
+				compilation.addLazyRuntimeModule(
+					chunk,
+					getEnsureChunkRuntimeModule,
+					(Ctor) => new Ctor(set)
+				);
+			};
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.ensureChunk)
+				.tap(PLUGIN_NAME, (chunk, set) => {
+					const hasAsyncChunks = chunk.hasAsyncChunks();
+					if (hasAsyncChunks) {
+						set.add(RuntimeGlobals.ensureChunkHandlers);
+					}
+					addEnsureChunkRuntimeModule(chunk, set);
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.ensureChunkHandlers)
+				.tap(PLUGIN_NAME, (chunk, set) => {
+					// No bail: every other plugin attaching a handler taps this same key.
+					addEnsureChunkRuntimeModule(chunk, set);
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.ensureChunkIncludeEntries)
+				.tap(PLUGIN_NAME, (chunk, set) => {
+					set.add(RuntimeGlobals.ensureChunkHandlers);
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.loadScript)
+				.tap(PLUGIN_NAME, (chunk, set) => {
+					const withCreateScriptUrl = Boolean(
+						compilation.outputOptions.trustedTypes
+					);
+					if (withCreateScriptUrl) {
+						set.add(RuntimeGlobals.createScriptUrl);
+					}
+					const withFetchPriority = set.has(RuntimeGlobals.hasFetchPriority);
+					compilation.addLazyRuntimeModule(
+						chunk,
+						getLoadScriptRuntimeModule,
+						(Ctor) => new Ctor(withCreateScriptUrl, withFetchPriority)
+					);
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.createScript)
+				.tap(PLUGIN_NAME, (chunk, set) => {
+					if (compilation.outputOptions.trustedTypes) {
+						set.add(RuntimeGlobals.getTrustedTypesPolicy);
+					}
+					compilation.addLazyRuntimeModule(
+						chunk,
+						getCreateScriptRuntimeModule,
+						(Ctor) => new Ctor()
+					);
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.createScriptUrl)
+				.tap(PLUGIN_NAME, (chunk, set) => {
+					if (compilation.outputOptions.trustedTypes) {
+						set.add(RuntimeGlobals.getTrustedTypesPolicy);
+					}
+					compilation.addLazyRuntimeModule(
+						chunk,
+						getCreateScriptUrlRuntimeModule,
+						(Ctor) => new Ctor()
+					);
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.getTrustedTypesPolicy)
+				.tap(PLUGIN_NAME, (chunk, set) => {
+					compilation.addLazyRuntimeModule(
+						chunk,
+						getGetTrustedTypesPolicyRuntimeModule,
+						(Ctor) => new Ctor(set)
+					);
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.baseURI)
+				.tap(PLUGIN_NAME, (chunk) => {
+					if (isChunkLoadingDisabledForChunk(chunk)) {
+						compilation.addLazyRuntimeModule(
+							chunk,
+							getBaseUriRuntimeModule,
+							(Ctor) => new Ctor()
+						);
+						return true;
+					}
+				});
+			// TODO webpack 6: remove CompatRuntimeModule
+			compilation.hooks.additionalTreeRuntimeRequirements.tap(
+				PLUGIN_NAME,
+				(chunk, _set) => {
+					const { mainTemplate } = compilation;
+					if (
+						mainTemplate.hooks.bootstrap.isUsed() ||
+						mainTemplate.hooks.localVars.isUsed() ||
+						mainTemplate.hooks.requireEnsure.isUsed() ||
+						mainTemplate.hooks.requireExtensions.isUsed()
+					) {
+						compilation.addLazyRuntimeModule(
+							chunk,
+							getCompatRuntimeModule,
+							(Ctor) => new Ctor()
+						);
+					}
+				}
+			);
+			JavascriptModulesPlugin.getCompilationHooks(compilation).chunkHash.tap(
+				PLUGIN_NAME,
+				(chunk, hash, { chunkGraph }) => {
+					const xor = new StringXor();
+					for (const m of chunkGraph.getChunkRuntimeModulesIterable(chunk)) {
+						xor.add(chunkGraph.getModuleHash(m, chunk.runtime));
+					}
+					xor.updateHash(hash);
+				}
+			);
+		});
+	}
+}
+
+module.exports = RuntimePlugin;
diff --git a/lib/RuntimeTemplate.js b/lib/RuntimeTemplate.js
index 92292d389c1..19e1c5b71a7 100644
--- a/lib/RuntimeTemplate.js
+++ b/lib/RuntimeTemplate.js
@@ -2,142 +2,1729 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
+const APIPlugin = require("./APIPlugin");
+
+const InitFragment = require("./InitFragment");
+const {
+	ASSET_TYPE,
+	ASSET_URL_TYPE,
+	HTML_TYPE,
+	JAVASCRIPT_TYPE,
+	RUNTIME_TYPE
+} = require("./ModuleSourceTypeConstants");
+const { WEBASSEMBLY_MODULE_TYPE_ASYNC } = require("./ModuleTypeConstants");
+const RuntimeGlobals = require("./RuntimeGlobals");
 const Template = require("./Template");
+const {
+	getOutgoingAsyncModules
+} = require("./async-modules/AsyncModuleHelpers");
+const HarmonyImportDependency = require("./dependencies/HarmonyImportDependency");
+const ImportDependency = require("./dependencies/ImportDependency");
+const { ImportPhaseUtils } = require("./dependencies/ImportPhase");
+const JavascriptModulesPlugin = require("./javascript/JavascriptModulesPlugin");
+const { InlinedUsedName } = require("./optimize/InlineExports");
+const {
+	getDeferredCycleModuleIds,
+	getDeferredCycleModules,
+	getMakeDeferredNamespaceModeFromExportsType,
+	getOptimizedDeferredModule
+} = require("./runtime/MakeDeferredNamespaceObjectRuntime");
+const {
+	isAbsoluteBaseUri,
+	isChunkRelativeBaseUri
+} = require("./runtime/baseUri");
+const { equals } = require("./util/ArrayHelpers");
+const { getScheme } = require("./util/URLAbsoluteSpecifier");
+const compileBooleanMatcher = require("./util/compileBooleanMatcher");
+const { getUndoPath, toJsStringLiteral } = require("./util/identifier");
+const memoize = require("./util/memoize");
+const { propertyAccess, propertyName } = require("./util/property");
+const {
+	forEachRuntime,
+	getRuntimeKey,
+	intersectRuntime,
+	subtractRuntime
+} = require("./util/runtime");
+
+const getTemplatedPathPlugin = memoize(() => require("./TemplatedPathPlugin"));
+const getAnalyzableChunkHashPlugin = memoize(() =>
+	require("./esm/AnalyzableChunkHashPlugin")
+);
+const getConcatenatedModule = memoize(() =>
+	require("./optimize/ConcatenatedModule")
+);
+
+// Any `[hash]`/`[fullhash]`/`[chunkhash]`/`[contenthash]` token, incl. a `:`
+// or `:[:]` suffix (e.g. `[contenthash:base64:8]`). Its value is only
+// resolved after code generation, so a filename using one can't be an inline literal.
+const HASH_IN_FILENAME = /\[(?:full|chunk|content)?hash(?::[^\]]+)?\]/;
+// Two stand-ins for every hash a filename function might read: a name built from one
+// is hash-dependent exactly when the two calls disagree.
+const HASH_PROBE = "0123456789abcdef0123";
+// Reversed, so the two differ at every position and no slice of one equals the other.
+const HASH_PROBE_ALTERNATE = "3210fedcba9876543210";
+const HASH_IN_FILENAME_GLOBAL = /\[(?:full|chunk|content)?hash(?::[^\]]+)?\]/g;
+
+/**
+ * @param {SpecifierPart} part one piece of a reserved name
+ * @returns {boolean} true when it is text the deferred pass adds nothing to
+ */
+const isLiteralPart = (part) => part[0] === "literal";
+
+/**
+ * The text these parts spell, or `null` when one of them is a stand-in that only
+ * the deferred pass can fill in.
+ * @param {SpecifierPart[] | null} parts pieces of a name
+ * @returns {string | null} the text, or `null` when they are not all literal
+ */
+const literalText = (parts) => {
+	if (parts === null) return null;
+	// Built in one pass rather than tested and then joined: every specifier asks, and
+	// a stand-in in the first part answers without reading the rest.
+	let text = "";
+	for (const part of parts) {
+		if (!isLiteralPart(part)) return null;
+		text += part[1];
+	}
+	return text;
+};
+
+/**
+ * Whether a public path reaches the same place from any base. A relative one does
+ * not, and is equivalent only behind the `../` path back to the output root.
+ * @param {string} publicPath the resolved public path, or the shape of one
+ * @returns {boolean} true when no base is needed
+ */
+const isBaseIndependent = (publicPath) =>
+	publicPath.startsWith("/") || getScheme(publicPath) !== undefined;
+
+/**
+ * Drops the `./` a public path may open with: what follows it is already walked back
+ * to the output root, so it would only lengthen the name.
+ * @param {SpecifierPart[]} parts a public path's pieces
+ * @returns {SpecifierPart[]} them, without that `./`
+ */
+const rootedParts = (parts) =>
+	parts.length > 0 &&
+	isLiteralPart(parts[0]) &&
+	/** @type {string} */ (parts[0][1]).startsWith("./")
+		? [
+				/** @type {SpecifierPart} */ ([
+					"literal",
+					/** @type {string} */ (parts[0][1]).slice(2)
+				]),
+				...parts.slice(1)
+			]
+		: parts;
+
+// Types that ride the chunk itself or render their own asset, so no `.f` handler
+// fetches them; every other type may install one, so the map has to exist for it.
+/** @type {Set} */
+const TYPES_WITHOUT_CHUNK_HANDLER = new Set([
+	JAVASCRIPT_TYPE,
+	RUNTIME_TYPE,
+	ASSET_TYPE,
+	ASSET_URL_TYPE,
+	HTML_TYPE
+]);
+
+// Why a name only the deferred pass could settle was not deferred.
+const DEFER_BAILOUT =
+	"a hashed name needs optimization.realContentHash, or no emitted javascript named by its content";
+
+/**
+ * Where a literal is read from — the `../` path back to the output root, and whether
+ * the chunk loader fetched the chunk it sits in through `output.publicPath`.
+ * @typedef {object} Placement
+ * @property {string | null} undo the path back to the output root
+ * @property {boolean | null} loaded whether the chunk loader fetched the chunk
+ */
+
+// A reference in no chunk at all: nothing is known about where it is read from.
+/** @type {Placement} */
+const NO_PLACEMENT = { undo: null, loaded: null };
+
+// Why a public path needing a base could not be spelled from the chunk holding it.
+const SERVED_BAILOUT =
+	"this module is in a chunk webpack loads through output.publicPath and in one it does not, so no one path back to the output root fits both";
+
+/** @typedef {import("./config/defaults").OutputNormalizedWithDefaults} OutputOptions */
+/** @typedef {import("../declarations/WebpackOptions").PublicPath} PublicPath */
+/** @typedef {import("../declarations/WebpackOptions").WasmLoading} WasmLoading */
+/** @typedef {import("./esm/AnalyzableChunkHashPlugin").SpecifierPart} SpecifierPart */
+/** @typedef {"import" | "url" | "url-inline" | "wasm" | "wasm-relative"} AnalyzableForm */
+/** @typedef {import("./AsyncDependenciesBlock")} AsyncDependenciesBlock */
+/** @typedef {import("./Chunk")} Chunk */
+/** @typedef {import("./ChunkGroup")} ChunkGroup */
+/** @typedef {import("./Chunk").ChunkFilenameTemplate} ChunkFilenameTemplate */
+/** @typedef {import("./Chunk").ChunkId} ChunkId */
+/** @typedef {import("./ChunkGraph")} ChunkGraph */
+/** @typedef {import("./Compilation")} Compilation */
+/** @typedef {import("./Dependency")} Dependency */
+/** @typedef {import("./Module")} Module */
+/** @typedef {import("./Module").BuildMeta} BuildMeta */
+/** @typedef {import("./Module").RuntimeRequirements} RuntimeRequirements */
+/** @typedef {import("./ModuleGraph")} ModuleGraph */
+/** @typedef {import("./RequestShortener")} RequestShortener */
+/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */
+/** @typedef {import("./dependencies/ImportPhase").ImportPhaseType} ImportPhaseType */
+/** @typedef {import("./NormalModuleFactory").ModuleDependency} ModuleDependency */
+
+/**
+ * A stand-in whose every hash reads back as `hash`. Which content hashes a chunk
+ * carries is settled after code generation, so `present` makes the two probes disagree
+ * about that too — a name built by enumerating them then reads as hash-dependent.
+ * Shadows the chunk rather than mutating it, so a filename function still reads every
+ * other field and method straight off it.
+ * @param {Chunk} chunk the chunk being referenced
+ * @param {string} hash the stand-in hash
+ * @param {boolean} present whether the chunk is claimed to carry content hashes
+ * @returns {Chunk} the stand-in chunk
+ */
+const createHashProbeChunk = (chunk, hash, present) => {
+	const probeChunk = Object.create(chunk);
+	probeChunk.hash = hash;
+	probeChunk.renderedHash = hash;
+	probeChunk.contentHash = new Proxy(
+		present ? { [JAVASCRIPT_TYPE]: hash } : {},
+		{
+			// Any content-hash type, not only the ones this chunk happens to carry.
+			get: (target, key) => (typeof key === "string" ? hash : undefined),
+			has: () => present,
+			getOwnPropertyDescriptor: () =>
+				present
+					? {
+							value: hash,
+							writable: true,
+							enumerable: true,
+							configurable: true
+						}
+					: undefined
+		}
+	);
+	return probeChunk;
+};
+
+/**
+ * No module id error message.
+ * @param {Module} module the module
+ * @param {ChunkGraph} chunkGraph the chunk graph
+ * @returns {string} error message
+ */
+const noModuleIdErrorMessage = (
+	module,
+	chunkGraph
+) => `Module ${module.identifier()} has no id assigned.
+This should not happen.
+It's in these chunks: ${
+	Array.from(
+		chunkGraph.getModuleChunksIterable(module),
+		(c) => c.name || c.id || c.debugId
+	).join(", ") || "none"
+} (If module is in no chunk this indicates a bug in some chunk/module optimization logic)
+Module has these incoming connections: ${Array.from(
+	chunkGraph.moduleGraph.getIncomingConnections(module),
+	(connection) =>
+		`\n - ${connection.originModule && connection.originModule.identifier()} ${
+			connection.dependency && connection.dependency.type
+		} ${
+			(connection.explanations && [...connection.explanations].join(", ")) || ""
+		}`
+).join("")}`;
+
+// `this` as a value, not as a property name or part of a longer identifier
+// (identifier characters per `getGlobalObject` below, so `globalThis` is not one)
+const THIS_REFERENCE_REGEXP = /(?:^|[^\p{L}\p{N}_$.])this(?![\p{L}\p{N}_$])/u;
+
+/**
+ * Gets global object.
+ * @param {string | undefined} definition global object definition
+ * @returns {string | undefined} save to use global object
+ */
+function getGlobalObject(definition) {
+	if (!definition) return definition;
+	const trimmed = definition.trim();
+
+	if (
+		// identifier, we do not need real identifier regarding ECMAScript/Unicode
+		/^[_\p{L}][_0-9\p{L}]*$/iu.test(trimmed) ||
+		// iife
+		// call expression
+		// expression in parentheses
+		/^(?:[_\p{L}][_0-9\p{L}]*)?\(.*\)$/iu.test(trimmed)
+	) {
+		return trimmed;
+	}
+
+	return `Object(${trimmed})`;
+}
 
-module.exports = class RuntimeTemplate {
-	constructor(outputOptions, requestShortener) {
-		this.outputOptions = outputOptions || {};
+class RuntimeTemplate {
+	/**
+	 * Creates an instance of RuntimeTemplate.
+	 * @param {Compilation} compilation the compilation
+	 * @param {OutputOptions} outputOptions the compilation output options
+	 * @param {RequestShortener} requestShortener the request shortener
+	 */
+	constructor(compilation, outputOptions, requestShortener) {
+		/** @type {Compilation} */
+		this.compilation = compilation;
+		this.outputOptions = /** @type {OutputOptions} */ (outputOptions || {});
+		/** @type {RequestShortener} */
 		this.requestShortener = requestShortener;
+		/** @type {string} */
+		this.globalObject =
+			/** @type {string} */
+			(getGlobalObject(outputOptions.globalObject));
+		/** @type {string} */
+		this.contentHashReplacement = "X".repeat(outputOptions.hashDigestLength);
+		/** @type {boolean | undefined} */
+		this._javascriptNamedWithoutContent = undefined;
+		/** @type {WeakMap} */
+		this._chunkNamedWithoutContent = new WeakMap();
+		/** @type {string | false | undefined} */
+		this._publicPathShapeText = undefined;
+		/** @type {string | null | undefined} */
+		this._publicPathClimbText = undefined;
+		/** @type {WeakMap} */
+		this._placementByChunk = new WeakMap();
+		/** @type {Map} */
+		this._entryBaseUriByRuntime = new Map();
+		/** @type {Map | undefined} */
+		this._wasmRuntimeGroupsByKey = undefined;
+		/** @type {Set | false | undefined} */
+		this._wasmFetchingGroupSet = undefined;
+		/** @type {Map | undefined} */
+		this._wasmAnchorKnownByGroup = undefined;
+		/** @type {Map | undefined} */
+		this._wasmGroupOnlyFetchesMap = undefined;
+	}
+
+	isIIFE() {
+		return this.outputOptions.iife;
+	}
+
+	/**
+	 * Whether the global object expression reads the `this` binding, which only
+	 * refers to the global object outside of strict mode.
+	 * @returns {boolean} true, when it reads `this`
+	 */
+	globalObjectUsesThis() {
+		return THIS_REFERENCE_REGEXP.test(this.globalObject);
+	}
+
+	isModule() {
+		return this.outputOptions.module;
+	}
+
+	isNeutralPlatform() {
+		return (
+			!this.compilation.compiler.platform.web &&
+			!this.compilation.compiler.platform.node
+		);
+	}
+
+	/**
+	 * Whether the bundle targets node and web at once (universal `["node", "web"]` + `output.module`), like `isUniversalTarget` in `WebpackOptionsApply`.
+	 * @returns {boolean} true for a universal target
+	 */
+	isUniversalTarget() {
+		const { platform } = this.compilation.compiler;
+		return (
+			Boolean(this.outputOptions.module) &&
+			platform.node === null &&
+			platform.web === null
+		);
 	}
 
 	/**
-	 * Add a comment
+	 * Runtime expression that is truthy in browser-like environments (a DOM
+	 * `document` or a worker `self`) and falsy in Node.js. Single source of
+	 * truth for branching a universal ("node-or-web") target at runtime.
+	 * @returns {string} runtime condition expression
+	 */
+	isWebLikePlatformExpression() {
+		return "typeof document !== 'undefined' || typeof self !== 'undefined'";
+	}
+
+	/**
+	 * Expression for the global registry that collects CSS server-side when there
+	 * is no DOM (SSR). Read it with `__webpack_css_server_styles__`; it is keyed
+	 * by the style/chunk identifier and namespaced by `output.uniqueName`.
+	 * Targets without `globalThis` go through the `__webpack_require__.g`
+	 * polyfill, so consumers must also require `RuntimeGlobals.global`.
+	 * @returns {string} runtime expression evaluating to the registry object
+	 */
+	cssServerStyleRegistry() {
+		const name = this.outputOptions.uniqueName;
+		const key = JSON.stringify(
+			name ? `__webpack_css__${name}` : "__webpack_css__"
+		);
+		const global = this.outputOptions.environment.globalThis
+			? "globalThis"
+			: RuntimeGlobals.global;
+		return `(${this.assignOr(`${global}[${key}]`, "{}")})`;
+	}
+
+	supportsConst() {
+		return this.outputOptions.environment.const;
+	}
+
+	supportsLet() {
+		return this.outputOptions.environment.let;
+	}
+
+	supportsMethodShorthand() {
+		return this.outputOptions.environment.methodShorthand;
+	}
+
+	supportsLogicalAssignment() {
+		return this.outputOptions.environment.logicalAssignment;
+	}
+
+	supportsArrowFunction() {
+		return this.outputOptions.environment.arrowFunction;
+	}
+
+	supportsAsyncFunction() {
+		return this.outputOptions.environment.asyncFunction;
+	}
+
+	supportsGenerator() {
+		return this.outputOptions.environment.generator;
+	}
+
+	supportsOptionalChaining() {
+		return this.outputOptions.environment.optionalChaining;
+	}
+
+	supportsSpread() {
+		return this.outputOptions.environment.spread;
+	}
+
+	supportsObjectHasOwn() {
+		return this.outputOptions.environment.hasOwn;
+	}
+
+	supportsSymbol() {
+		return this.outputOptions.environment.symbol;
+	}
+
+	supportsForOf() {
+		return this.outputOptions.environment.forOf;
+	}
+
+	supportsDestructuring() {
+		return this.outputOptions.environment.destructuring;
+	}
+
+	supportsBigIntLiteral() {
+		return this.outputOptions.environment.bigIntLiteral;
+	}
+
+	supportsDynamicImport() {
+		return this.outputOptions.environment.dynamicImport;
+	}
+
+	supportsEcmaScriptModuleSyntax() {
+		return this.outputOptions.environment.module;
+	}
+
+	supportsModulePreload() {
+		return this.outputOptions.environment.modulePreload;
+	}
+
+	/**
+	 * Whether a reference a foreign bundler can follow without running webpack's runtime
+	 * may be emitted — the one question every caller asks, in the form it is asking for:
+	 *
+	 * - `"import"` — a literal `import("./chunk.js")` in place of `ensureChunk(id)`
+	 * - `"url"` — a literal `new URL(, import.meta.url)`
+	 * - `"url-inline"` — whether such a reference names the file at the call site
+	 * - `"wasm"` — the same, fully baked for a wasm binary the runtime would name
+	 * - `"wasm-relative"` — a wasm path built at runtime under an `import.meta.url` base
+	 *
+	 * `"url-inline"` differs from `"url"` only in taking the `.p + ` fallback as
+	 * an answer too — both name the file where it is used rather than through the
+	 * asset's javascript wrapper, which is what decides whether that wrapper is emitted.
+	 *
+	 * A name code generation cannot settle may still be baked, through a stand-in the
+	 * deferred pass fills in. Not covered here, because only the reference can tell: a
+	 * chunk with no id, and one this compilation emits no javascript for.
+	 * @param {AnalyzableForm} form which reference is being emitted
+	 * @param {ChunkGraph=} chunkGraph the chunk graph, to place `module` in its runtimes
+	 * @param {Module=} module the module the reference is emitted into
+	 * @param {RuntimeSpec=} runtime the runtime the reference is emitted for; the wasm forms answer for the loader it shares rather than for every loader in the compilation
+	 * @returns {boolean} true when the literal form may be emitted
+	 */
+	supportsAnalyzable(form, chunkGraph, module, runtime) {
+		// Analyzable output is ESM output — anything else is a different feature.
+		if (!this.isModule()) return false;
+		// Build-time execution keeps the runtime form — `import.meta` does not parse in its
+		// vm wrapper. Unreported on purpose: its chunk graph shares the real module graph.
+		if (chunkGraph && chunkGraph.buildTimeExecution) return false;
+		const { outputOptions } = this;
+		// One loader per runtime serves every wasm module in it, so the answer has to
+		// hold compilation-wide — else a baked url reaches the id-and-hash signature.
+		const scoped = form !== "wasm" && form !== "wasm-relative";
+		// Resolved once: concatenation may have absorbed the module that wrote the
+		// reference, and both the scope below and the worker loop ask about the same one.
+		const placedModule =
+			scoped && module !== undefined
+				? getConcatenatedModule().getChunkGraphModule(this.compilation, module)
+				: undefined;
+		// A reassigned `__webpack_public_path__` cannot reach a baked literal, and `.p`
+		// belongs to a runtime, so only the runtimes it reaches keep the runtime form.
+		// `url-inline` is unaffected: that form falls back to `.p + `, which reads
+		// the reassigned value at the call site rather than through the wrapper.
+		if (
+			form !== "url-inline" &&
+			APIPlugin.runtimeUsesPublicPathOverride(
+				this.compilation,
+				scoped ? chunkGraph : undefined,
+				placedModule
+			)
+		) {
+			this._analyzableBailout(
+				module,
+				"__webpack_public_path__ is reassigned in a runtime this module belongs to"
+			);
+			return false;
+		}
+
+		if (form === "import") {
+			// Read through a native `import()`, or it is not this feature at all.
+			if (outputOptions.chunkFormat !== "module") {
+				this._analyzableBailout(
+					module,
+					`output.chunkFormat is ${JSON.stringify(outputOptions.chunkFormat)}, so chunks are not read through a native import()`
+				);
+				return false;
+			}
+			if (outputOptions.importFunctionName !== "import") {
+				this._analyzableBailout(
+					module,
+					`output.importFunctionName is ${JSON.stringify(outputOptions.importFunctionName)}, so the call site is not a native import()`
+				);
+				return false;
+			}
+			// A worker loading its own chunks some other way keeps that runtime; one on
+			// `import` uses the same ESM loader as the main graph, so it can be analyzable.
+			for (const originChunk of /** @type {ChunkGraph} */ (
+				chunkGraph
+			).getModuleChunksIterable(/** @type {Module} */ (placedModule))) {
+				const entryOptions = originChunk.getEntryOptions();
+				if (!entryOptions || !entryOptions.worker) continue;
+				// `WorkerAndWorkletPlugin` always seeds this from `output.workerChunkLoading`.
+				if (entryOptions.chunkLoading !== "import") {
+					this._analyzableBailout(
+						module,
+						`this worker loads its chunks with ${JSON.stringify(entryOptions.chunkLoading)}, not "import"`
+					);
+					return false;
+				}
+			}
+			return true;
+		}
+
+		// `url-inline` asks whether the file can be named at the call site at all, and
+		// its `.p + ` fallback spells that without `import.meta` — so neither gate
+		// below rules it out, and the asset's javascript wrapper stays dropped.
+		if (form === "url-inline") return true;
+		// `import.meta` is ESM syntax the target has to read. A chunk `import()` is
+		// emitted by the module chunk loader either way, so only the url forms check.
+		if (!this.supportsEcmaScriptModuleSyntax()) {
+			this._analyzableBailout(
+				module,
+				"output.environment.module is false, so the target does not read the import.meta this form needs"
+			);
+			return false;
+		}
+		// `eval` devtool wraps each module in `eval(...)`, where `import.meta` is a
+		// syntax error. A literal `import()` survives it, so only the url forms check.
+		const { devtool } = this.compilation.options;
+		if (typeof devtool === "string" && devtool.includes("eval")) {
+			this._analyzableBailout(
+				module,
+				`devtool ${JSON.stringify(devtool)} wraps the module in eval(), where import.meta does not parse`
+			);
+			return false;
+		}
+		if (form === "url") return true;
+		// A bare relative URL is what `__webpack_require__.p + path` means only under an
+		// `auto` public path; anything else has to be baked, which is the "wasm" form.
+		if (form === "wasm-relative") {
+			if (outputOptions.publicPath === "auto") return true;
+			this._analyzableBailout(
+				module,
+				"output.publicPath is set, so a bare relative url no longer means what the public path would have resolved to"
+			);
+			return false;
+		}
+
+		// The rest is the `"wasm"` form: whether the whole binary url can be spelled
+		// here, rather than built at runtime under an `import.meta.url` base.
+		const { publicPath, webassemblyModuleFilename } = outputOptions;
+		if (publicPath !== "auto") {
+			// A public path that needs no base names the same place from the chunk as from
+			// the document, so a literal spells what `fetch` would have reached.
+			if (this._publicPathNeedsNoBase()) {
+				// Settled no earlier than the hash it reads or is called with, so it is
+				// baked only where the deferred pass may finish it.
+				if (this._resolvePublicPathPrefix(publicPath, module) === null) {
+					return false;
+				}
+			} else if (
+				this._anyWasmChunkFetches(runtime) &&
+				!this._wasmFetchAnchorIsKnown(runtime)
+			) {
+				this._analyzableBailout(
+					module,
+					"output.publicPath needs a base, and no one path back to the document fits every chunk `fetch` reads it from"
+				);
+				return false;
+			}
+		}
+		// The compilation hash is settled after code generation, so a name carrying one
+		// is baked only where the deferred pass can fill it in. `[hash]` is the module's
+		// own here, which code generation already knows.
+		if (
+			getTemplatedPathPlugin()
+				.getPresentKinds(/** @type {string} */ (webassemblyModuleFilename))
+				.has("fullhash") &&
+			!this._canDeferAnalyzableName()
+		) {
+			this._analyzableBailout(module, DEFER_BAILOUT);
+			return false;
+		}
+		return true;
+	}
+
+	/**
+	 * Records why a reference kept the runtime form, on the module that wrote it —
+	 * the channel `ModuleConcatenationPlugin` already reports through, so it reaches
+	 * `stats.optimizationBailout`. Silent unless the build asked for ESM output, where
+	 * alone the answer is actionable; deduplicated because a module may write many.
+	 * @param {Module | undefined} module the module the reference is emitted into
+	 * @param {string} reason why no literal could be baked
+	 * @returns {void}
+	 */
+	_analyzableBailout(module, reason) {
+		if (module === undefined || !this.outputOptions.module) return;
+		const text = `Analyzable ESM bailout: ${reason}`;
+		const bailouts =
+			this.compilation.moduleGraph.getOptimizationBailout(module);
+		for (const existing of bailouts) {
+			if (existing === text) return;
+		}
+		bailouts.push(text);
+	}
+
+	/**
+	 * Whether a name that code generation cannot settle may be reserved as a stand-in
+	 * and filled in once the hashes exist. Substituting rewrites a chunk after its own
+	 * content hash was taken, so either `RealContentHashPlugin` has to bring the two
+	 * back in line, or no emitted javascript may be named by its content in the first
+	 * place — with a name like `[name].js` there is nothing to go stale. Asked of the
+	 * names the chunks actually carry rather than of `output`, so a template nothing is
+	 * emitted under does not rule the rewrite out and a `chunk.filenameTemplate` does
+	 * not slip past it.
+	 * @param {Iterable=} chunks the chunks the stand-in is written into; omit
+	 * where the answer has to hold for the whole compilation, as it does for a gate a
+	 * runtime module asks from the other end
+	 * @returns {boolean} true when deferring is safe
+	 */
+	_canDeferAnalyzableName(chunks) {
+		if (chunks === undefined) {
+			// Memoized: the chunk graph is settled before any caller asks compilation-wide.
+			if (this._javascriptNamedWithoutContent === undefined) {
+				this._javascriptNamedWithoutContent = this._canDeferAnalyzableName(
+					this.compilation.chunks
+				);
+			}
+			return this._javascriptNamedWithoutContent;
+		}
+		let found = false;
+		for (const chunk of chunks) {
+			found = true;
+			if (!this._chunkNameIndependentOfContent(chunk)) return false;
+		}
+		// None at all names nothing to reason about — the asset is unknown, not safe.
+		return found;
+	}
+
+	/**
+	 * Whether the name this chunk is emitted under is settled before its own content
+	 * is, which is what leaves it right after a later rewrite. A filename function is
+	 * asked rather than assumed to read a hash — the same two probes the chunk
+	 * specifier is resolved with. Memoized per chunk: answering calls that function
+	 * twice, and a reference asks once for every chunk it is written into.
+	 * @param {Chunk} chunk a chunk the stand-in is written into
+	 * @returns {boolean} true when the name does not move with the content
+	 */
+	_chunkNameIndependentOfContent(chunk) {
+		const cached = this._chunkNamedWithoutContent.get(chunk);
+		if (cached !== undefined) return cached;
+		const template = this._resolveChunkFilenameTemplate(
+			JavascriptModulesPlugin.getChunkFilenameTemplate(
+				chunk,
+				this.outputOptions
+			),
+			chunk
+		);
+		let independent = false;
+		if (typeof template === "string") {
+			const kinds = getTemplatedPathPlugin().getPresentKinds(template);
+			// `[chunkhash]` is taken from this chunk's own modules and nothing repairs it
+			// afterwards, so such a name stays put while the reference it holds moves.
+			independent =
+				!kinds.has("chunkhash") &&
+				(!kinds.has("contenthash") ||
+					Boolean(this.compilation.options.optimization.realContentHash));
+		}
+		this._chunkNamedWithoutContent.set(chunk, independent);
+		return independent;
+	}
+
+	/**
+	 * Builds the analyzable `new URL(specifier, import.meta.url)` expression the ESM
+	 * wasm/asset loader backends use to reference an emitted binary relative to the
+	 * current module (via `output.importMetaName`) instead of the runtime public-path
+	 * global — the form other bundlers and webpack itself can statically follow.
+	 * @param {string} specifier already-rendered URL argument (a literal or expression)
+	 * @returns {string} the `new URL(...)` expression
+	 */
+	importMetaUrl(specifier) {
+		return `new URL(${specifier}, ${this.outputOptions.importMetaName}.url)`;
+	}
+
+	/**
+	 * Whether a baked asset url resolves against the entry `baseUri` at all. A public
+	 * path that reaches the same place from any base never reads `.b` — and `auto`
+	 * resolves to an absolute url too — while output with no baked form has nothing to
+	 * resolve. Where this is false, what `baseUri` is set to cannot reach the generated
+	 * code, so it must not reach the module hash either: `URLDependency.updateHash`
+	 * asks this before contributing one.
+	 * @returns {boolean} true when the base can change what is generated
+	 */
+	analyzableUrlReadsBaseUri() {
+		if (!this.isModule()) return false;
+		const { publicPath } = this.outputOptions;
+		if (publicPath === "auto") return false;
+		return !this._publicPathNeedsNoBase();
+	}
+
+	/**
+	 * Whether `output.publicPath` reaches the same place from any base, so a literal may
+	 * carry it without walking back to the output root first. One whose shape nothing
+	 * answers needs a base as far as anything here can tell. Never asked of `auto`,
+	 * which is no path of its own.
+	 * @returns {boolean} true when no base is needed
+	 */
+	_publicPathNeedsNoBase() {
+		const shape = this._publicPathShape();
+		return shape !== undefined && isBaseIndependent(shape);
+	}
+
+	/**
+	 * Static literal specifier (already quoted) for the `new URL(, import.meta.url)`
+	 * an asset reference bakes to, or `null` to keep the runtime form. Unlike a wasm
+	 * binary, the runtime resolves an asset url against `__webpack_require__.b` — the
+	 * output root, or an entry `baseUri` where one is set — so that base is settled here
+	 * before the rest of the name is.
+	 * @param {Module} module the module the reference is emitted into
+	 * @param {ChunkGraph} chunkGraph the chunk graph
+	 * @param {string} filename the asset's name, relative to the output root
+	 * @param {RuntimeSpec} runtime the runtime the code is generated for
+	 * @returns {string | null} a quoted literal, or `null` to fall back
+	 */
+	getAnalyzableAssetUrl(module, chunkGraph, filename, runtime) {
+		const { publicPath } = this.outputOptions;
+		const base = this.analyzableUrlReadsBaseUri()
+			? this.entryBaseUri(runtime)
+			: undefined;
+		if (base !== undefined) {
+			if (base === null) {
+				this._analyzableBailout(
+					module,
+					"the entries this module is generated for set different baseUri values, so no one url resolves for all of them"
+				);
+				return null;
+			}
+			// A relative one is no base of its own: the runtime reads it against the
+			// chunk, so the literal spells it there rather than resolving against it.
+			if (isChunkRelativeBaseUri(base)) {
+				return this._getAnalyzableFileSpecifier(
+					module,
+					chunkGraph,
+					[["literal", filename]],
+					true,
+					base
+				);
+			}
+			if (!isAbsoluteBaseUri(base)) {
+				this._analyzableBailout(
+					module,
+					"the entry baseUri is protocol-relative, so its scheme is only known where the chunk is loaded"
+				);
+				return null;
+			}
+			const chunks = this._moduleChunks(module, chunkGraph);
+			const parts = this._resolvePublicPathPrefix(publicPath, module, chunks);
+			if (parts === null) return null;
+			// Resolved here rather than against the output root, and only an absolute base
+			// settles it — a relative one has no base of its own to be read against.
+			const spelled = literalText(parts);
+			// A hash the deferred pass still has to fill in cannot be resolved against the
+			// base here: the fill would land inside an already-resolved url, where a hash
+			// opening with a letter reads as a scheme and drops the base entirely.
+			const text =
+				spelled !== null &&
+				!getAnalyzableChunkHashPlugin().hasReservedFullHash(spelled)
+					? spelled
+					: null;
+			/** @type {URL} */
+			let resolved;
+			try {
+				// Empty where only the deferred pass can spell the rest, which still settles
+				// whether the base itself is one anything may be resolved against.
+				resolved = new URL(text === null ? "" : text + filename, base);
+			} catch (_error) {
+				this._analyzableBailout(
+					module,
+					`an entry sets a baseUri of ${JSON.stringify(base)}, which is not absolute, so no url can be resolved against it here`
+				);
+				return null;
+			}
+			if (text !== null) return toJsStringLiteral(resolved.href);
+			if (!this._canDeferAnalyzableName(chunks)) {
+				this._analyzableBailout(module, DEFER_BAILOUT);
+				return null;
+			}
+			return toJsStringLiteral(
+				getAnalyzableChunkHashPlugin().reserveSpecifier([
+					["base", base],
+					...parts,
+					["literal", filename]
+				])
+			);
+		}
+		return this._getAnalyzableFileSpecifier(
+			module,
+			chunkGraph,
+			[["literal", filename]],
+			true
+		);
+	}
+
+	/**
+	 * The `baseUri` the entries this code runs under agree on, which replaces the output
+	 * root an asset url resolves against. Asked per runtime, because that is what the
+	 * module is generated for: two entries with different bases each get their own
+	 * source. `undefined` when none of them sets one, `null` when they disagree — an
+	 * entry that omits it disagrees with one that sets it.
+	 * @param {RuntimeSpec} runtime the runtime the code is generated for
+	 * @returns {string | null | undefined} the base those entries agree on
+	 */
+	entryBaseUri(runtime) {
+		const key = getRuntimeKey(runtime);
+		if (this._entryBaseUriByRuntime.has(key)) {
+			return this._entryBaseUriByRuntime.get(key);
+		}
+		/** @type {string | null | undefined} */
+		let base;
+		let found = false;
+		for (const chunk of this.compilation.chunks) {
+			const entryOptions = chunk.getEntryOptions();
+			// An entry this code cannot run under has no say in the base it resolves to.
+			if (
+				!entryOptions ||
+				intersectRuntime(chunk.runtime, runtime) === undefined
+			) {
+				continue;
+			}
+			if (found && base !== entryOptions.baseUri) {
+				base = null;
+				break;
+			}
+			base = entryOptions.baseUri;
+			found = true;
+		}
+		this._entryBaseUriByRuntime.set(key, base);
+		return base;
+	}
+
+	/**
+	 * `output.publicPath` as the constant it will be, for code that would otherwise read
+	 * `__webpack_require__.p` for a value that never changes. `undefined` when only the
+	 * hash could say, or when a runtime reassigns `__webpack_public_path__` — then the
+	 * global is the only thing that knows.
+	 * @returns {string | undefined} the settled public path
+	 */
+	constantPublicPath() {
+		const { publicPath } = this.outputOptions;
+		if (
+			publicPath === "auto" ||
+			APIPlugin.usesRuntimePublicPathOverride(this.compilation)
+		) {
+			return undefined;
+		}
+		if (typeof publicPath === "function") {
+			const resolved = this._resolveHashIndependent(
+				publicPath,
+				this._publicPathShape()
+			);
+			return resolved === null ? undefined : resolved;
+		}
+		return publicPath.includes("[") ? undefined : publicPath;
+	}
+
+	/**
+	 * Whether a chunk loads WebAssembly through `fetch`, which is the only loader the
+	 * public path reaches: `readFile` resolves the binary's name against the chunk it is
+	 * read from, exactly as a baked literal does, so a public path is irrelevant to it.
+	 * Answered per runtime rather than per chunk on purpose — a module's generated source
+	 * and the runtime module of every chunk holding it have to agree on the shape, and
+	 * they ask from opposite ends; one loader serves a whole runtime, so that is the
+	 * finest scope on which they can. Without a runtime, or where the runtimes cannot be
+	 * told apart, the answer covers the compilation.
+	 * @param {RuntimeSpec=} runtime the runtime being asked about
+	 * @returns {boolean} true when a chunk of that runtime fetches its binaries
+	 */
+	_anyWasmChunkFetches(runtime) {
+		const fetching = this._wasmFetchingGroups();
+		if (fetching === undefined) return false;
+		// Without a runtime to place it in there is nothing to narrow by, so any chunk
+		// answers for all of them.
+		if (runtime === undefined) return true;
+		const groups = this._wasmRuntimeGroups();
+		if (typeof runtime === "string") {
+			return fetching.has(this._wasmGroupOf(groups, runtime));
+		}
+		for (const key of runtime) {
+			if (fetching.has(this._wasmGroupOf(groups, key))) return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Whether every entry of a wasm runtime group reads its binaries with `fetch`. Only
+	 * an entry names a loader, so the scan is of entries, as `_wasmFetchingGroups` is.
+	 * @returns {Map} whether each group fetches and nothing else
+	 */
+	_wasmGroupOnlyFetches() {
+		if (this._wasmGroupOnlyFetchesMap === undefined) {
+			const groups = this._wasmRuntimeGroups();
+			/** @type {Map} */
+			const only = new Map();
+			for (const chunk of this.compilation.chunks) {
+				if (!chunk.getEntryOptions()) continue;
+				const fetches = this._chunkWasmLoading(chunk) === "fetch";
+				forEachRuntime(chunk.runtime, (key) => {
+					const group = this._wasmGroupOf(groups, /** @type {string} */ (key));
+					only.set(group, only.get(group) !== false && fetches);
+				});
+			}
+			this._wasmGroupOnlyFetchesMap = only;
+		}
+		return this._wasmGroupOnlyFetchesMap;
+	}
+
+	/**
+	 * Whether a public path needing a base can be spelled from the chunk every binary of
+	 * a runtime sits in — `fetch` reads it against the document, so a literal climbs back
+	 * there first. Per group, as `_anyWasmChunkFetches` is, since the two ends must agree.
+	 * @param {RuntimeSpec=} runtime the runtime being asked about
+	 * @returns {boolean} true when every binary of that group can be spelled
+	 */
+	_wasmFetchAnchorIsKnown(runtime) {
+		if (this._wasmAnchorKnownByGroup === undefined) {
+			const { chunkGraph, modules } = this.compilation;
+			const publicPath = /** @type {PublicPath} */ (
+				this.outputOptions.publicPath
+			);
+			const groups = this._wasmRuntimeGroups();
+			/** @type {Map} */
+			const known = new Map();
+			for (const module of modules) {
+				if (module.type !== WEBASSEMBLY_MODULE_TYPE_ASYNC) continue;
+				const spellable =
+					this._modulePlacement(module, chunkGraph).loaded !== null &&
+					this._resolvePublicPathPrefix(publicPath, module) !== null;
+				for (const moduleRuntime of chunkGraph.getModuleRuntimes(module)) {
+					forEachRuntime(moduleRuntime, (key) => {
+						const group = this._wasmGroupOf(
+							groups,
+							/** @type {string} */ (key)
+						);
+						known.set(group, known.get(group) !== false && spellable);
+					});
+				}
+			}
+			// A group one entry reads with `readFile` cannot take a prefix meant for the
+			// document, and only an entry names its loader.
+			for (const [group, only] of this._wasmGroupOnlyFetches()) {
+				if (!only) known.set(group, false);
+			}
+			this._wasmAnchorKnownByGroup = known;
+		}
+		const known = this._wasmAnchorKnownByGroup;
+		// No runtime to place it in leaves every group answering, so one that cannot be
+		// spelled speaks for all of them.
+		if (runtime === undefined) {
+			for (const value of known.values()) if (!value) return false;
+			return known.size > 0;
+		}
+		const groups = this._wasmRuntimeGroups();
+		if (typeof runtime === "string") {
+			return known.get(this._wasmGroupOf(groups, runtime)) === true;
+		}
+		for (const key of runtime) {
+			if (known.get(this._wasmGroupOf(groups, key)) !== true) return false;
+		}
+		return true;
+	}
+
+	/**
+	 * The runtime groups an entry of which asked for the `fetch` loader, or `undefined`
+	 * where none did — the cheap answer for a compilation with no fetching entry at all,
+	 * which is every non-web target. Only an entry names a loader; every other chunk of
+	 * the runtime is served by the one its entry asked for.
+	 * @returns {Set | undefined} the groups that fetch
+	 */
+	_wasmFetchingGroups() {
+		if (this._wasmFetchingGroupSet === undefined) {
+			/** @type {Set} */
+			const fetching = new Set();
+			/** @type {Map | undefined} */
+			let groups;
+			for (const chunk of this.compilation.chunks) {
+				// Only an entry names a loader; every other chunk of the runtime is served
+				// by the one its entry asked for, so it says nothing on its own.
+				if (!chunk.getEntryOptions()) continue;
+				if (this._chunkWasmLoading(chunk) !== "fetch") continue;
+				// Built only once something fetches, so nothing pays for the grouping.
+				if (groups === undefined) groups = this._wasmRuntimeGroups();
+				const named = /** @type {Map} */ (groups);
+				forEachRuntime(chunk.runtime, (key) => {
+					fetching.add(this._wasmGroupOf(named, /** @type {string} */ (key)));
+				});
+			}
+			this._wasmFetchingGroupSet = fetching.size === 0 ? false : fetching;
+		}
+		return this._wasmFetchingGroupSet === false
+			? undefined
+			: this._wasmFetchingGroupSet;
+	}
+
+	/**
+	 * Runtime keys mapped to the group they answer with, named by one key of it. Code
+	 * generation runs once for runtimes a module hashes alike in, and nothing in a
+	 * binary's hash knows which loader will read it — so a binary two runtimes reach
+	 * carries one shape into both, and a third runtime sharing another binary with
+	 * either is pulled in after them. A key in no group answers as itself. Asked of the
+	 * same modules the loaders are created for, and only once per compilation.
+	 * @returns {Map} runtime key to the group it answers with
+	 */
+	_wasmRuntimeGroups() {
+		if (this._wasmRuntimeGroupsByKey === undefined) {
+			const { chunkGraph } = this.compilation;
+			/** @type {Map} */
+			const parent = new Map();
+			/**
+			 * @param {string} key runtime key
+			 * @returns {string} the key naming its group
+			 */
+			const find = (key) => {
+				let root = key;
+				let seen = parent.get(root);
+				while (seen !== undefined && seen !== root) {
+					root = seen;
+					seen = parent.get(root);
+				}
+				// Path compression keeps a long share chain from costing more than once.
+				let walk = key;
+				let next = parent.get(walk);
+				while (next !== undefined && next !== root) {
+					parent.set(walk, root);
+					walk = next;
+					next = parent.get(walk);
+				}
+				return root;
+			};
+			/** @type {string[]} */
+			const keys = [];
+			/**
+			 * @param {RuntimeSpec} runtime a runtime the module is generated for
+			 * @returns {void}
+			 */
+			const collectKeys = (runtime) => {
+				forEachRuntime(runtime, (key) => {
+					keys.push(/** @type {string} */ (key));
+				});
+			};
+			for (const module of this.compilation.modules) {
+				if (module.type !== WEBASSEMBLY_MODULE_TYPE_ASYNC) continue;
+				keys.length = 0;
+				for (const runtime of chunkGraph.getModuleRuntimes(module)) {
+					collectKeys(runtime);
+				}
+				for (const key of keys) {
+					if (!parent.has(key)) parent.set(key, key);
+				}
+				// Every runtime this binary reaches answers with the first one.
+				for (let i = 1; i < keys.length; i++) {
+					parent.set(find(keys[i]), find(keys[0]));
+				}
+			}
+			// Flattened once, so a lookup is a single `get` rather than a chain walk.
+			for (const key of parent.keys()) parent.set(key, find(key));
+			this._wasmRuntimeGroupsByKey = parent;
+		}
+		return this._wasmRuntimeGroupsByKey;
+	}
+
+	/**
+	 * @param {Map} groups runtime key to the group it answers with
+	 * @param {string} key a runtime key
+	 * @returns {string} the group it answers with, or itself when it shares nothing
+	 */
+	_wasmGroupOf(groups, key) {
+		const group = groups.get(key);
+		// An entry named "" makes "" a runtime key, so test absence, not a falsy value.
+		return group === undefined ? key : group;
+	}
+
+	supportTemplateLiteral() {
+		return this.outputOptions.environment.templateLiteral;
+	}
+
+	supportNodePrefixForCoreModules() {
+		return this.outputOptions.environment.nodePrefixForCoreModules;
+	}
+
+	/**
+	 * Renders node prefix for core module.
+	 * @param {string} mod a module
+	 * @returns {string} a module with `node:` prefix when supported, otherwise an original name
+	 */
+	renderNodePrefixForCoreModule(mod) {
+		return this.outputOptions.environment.nodePrefixForCoreModules
+			? `"node:${mod}"`
+			: `"${mod}"`;
+	}
+
+	/**
+	 * Renders return const when it is supported, otherwise let when supported, otherwise var.
+	 * @returns {"const" | "let" | "var"} return `const` when it is supported, otherwise `let` when supported, otherwise `var`
+	 */
+	renderConst() {
+		return this.supportsConst() ? "const" : this.supportsLet() ? "let" : "var";
+	}
+
+	/**
+	 * Renders return let when it is supported, otherwise var.
+	 * @returns {"let" | "var"} return `let` when it is supported, otherwise `var`
+	 */
+	renderLet() {
+		return this.supportsLet() ? "let" : "var";
+	}
+
+	/**
+	 * Returning function.
+	 * @param {string} returnValue return value
+	 * @param {string} args arguments
+	 * @returns {string} returning function
+	 */
+	returningFunction(returnValue, args = "") {
+		return this.supportsArrowFunction()
+			? `(${args}) => (${returnValue})`
+			: `function(${args}) { return ${returnValue}; }`;
+	}
+
+	/**
+	 * Returns basic function.
+	 * @param {string} args arguments
+	 * @param {string | string[]} body body
+	 * @returns {string} basic function
+	 */
+	basicFunction(args, body) {
+		return this.supportsArrowFunction()
+			? `(${args}) => {\n${Template.indent(body)}\n}`
+			: `function(${args}) {\n${Template.indent(body)}\n}`;
+	}
+
+	/**
+	 * Returns result expression.
+	 * @param {(string | { expr: string })[]} args args
+	 * @returns {string} result expression
+	 */
+	concatenation(...args) {
+		const len = args.length;
+
+		if (len === 2) return this._es5Concatenation(args);
+		if (len === 0) return '""';
+		if (len === 1) {
+			return typeof args[0] === "string"
+				? JSON.stringify(args[0])
+				: `"" + ${args[0].expr}`;
+		}
+		if (!this.supportTemplateLiteral()) return this._es5Concatenation(args);
+
+		// cost comparison between template literal and concatenation:
+		// both need equal surroundings: `xxx` vs "xxx"
+		// template literal has constant cost of 3 chars for each expression
+		// es5 concatenation has cost of 3 + n chars for n expressions in row
+		// when a es5 concatenation ends with an expression it reduces cost by 3
+		// when a es5 concatenation starts with an single expression it reduces cost by 3
+		// e. g. `${a}${b}${c}` (3*3 = 9) is longer than ""+a+b+c ((3+3)-3 = 3)
+		// e. g. `x${a}x${b}x${c}x` (3*3 = 9) is shorter than "x"+a+"x"+b+"x"+c+"x" (4+4+4 = 12)
+
+		let templateCost = 0;
+		let concatenationCost = 0;
+
+		let lastWasExpr = false;
+		for (const arg of args) {
+			const isExpr = typeof arg !== "string";
+			if (isExpr) {
+				templateCost += 3;
+				concatenationCost += lastWasExpr ? 1 : 4;
+			}
+			lastWasExpr = isExpr;
+		}
+		if (lastWasExpr) concatenationCost -= 3;
+		if (typeof args[0] !== "string" && typeof args[1] === "string") {
+			concatenationCost -= 3;
+		}
+
+		if (concatenationCost <= templateCost) return this._es5Concatenation(args);
+
+		return `\`${args
+			.map((arg) => (typeof arg === "string" ? arg : `\${${arg.expr}}`))
+			.join("")}\``;
+	}
+
+	/**
+	 * Returns result expression.
+	 * @param {(string | { expr: string })[]} args args (len >= 2)
+	 * @returns {string} result expression
+	 * @private
+	 */
+	_es5Concatenation(args) {
+		const str = args
+			.map((arg) => (typeof arg === "string" ? JSON.stringify(arg) : arg.expr))
+			.join(" + ");
+
+		// when the first two args are expression, we need to prepend "" + to force string
+		// concatenation instead of number addition.
+		return typeof args[0] !== "string" && typeof args[1] !== "string"
+			? `"" + ${str}`
+			: str;
+	}
+
+	/**
+	 * Expression function.
+	 * @param {string} expression expression
+	 * @param {string} args arguments
+	 * @returns {string} expression function code
+	 */
+	expressionFunction(expression, args = "") {
+		return this.supportsArrowFunction()
+			? `(${args}) => (${expression})`
+			: `function(${args}) { ${expression}; }`;
+	}
+
+	/**
+	 * Returns empty function code.
+	 * @returns {string} empty function code
+	 */
+	emptyFunction() {
+		return this.supportsArrowFunction() ? "x => {}" : "function() {}";
+	}
+
+	/**
+	 * Guards an access/call on `object` with optional chaining when supported,
+	 * otherwise an equivalent `&&` short-circuit. `object` is evaluated twice in
+	 * the fallback, so it must be side-effect free.
+	 * @param {string} object base expression (side-effect free)
+	 * @param {string} access continuation after the optional point, e.g. `()`, `prop`, `method(arg)` or `[key]`
+	 * @returns {string} guarded access expression
+	 */
+	optionalChaining(object, access) {
+		if (this.supportsOptionalChaining()) {
+			return `${object}?.${access}`;
+		}
+		const sep = access[0] === "(" || access[0] === "[" ? "" : ".";
+		return `${object} && ${object}${sep}${access}`;
+	}
+
+	/**
+	 * Reads a node builtin via `process.getBuiltinModule()`, guarded to stay falsy off node so universal `["node", "web"]` bundles don't crash (also falsy on node <22.3).
+	 * @param {string} request builtin module request as a JS string expression, e.g. from `renderNodePrefixForCoreModule`
+	 * @param {string=} access member/call chain appended to the module, e.g. `.Worker` or `.createRequire(url)`
+	 * @returns {string} guarded expression
+	 */
+	getBuiltinModule(request, access = "") {
+		const getter = `process.getBuiltinModule(${request})${access}`;
+		if (this.outputOptions.environment.nodeBuiltinModuleGetter) {
+			return `typeof process !== "undefined" && ${getter}`;
+		}
+		return `typeof process !== "undefined" && typeof process.getBuiltinModule === "function" && ${getter}`;
+	}
+
+	/**
+	 * Renders an object-literal method, using method shorthand when supported
+	 * and falling back to a `prop: function/arrow` property otherwise.
+	 * @param {string} prop property name (or computed key like `[x]`)
+	 * @param {string} args arguments
+	 * @param {string | string[]} body body
+	 * @returns {string} method code
+	 */
+	method(prop, args, body) {
+		return this.supportsMethodShorthand()
+			? `${prop}(${args}) {\n${Template.indent(body)}\n}`
+			: `${prop}: ${this.basicFunction(args, body)}`;
+	}
+
+	/**
+	 * Returns an own-property check, using `Object.hasOwn` when supported and
+	 * falling back to `Object.prototype.hasOwnProperty.call` otherwise.
+	 * @param {string} object object expression
+	 * @param {string} property property expression
+	 * @returns {string} own-property check expression
+	 */
+	objectHasOwn(object, property) {
+		return this.supportsObjectHasOwn()
+			? `Object.hasOwn(${object}, ${property})`
+			: `Object.prototype.hasOwnProperty.call(${object}, ${property})`;
+	}
+
+	/**
+	 * Returns a self-defaulting assignment, using the `||=` logical assignment
+	 * operator when supported and falling back to `target = target || value`
+	 * otherwise. `target` is evaluated twice in the fallback, so it must be
+	 * side-effect free. The expression evaluates to the resulting value.
+	 * Models `||` only, so `target` must never hold a legitimate falsy value
+	 * (`0`, `""`, `false`) — it would be overwritten; use it for object/array defaults.
+	 * @param {string} target assignment target (side-effect free)
+	 * @param {string} value default value expression
+	 * @returns {string} assignment expression
+	 */
+	assignOr(target, value) {
+		return this.supportsLogicalAssignment()
+			? `${target} ||= ${value}`
+			: `${target} = ${target} || ${value}`;
+	}
+
+	/**
+	 * Returns destructure array code.
+	 * @param {string[]} items items
+	 * @param {string} value value
+	 * @returns {string} destructure array code
+	 */
+	destructureArray(items, value) {
+		const decl = this.renderLet();
+		return this.supportsDestructuring()
+			? `${decl} [${items.join(", ")}] = ${value};`
+			: Template.asString(
+					items.map((item, i) => `${decl} ${item} = ${value}[${i}];`)
+				);
+	}
+
+	/**
+	 * Destructure object.
+	 * @param {string[]} items items
+	 * @param {string} value value
+	 * @returns {string} destructure object code
+	 */
+	destructureObject(items, value) {
+		const decl = this.renderLet();
+		return this.supportsDestructuring()
+			? `${decl} {${items.join(", ")}} = ${value};`
+			: Template.asString(
+					items.map(
+						(item) => `${decl} ${item} = ${value}${propertyAccess([item])};`
+					)
+				);
+	}
+
+	/**
+	 * Returns iIFE code.
+	 * @param {string} args arguments
+	 * @param {string} body body
+	 * @returns {string} IIFE code
+	 */
+	iife(args, body) {
+		return `(${this.basicFunction(args, body)})()`;
+	}
+
+	/**
+	 * Returns for each code.
+	 * @param {string} variable variable
+	 * @param {string} array array
+	 * @param {string | string[]} body body
+	 * @returns {string} for each code
+	 */
+	forEach(variable, array, body) {
+		return this.supportsForOf()
+			? `for(const ${variable} of ${array}) {\n${Template.indent(body)}\n}`
+			: `${array}.forEach(function(${variable}) {\n${Template.indent(
+					body
+				)}\n});`;
+	}
+
+	/**
+	 * Returns comment.
 	 * @param {object} options Information content of the comment
 	 * @param {string=} options.request request string used originally
-	 * @param {string=} options.chunkName name of the chunk referenced
+	 * @param {(string | null)=} options.chunkName name of the chunk referenced
 	 * @param {string=} options.chunkReason reason information of the chunk
 	 * @param {string=} options.message additional message
 	 * @param {string=} options.exportName name of the export
 	 * @returns {string} comment
 	 */
 	comment({ request, chunkName, chunkReason, message, exportName }) {
+		/** @type {string} */
 		let content;
 		if (this.outputOptions.pathinfo) {
 			content = [message, request, chunkName, chunkReason]
 				.filter(Boolean)
-				.map(item => this.requestShortener.shorten(item))
+				.map((item) => this.requestShortener.shorten(item))
 				.join(" | ");
 		} else {
 			content = [message, chunkName, chunkReason]
 				.filter(Boolean)
-				.map(item => this.requestShortener.shorten(item))
+				.map((item) => this.requestShortener.shorten(item))
 				.join(" | ");
 		}
 		if (!content) return "";
 		if (this.outputOptions.pathinfo) {
-			return Template.toComment(content) + " ";
-		} else {
-			return Template.toNormalComment(content) + " ";
+			return `${Template.toComment(content)} `;
 		}
+		return `${Template.toNormalComment(content)} `;
 	}
 
-	throwMissingModuleErrorFunction({ request }) {
+	/**
+	 * Throw missing module error block.
+	 * @param {object} options generation options
+	 * @param {string=} options.request request string used originally
+	 * @returns {string} generated error block
+	 */
+	throwMissingModuleErrorBlock({ request }) {
 		const err = `Cannot find module '${request}'`;
-		return `function webpackMissingModule() { var e = new Error(${JSON.stringify(
+		return `${this.renderConst()} e = new Error(${JSON.stringify(
 			err
-		)}); e.code = 'MODULE_NOT_FOUND'; throw e; }`;
+		)}); e.code = 'MODULE_NOT_FOUND'; throw e;`;
+	}
+
+	/**
+	 * Throw missing module error function.
+	 * @param {object} options generation options
+	 * @param {string=} options.request request string used originally
+	 * @returns {string} generated error function
+	 */
+	throwMissingModuleErrorFunction({ request }) {
+		return `function webpackMissingModule() { ${this.throwMissingModuleErrorBlock(
+			{ request }
+		)} }`;
 	}
 
+	/**
+	 * Returns generated error IIFE.
+	 * @param {object} options generation options
+	 * @param {string=} options.request request string used originally
+	 * @returns {string} generated error IIFE
+	 */
 	missingModule({ request }) {
-		return `!(${this.throwMissingModuleErrorFunction({ request })}())`;
+		return `Object(${this.throwMissingModuleErrorFunction({ request })}())`;
 	}
 
+	/**
+	 * Missing module statement.
+	 * @param {object} options generation options
+	 * @param {string=} options.request request string used originally
+	 * @returns {string} generated error statement
+	 */
 	missingModuleStatement({ request }) {
 		return `${this.missingModule({ request })};\n`;
 	}
 
+	/**
+	 * Missing module promise.
+	 * @param {object} options generation options
+	 * @param {string=} options.request request string used originally
+	 * @returns {string} generated error code
+	 */
 	missingModulePromise({ request }) {
 		return `Promise.resolve().then(${this.throwMissingModuleErrorFunction({
 			request
 		})})`;
 	}
 
-	moduleId({ module, request }) {
+	/**
+	 * Returns the code.
+	 * @param {object} options options object
+	 * @param {ChunkGraph} options.chunkGraph the chunk graph
+	 * @param {Module} options.module the module
+	 * @param {string=} options.request the request that should be printed as comment
+	 * @param {string=} options.idExpr expression to use as id expression
+	 * @param {"expression" | "promise" | "statements"} options.type which kind of code should be returned
+	 * @returns {string} the code
+	 */
+	weakError({ module, chunkGraph, request, idExpr, type }) {
+		const moduleId = chunkGraph.getModuleId(module);
+		const errorMessage =
+			moduleId === null
+				? JSON.stringify("Module is not available (weak dependency)")
+				: idExpr
+					? `"Module '" + ${idExpr} + "' is not available (weak dependency)"`
+					: JSON.stringify(
+							`Module '${moduleId}' is not available (weak dependency)`
+						);
+		const comment = request ? `${Template.toNormalComment(request)} ` : "";
+		const errorStatements = `${this.renderConst()} e = new Error(${errorMessage}); ${comment}e.code = 'MODULE_NOT_FOUND'; throw e;`;
+		switch (type) {
+			case "statements":
+				return errorStatements;
+			case "promise":
+				return `Promise.resolve().then(${this.basicFunction(
+					"",
+					errorStatements
+				)})`;
+			case "expression":
+				return this.iife("", errorStatements);
+		}
+	}
+
+	/**
+	 * Returns the expression.
+	 * @param {object} options options object
+	 * @param {Module} options.module the module
+	 * @param {ChunkGraph} options.chunkGraph the chunk graph
+	 * @param {string=} options.request the request that should be printed as comment
+	 * @param {boolean=} options.weak if the dependency is weak (will create a nice error message)
+	 * @returns {string} the expression
+	 */
+	moduleId({ module, chunkGraph, request, weak }) {
 		if (!module) {
 			return this.missingModule({
 				request
 			});
 		}
-		if (module.id === null) {
+		const moduleId = chunkGraph.getModuleId(module);
+		if (moduleId === null) {
+			if (weak) {
+				return "null /* weak dependency, without id */";
+			}
 			throw new Error(
-				`RuntimeTemplate.moduleId(): Module ${module.identifier()} has no id. This should not happen.`
+				`RuntimeTemplate.moduleId(): ${noModuleIdErrorMessage(
+					module,
+					chunkGraph
+				)}`
 			);
 		}
-		return `${this.comment({ request })}${JSON.stringify(module.id)}`;
+		return `${this.comment({ request })}${JSON.stringify(moduleId)}`;
 	}
 
-	moduleRaw({ module, request }) {
+	/**
+	 * Returns the expression.
+	 * @param {object} options options object
+	 * @param {Module | null} options.module the module
+	 * @param {ChunkGraph} options.chunkGraph the chunk graph
+	 * @param {string=} options.request the request that should be printed as comment
+	 * @param {boolean=} options.weak if the dependency is weak (will create a nice error message)
+	 * @param {RuntimeRequirements} options.runtimeRequirements if set, will be filled with runtime requirements
+	 * @returns {string} the expression
+	 */
+	moduleRaw({ module, chunkGraph, request, weak, runtimeRequirements }) {
 		if (!module) {
 			return this.missingModule({
 				request
 			});
 		}
-		return `__webpack_require__(${this.moduleId({ module, request })})`;
+		const moduleId = chunkGraph.getModuleId(module);
+		if (moduleId === null) {
+			if (weak) {
+				// only weak referenced modules don't get an id
+				// we can always emit an error emitting code here
+				return this.weakError({
+					module,
+					chunkGraph,
+					request,
+					type: "expression"
+				});
+			}
+			throw new Error(
+				`RuntimeTemplate.moduleId(): ${noModuleIdErrorMessage(
+					module,
+					chunkGraph
+				)}`
+			);
+		}
+		runtimeRequirements.add(RuntimeGlobals.require);
+		return `${RuntimeGlobals.require}(${this.moduleId({
+			module,
+			chunkGraph,
+			request,
+			weak
+		})})`;
 	}
 
-	moduleExports({ module, request }) {
+	/**
+	 * Returns the expression.
+	 * @param {object} options options object
+	 * @param {Module | null} options.module the module
+	 * @param {ChunkGraph} options.chunkGraph the chunk graph
+	 * @param {string} options.request the request that should be printed as comment
+	 * @param {boolean=} options.weak if the dependency is weak (will create a nice error message)
+	 * @param {RuntimeRequirements} options.runtimeRequirements if set, will be filled with runtime requirements
+	 * @returns {string} the expression
+	 */
+	moduleExports({ module, chunkGraph, request, weak, runtimeRequirements }) {
 		return this.moduleRaw({
 			module,
-			request
+			chunkGraph,
+			request,
+			weak,
+			runtimeRequirements
 		});
 	}
 
-	moduleNamespace({ module, request, strict }) {
+	/**
+	 * Returns the expression.
+	 * @param {object} options options object
+	 * @param {Module} options.module the module
+	 * @param {ChunkGraph} options.chunkGraph the chunk graph
+	 * @param {string} options.request the request that should be printed as comment
+	 * @param {boolean=} options.strict if the current module is in strict esm mode
+	 * @param {boolean=} options.weak if the dependency is weak (will create a nice error message)
+	 * @param {RuntimeRequirements} options.runtimeRequirements if set, will be filled with runtime requirements
+	 * @returns {string} the expression
+	 */
+	moduleNamespace({
+		module,
+		chunkGraph,
+		request,
+		strict,
+		weak,
+		runtimeRequirements
+	}) {
 		if (!module) {
 			return this.missingModule({
 				request
 			});
 		}
+		if (chunkGraph.getModuleId(module) === null) {
+			if (weak) {
+				// only weak referenced modules don't get an id
+				// we can always emit an error emitting code here
+				return this.weakError({
+					module,
+					chunkGraph,
+					request,
+					type: "expression"
+				});
+			}
+			throw new Error(
+				`RuntimeTemplate.moduleNamespace(): ${noModuleIdErrorMessage(
+					module,
+					chunkGraph
+				)}`
+			);
+		}
 		const moduleId = this.moduleId({
 			module,
-			request
+			chunkGraph,
+			request,
+			weak
 		});
-		const exportsType = module.buildMeta && module.buildMeta.exportsType;
-		if (exportsType === "namespace") {
-			const rawModule = this.moduleRaw({
-				module,
-				request
-			});
-			return rawModule;
-		} else if (exportsType === "named") {
-			return `__webpack_require__.t(${moduleId}, 3)`;
-		} else if (strict) {
-			return `__webpack_require__.t(${moduleId}, 1)`;
-		} else {
-			return `__webpack_require__.t(${moduleId}, 7)`;
+		const exportsType = module.getExportsType(chunkGraph.moduleGraph, strict);
+		switch (exportsType) {
+			case "namespace":
+				return this.moduleRaw({
+					module,
+					chunkGraph,
+					request,
+					weak,
+					runtimeRequirements
+				});
+			case "default-with-named":
+				runtimeRequirements.add(RuntimeGlobals.createFakeNamespaceObject);
+				return `${RuntimeGlobals.createFakeNamespaceObject}(${moduleId}, 3)`;
+			case "default-only":
+				runtimeRequirements.add(RuntimeGlobals.createFakeNamespaceObject);
+				return `${RuntimeGlobals.createFakeNamespaceObject}(${moduleId}, 1)`;
+			case "dynamic":
+				runtimeRequirements.add(RuntimeGlobals.createFakeNamespaceObject);
+				return `${RuntimeGlobals.createFakeNamespaceObject}(${moduleId}, 7)`;
 		}
 	}
 
-	moduleNamespacePromise({ block, module, request, message, strict, weak }) {
+	/**
+	 * Module namespace promise.
+	 * @param {object} options options object
+	 * @param {ChunkGraph} options.chunkGraph the chunk graph
+	 * @param {AsyncDependenciesBlock=} options.block the current dependencies block
+	 * @param {Module} options.module the module
+	 * @param {string} options.request the request that should be printed as comment
+	 * @param {string} options.message a message for the comment
+	 * @param {boolean=} options.strict if the current module is in strict esm mode
+	 * @param {boolean=} options.weak if the dependency is weak (will create a nice error message)
+	 * @param {Dependency} options.dependency dependency
+	 * @param {RuntimeRequirements} options.runtimeRequirements if set, will be filled with runtime requirements
+	 * @param {Module=} options.originModule the module the `import()` is emitted into
+	 * @returns {string} the promise expression
+	 */
+	moduleNamespacePromise({
+		chunkGraph,
+		block,
+		module,
+		request,
+		message,
+		strict,
+		weak,
+		dependency,
+		runtimeRequirements,
+		originModule
+	}) {
 		if (!module) {
 			return this.missingModulePromise({
 				request
 			});
 		}
-		if (module.id === null) {
+		const moduleId = chunkGraph.getModuleId(module);
+		if (moduleId === null) {
+			if (weak) {
+				// only weak referenced modules don't get an id
+				// we can always emit an error emitting code here
+				return this.weakError({
+					module,
+					chunkGraph,
+					request,
+					type: "promise"
+				});
+			}
 			throw new Error(
-				`RuntimeTemplate.moduleNamespacePromise(): Module ${module.identifier()} has no id. This should not happen.`
+				`RuntimeTemplate.moduleNamespacePromise(): ${noModuleIdErrorMessage(
+					module,
+					chunkGraph
+				)}`
 			);
 		}
 		const promise = this.blockPromise({
+			chunkGraph,
 			block,
-			message
+			message,
+			runtimeRequirements,
+			originModule
 		});
 
-		let getModuleFunction;
-		let idExpr = JSON.stringify(module.id);
+		/** @type {string} */
+		let appending;
+		let idExpr = JSON.stringify(chunkGraph.getModuleId(module));
 		const comment = this.comment({
 			request
 		});
@@ -145,78 +1732,316 @@ module.exports = class RuntimeTemplate {
 		if (weak) {
 			if (idExpr.length > 8) {
 				// 'var x="nnnnnn";x,"+x+",x' vs '"nnnnnn",nnnnnn,"nnnnnn"'
-				header += `var id = ${idExpr}; `;
+				header += `${this.renderConst()} id = ${idExpr}; `;
 				idExpr = "id";
 			}
-			header += `if(!__webpack_require__.m[${idExpr}]) { var e = new Error("Module '" + ${idExpr} + "' is not available (weak dependency)"); e.code = 'MODULE_NOT_FOUND'; throw e; } `;
+			runtimeRequirements.add(RuntimeGlobals.moduleFactories);
+			header += `if(!${
+				RuntimeGlobals.moduleFactories
+			}[${idExpr}]) { ${this.weakError({
+				module,
+				chunkGraph,
+				request,
+				idExpr,
+				type: "statements"
+			})} } `;
 		}
-		const moduleId = this.moduleId({
-			module,
-			request
-		});
-		const exportsType = module.buildMeta && module.buildMeta.exportsType;
-		if (exportsType === "namespace") {
-			if (header) {
-				const rawModule = this.moduleRaw({
-					module,
-					request
-				});
-				getModuleFunction = `function() { ${header}return ${rawModule}; }`;
-			} else {
-				getModuleFunction = `__webpack_require__.bind(null, ${comment}${idExpr})`;
-			}
-		} else if (exportsType === "named") {
-			if (header) {
-				getModuleFunction = `function() { ${header}return __webpack_require__.t(${moduleId}, 3); }`;
-			} else {
-				getModuleFunction = `__webpack_require__.t.bind(null, ${comment}${idExpr}, 3)`;
-			}
-		} else if (strict) {
-			if (header) {
-				getModuleFunction = `function() { ${header}return __webpack_require__.t(${moduleId}, 1); }`;
+		const exportsType = module.getExportsType(chunkGraph.moduleGraph, strict);
+
+		const isModuleDeferred =
+			(dependency instanceof HarmonyImportDependency ||
+				dependency instanceof ImportDependency) &&
+			ImportPhaseUtils.isDefer(dependency.phase) &&
+			!(/** @type {BuildMeta} */ (module.buildMeta).async);
+
+		if (isModuleDeferred) {
+			runtimeRequirements.add(RuntimeGlobals.makeDeferredNamespaceObject);
+
+			let mode = getMakeDeferredNamespaceModeFromExportsType(exportsType);
+			if (mode) mode = `${mode} | 16`;
+
+			const asyncDeps = Array.from(
+				getOutgoingAsyncModules(chunkGraph.moduleGraph, module),
+				(m) => chunkGraph.getModuleId(m)
+			).filter((id) => id !== null);
+			if (asyncDeps.length) {
+				if (header) {
+					appending = `.then(${this.basicFunction(
+						"",
+						`${header}return ${
+							RuntimeGlobals.deferredModuleAsyncTransitiveDependencies
+						}(${JSON.stringify(asyncDeps)});`
+					)})`;
+				} else {
+					runtimeRequirements.add(RuntimeGlobals.require);
+					appending = `.then(${this.returningFunction(
+						`${
+							RuntimeGlobals.deferredModuleAsyncTransitiveDependencies
+						}(${JSON.stringify(asyncDeps)})`
+					)})`;
+				}
+				appending += `.then(${RuntimeGlobals.makeDeferredNamespaceObject}.bind(${RuntimeGlobals.require}, ${comment}${idExpr}, ${mode}))`;
+			} else if (header) {
+				appending = `.then(${this.basicFunction(
+					"",
+					`${header}return ${RuntimeGlobals.makeDeferredNamespaceObject}(${comment}${idExpr}, ${mode});`
+				)})`;
 			} else {
-				getModuleFunction = `__webpack_require__.t.bind(null, ${comment}${idExpr}, 1)`;
+				runtimeRequirements.add(RuntimeGlobals.require);
+				appending = `.then(${RuntimeGlobals.makeDeferredNamespaceObject}.bind(${RuntimeGlobals.require}, ${comment}${idExpr}, ${mode}))`;
 			}
 		} else {
-			if (header) {
-				getModuleFunction = `function() { ${header}return __webpack_require__.t(${moduleId}, 7); }`;
-			} else {
-				getModuleFunction = `__webpack_require__.t.bind(null, ${comment}${idExpr}, 7)`;
+			let fakeType = 16;
+			switch (exportsType) {
+				case "namespace":
+					if (header) {
+						const rawModule = this.moduleRaw({
+							module,
+							chunkGraph,
+							request,
+							weak,
+							runtimeRequirements
+						});
+						appending = `.then(${this.basicFunction(
+							"",
+							`${header}return ${rawModule};`
+						)})`;
+					} else {
+						runtimeRequirements.add(RuntimeGlobals.require);
+						appending = `.then(${RuntimeGlobals.require}.bind(${RuntimeGlobals.require}, ${comment}${idExpr}))`;
+					}
+					break;
+				case "dynamic":
+					fakeType |= 4;
+				/* fall through */
+				case "default-with-named":
+					fakeType |= 2;
+				/* fall through */
+				case "default-only":
+					runtimeRequirements.add(RuntimeGlobals.createFakeNamespaceObject);
+					if (chunkGraph.moduleGraph.isAsync(module)) {
+						if (header) {
+							const rawModule = this.moduleRaw({
+								module,
+								chunkGraph,
+								request,
+								weak,
+								runtimeRequirements
+							});
+							appending = `.then(${this.basicFunction(
+								"",
+								`${header}return ${rawModule};`
+							)})`;
+						} else {
+							runtimeRequirements.add(RuntimeGlobals.require);
+							appending = `.then(${RuntimeGlobals.require}.bind(${RuntimeGlobals.require}, ${comment}${idExpr}))`;
+						}
+						appending += `.then(${this.returningFunction(
+							`${RuntimeGlobals.createFakeNamespaceObject}(m, ${fakeType})`,
+							"m"
+						)})`;
+					} else {
+						fakeType |= 1;
+						if (header) {
+							const moduleIdExpr = this.moduleId({
+								module,
+								chunkGraph,
+								request,
+								weak
+							});
+							const returnExpression = `${RuntimeGlobals.createFakeNamespaceObject}(${moduleIdExpr}, ${fakeType})`;
+							appending = `.then(${this.basicFunction(
+								"",
+								`${header}return ${returnExpression};`
+							)})`;
+						} else {
+							appending = `.then(${RuntimeGlobals.createFakeNamespaceObject}.bind(${RuntimeGlobals.require}, ${comment}${idExpr}, ${fakeType}))`;
+						}
+					}
+					break;
 			}
 		}
 
-		return `${promise || "Promise.resolve()"}.then(${getModuleFunction})`;
+		return `${promise || "Promise.resolve()"}${appending}`;
+	}
+
+	/**
+	 * Runtime condition expression.
+	 * @param {object} options options object
+	 * @param {ChunkGraph} options.chunkGraph the chunk graph
+	 * @param {RuntimeSpec=} options.runtime runtime for which this code will be generated
+	 * @param {RuntimeSpec | boolean=} options.runtimeCondition only execute the statement in some runtimes
+	 * @param {RuntimeRequirements} options.runtimeRequirements if set, will be filled with runtime requirements
+	 * @returns {string} expression
+	 */
+	runtimeConditionExpression({
+		chunkGraph,
+		runtimeCondition,
+		runtime,
+		runtimeRequirements
+	}) {
+		if (runtimeCondition === undefined) return "true";
+		if (typeof runtimeCondition === "boolean") return `${runtimeCondition}`;
+		/** @type {Set} */
+		const positiveRuntimeIds = new Set();
+		forEachRuntime(runtimeCondition, (runtime) =>
+			positiveRuntimeIds.add(
+				`${chunkGraph.getRuntimeId(/** @type {string} */ (runtime))}`
+			)
+		);
+		/** @type {Set} */
+		const negativeRuntimeIds = new Set();
+		forEachRuntime(subtractRuntime(runtime, runtimeCondition), (runtime) =>
+			negativeRuntimeIds.add(
+				`${chunkGraph.getRuntimeId(/** @type {string} */ (runtime))}`
+			)
+		);
+		runtimeRequirements.add(RuntimeGlobals.runtimeId);
+		return compileBooleanMatcher.fromLists(
+			[...positiveRuntimeIds],
+			[...negativeRuntimeIds]
+		)(RuntimeGlobals.runtimeId);
 	}
 
-	importStatement({ update, module, request, importVar, originModule }) {
+	/**
+	 * Returns the import statement and the compat statement.
+	 * @param {object} options options object
+	 * @param {boolean=} options.update whether a new variable should be created or the existing one updated
+	 * @param {Module} options.module the module
+	 * @param {Module} options.originModule module in which the statement is emitted
+	 * @param {ModuleGraph} options.moduleGraph the module graph
+	 * @param {ChunkGraph} options.chunkGraph the chunk graph
+	 * @param {RuntimeRequirements} options.runtimeRequirements if set, will be filled with runtime requirements
+	 * @param {string} options.importVar name of the import variable
+	 * @param {string=} options.request the request that should be printed as comment
+	 * @param {boolean=} options.weak true, if this is a weak dependency
+	 * @param {ModuleDependency=} options.dependency module dependency
+	 * @returns {[string, string]} the import statement and the compat statement
+	 */
+	importStatement({
+		update,
+		module,
+		moduleGraph,
+		chunkGraph,
+		request,
+		importVar,
+		originModule,
+		weak,
+		dependency,
+		runtimeRequirements
+	}) {
 		if (!module) {
-			return this.missingModuleStatement({
-				request
-			});
+			return [
+				this.missingModuleStatement({
+					request
+				}),
+				""
+			];
+		}
+
+		if (chunkGraph.getModuleId(module) === null) {
+			if (weak) {
+				// only weak referenced modules don't get an id
+				// we can always emit an error emitting code here
+				return [
+					this.weakError({
+						module,
+						chunkGraph,
+						request,
+						type: "statements"
+					}),
+					""
+				];
+			}
+			throw new Error(
+				`RuntimeTemplate.importStatement(): ${noModuleIdErrorMessage(
+					module,
+					chunkGraph
+				)}`
+			);
 		}
 		const moduleId = this.moduleId({
 			module,
-			request
+			chunkGraph,
+			request,
+			weak
 		});
+		// Harmony imports may be wrapped in runtime-condition `if` blocks
+		// but referenced outside those blocks (e.g. by harmony reexport),
+		// so they must remain function-scoped (`var`) rather than
+		// block-scoped (`let`/`const`).
 		const optDeclaration = update ? "" : "var ";
 
-		const exportsType = module.buildMeta && module.buildMeta.exportsType;
-		let content = `/* harmony import */ ${optDeclaration}${importVar} = __webpack_require__(${moduleId});\n`;
+		const exportsType = module.getExportsType(
+			chunkGraph.moduleGraph,
+			/** @type {BuildMeta} */
+			(originModule.buildMeta).strictHarmonyModule
+		);
+		runtimeRequirements.add(RuntimeGlobals.require);
+
+		/** @type {string} */
+		let importContent;
+
+		const isModuleDeferred =
+			(dependency instanceof HarmonyImportDependency ||
+				dependency instanceof ImportDependency) &&
+			ImportPhaseUtils.isDefer(dependency.phase) &&
+			!(/** @type {BuildMeta} */ (module.buildMeta).async);
 
-		if (!exportsType && !originModule.buildMeta.strictHarmonyModule) {
-			content += `/* harmony import */ ${optDeclaration}${importVar}_default = /*#__PURE__*/__webpack_require__.n(${importVar});\n`;
+		if (isModuleDeferred) {
+			/** @type {Set} */
+			const outgoingAsyncModules = getOutgoingAsyncModules(moduleGraph, module);
+
+			importContent = `/* deferred harmony import */ ${optDeclaration}${importVar} = ${getOptimizedDeferredModule(
+				moduleId,
+				exportsType,
+				Array.from(outgoingAsyncModules, (mod) => chunkGraph.getModuleId(mod)),
+				getDeferredCycleModuleIds(
+					getDeferredCycleModules(moduleGraph, module),
+					(mod) => chunkGraph.getModuleId(mod)
+				),
+				runtimeRequirements
+			)};\n`;
+
+			return [importContent, ""];
 		}
-		if (exportsType === "named") {
-			if (Array.isArray(module.buildMeta.providedExports)) {
-				content += `${optDeclaration}${importVar}_namespace = /*#__PURE__*/__webpack_require__.t(${moduleId}, 1);\n`;
-			} else {
-				content += `${optDeclaration}${importVar}_namespace = /*#__PURE__*/__webpack_require__.t(${moduleId});\n`;
-			}
+		importContent = `/* harmony import */ ${optDeclaration}${importVar} = ${RuntimeGlobals.require}(${moduleId});\n`;
+
+		if (exportsType === "dynamic") {
+			runtimeRequirements.add(RuntimeGlobals.compatGetDefaultExport);
+			return [
+				importContent,
+				`/* harmony import */ ${optDeclaration}${importVar}_default = /*#__PURE__*/${RuntimeGlobals.compatGetDefaultExport}(${importVar});\n`
+			];
 		}
-		return content;
+		return [importContent, ""];
 	}
 
+	/**
+	 * Export from import.
+	 * @template GenerateContext
+	 * @param {object} options options
+	 * @param {ModuleGraph} options.moduleGraph the module graph
+	 * @param {ChunkGraph} options.chunkGraph the chunk graph
+	 * @param {Module} options.module the module
+	 * @param {string} options.request the request
+	 * @param {string | string[]} options.exportName the export name
+	 * @param {Module} options.originModule the origin module
+	 * @param {boolean | undefined} options.asiSafe true, if location is safe for ASI, a bracket can be emitted
+	 * @param {boolean | undefined} options.isCall true, if expression will be called
+	 * @param {boolean | null} options.callContext when false, call context will not be preserved
+	 * @param {boolean} options.defaultInterop when true and accessing the default exports, interop code will be generated
+	 * @param {string} options.importVar the identifier name of the import variable
+	 * @param {InitFragment[]} options.initFragments init fragments will be added here
+	 * @param {RuntimeSpec} options.runtime runtime for which this code will be generated
+	 * @param {RuntimeRequirements} options.runtimeRequirements if set, will be filled with runtime requirements
+	 * @param {ModuleDependency} options.dependency module dependency
+	 * @param {boolean=} options.mangleableNamespace true, when a whole-namespace value may use a decoupled namespace object that keeps the original export names
+	 * @returns {string} expression
+	 */
 	exportFromImport({
+		moduleGraph,
+		chunkGraph,
 		module,
 		request,
 		exportName,
@@ -224,97 +2049,1137 @@ module.exports = class RuntimeTemplate {
 		asiSafe,
 		isCall,
 		callContext,
-		importVar
+		defaultInterop,
+		importVar,
+		initFragments,
+		runtime,
+		runtimeRequirements,
+		dependency,
+		mangleableNamespace = false
 	}) {
 		if (!module) {
 			return this.missingModule({
 				request
 			});
 		}
-		const exportsType = module.buildMeta && module.buildMeta.exportsType;
+		if (!Array.isArray(exportName)) {
+			exportName = exportName ? [exportName] : [];
+		}
+		const exportsType = module.getExportsType(
+			moduleGraph,
+			/** @type {BuildMeta} */
+			(originModule.buildMeta).strictHarmonyModule
+		);
 
-		if (!exportsType) {
-			if (exportName === "default") {
-				if (!originModule.buildMeta.strictHarmonyModule) {
-					if (isCall) {
-						return `${importVar}_default()`;
-					} else if (asiSafe) {
-						return `(${importVar}_default())`;
-					} else {
-						return `${importVar}_default.a`;
+		const isModuleDeferred =
+			(dependency instanceof HarmonyImportDependency ||
+				dependency instanceof ImportDependency) &&
+			ImportPhaseUtils.isDefer(dependency.phase) &&
+			!(/** @type {BuildMeta} */ (module.buildMeta).async);
+
+		if (defaultInterop) {
+			// when the defaultInterop is used (when a ESM imports a CJS module),
+			if (exportName.length > 0 && exportName[0] === "default") {
+				if (isModuleDeferred && exportsType !== "namespace") {
+					const exportsInfo = moduleGraph.getExportsInfo(module);
+					const name = exportName.slice(1);
+					const used = exportsInfo.getUsedName(name, runtime);
+					if (!used) {
+						const comment = Template.toNormalComment(
+							`unused export ${propertyAccess(exportName)}`
+						);
+						return `${comment} undefined`;
 					}
-				} else {
-					return importVar;
+					if (used instanceof InlinedUsedName) {
+						throw new Error(
+							"Can't inline the exports of defer imported module"
+						);
+					}
+					const access = `${importVar}.a${propertyAccess(
+						Array.isArray(used) ? used : [used]
+					)}`;
+					if (isCall || asiSafe === undefined) {
+						return access;
+					}
+					return asiSafe ? `(${access})` : `;(${access})`;
 				}
-			} else if (originModule.buildMeta.strictHarmonyModule) {
-				if (exportName) {
-					return "/* non-default import from non-esm module */undefined";
-				} else {
-					return `/*#__PURE__*/__webpack_require__.t(${importVar})`;
+				// accessing the .default property is same thing as `require()` the module.
+
+				// For example:
+				// import mod from "cjs";    mod.default.x;
+				// is translated to
+				// var mod = require("cjs"); mod.x;
+				switch (exportsType) {
+					case "dynamic":
+						if (isCall) {
+							return `${importVar}_default()${propertyAccess(exportName, 1)}`;
+						}
+						return asiSafe
+							? `(${importVar}_default()${propertyAccess(exportName, 1)})`
+							: asiSafe === false
+								? `;(${importVar}_default()${propertyAccess(exportName, 1)})`
+								: `${importVar}_default.a${propertyAccess(exportName, 1)}`;
+
+					case "default-only":
+					case "default-with-named":
+						exportName = exportName.slice(1);
+						break;
+				}
+			} else if (exportName.length > 0) {
+				// the property used is not .default.
+				// For example:
+				// import * as ns from "cjs"; cjs.prop;
+				if (exportsType === "default-only") {
+					// in the strictest case, it is a runtime error (e.g. NodeJS behavior of CJS-ESM interop).
+					return `/* non-default import from non-esm module */undefined${propertyAccess(
+						exportName,
+						1
+					)}`;
+				} else if (
+					exportsType !== "namespace" &&
+					exportName[0] === "__esModule"
+				) {
+					return "/* __esModule */true";
 				}
+			} else if (isModuleDeferred) {
+				// now exportName.length is 0
+				// fall through to the end of this function, create the namespace there.
+			} else if (
+				exportsType === "default-only" ||
+				exportsType === "default-with-named"
+			) {
+				// now exportName.length is 0, which means the namespace object is used in an unknown way
+				// for example:
+				// import * as ns from "cjs"; console.log(ns);
+				// we will need to createFakeNamespaceObject that simulates ES Module namespace object
+				runtimeRequirements.add(RuntimeGlobals.createFakeNamespaceObject);
+				initFragments.push(
+					new InitFragment(
+						`${this.renderLet()} ${importVar}_namespace_cache;\n`,
+						InitFragment.STAGE_CONSTANTS,
+						-1,
+						`${importVar}_namespace_cache`
+					)
+				);
+				return `/*#__PURE__*/ ${
+					asiSafe ? "" : asiSafe === false ? ";" : "Object"
+				}(${importVar}_namespace_cache || (${importVar}_namespace_cache = ${
+					RuntimeGlobals.createFakeNamespaceObject
+				}(${importVar}${exportsType === "default-only" ? "" : ", 2"})))`;
 			}
 		}
 
-		if (exportsType === "named") {
-			if (exportName === "default") {
-				return importVar;
-			} else if (!exportName) {
-				return `${importVar}_namespace`;
+		if (exportName.length > 0) {
+			const exportsInfo = moduleGraph.getExportsInfo(module);
+			// in some case the exported item is renamed (get this by getUsedName). for example,
+			// x.default might be emitted as x.Z (default is renamed to Z)
+			const used = exportsInfo.getUsedName(exportName, runtime);
+			if (!used) {
+				const comment = Template.toNormalComment(
+					`unused export ${propertyAccess(exportName)}`
+				);
+				return `${comment} undefined`;
+			}
+			if (used instanceof InlinedUsedName) {
+				return used.render(
+					Template.toNormalComment(
+						`inlined export ${propertyAccess(exportName)}`
+					)
+				);
+			}
+			const comment = equals(used, exportName)
+				? ""
+				: `${Template.toNormalComment(propertyAccess(exportName))} `;
+			const access = `${importVar}${
+				isModuleDeferred ? ".a" : ""
+			}${comment}${propertyAccess(Array.isArray(used) ? used : [used])}`;
+			if (isCall && callContext === false) {
+				return asiSafe
+					? `(0,${access})`
+					: asiSafe === false
+						? `;(0,${access})`
+						: `/*#__PURE__*/Object(${access})`;
 			}
+			return access;
 		}
+		if (isModuleDeferred) {
+			initFragments.push(
+				new InitFragment(
+					`${this.renderLet()} ${importVar}_deferred_namespace_cache;\n`,
+					InitFragment.STAGE_CONSTANTS,
+					-1,
+					`${importVar}_deferred_namespace_cache`
+				)
+			);
 
-		if (exportName) {
-			const used = module.isUsed(exportName);
-			const comment =
-				used !== exportName ? Template.toNormalComment(exportName) + " " : "";
-			const access = `${importVar}[${comment}${JSON.stringify(used)}]`;
-			if (isCall) {
-				if (callContext === false && asiSafe) {
-					return `(0,${access})`;
-				} else if (callContext === false) {
-					return `Object(${access})`;
-				}
+			runtimeRequirements.add(RuntimeGlobals.makeDeferredNamespaceObject);
+			const id = chunkGraph.getModuleId(module);
+			const type = getMakeDeferredNamespaceModeFromExportsType(exportsType);
+			const init = `${
+				RuntimeGlobals.makeDeferredNamespaceObject
+			}(${JSON.stringify(id)}, ${type})`;
+
+			return `/*#__PURE__*/ ${
+				asiSafe ? "" : asiSafe === false ? ";" : "Object"
+			}(${importVar}_deferred_namespace_cache || (${importVar}_deferred_namespace_cache = ${init}))`;
+		}
+		// The whole namespace object is used as a value. If the module's exports
+		// were mangled, importVar's keys are the mangled names, so we materialize
+		// a decoupled namespace object that exposes the original names.
+		if (
+			exportsType === "namespace" &&
+			mangleableNamespace &&
+			this.compilation.options.optimization.mangleExports
+		) {
+			const materialized = this._materializedNamespaceObject({
+				moduleGraph,
+				module,
+				importVar,
+				initFragments,
+				runtime,
+				runtimeRequirements
+			});
+			if (materialized !== undefined) return materialized;
+		}
+		// if we hit here, the importVar is either
+		// - already a ES module namespace object
+		// - or imported by a way that does not need interop.
+		return importVar;
+	}
+
+	/**
+	 * Materializes a namespace object that keeps the original export names while
+	 * the module's own exports are mangled. Returns undefined when no export was
+	 * mangled (then the raw namespace object can be used as-is).
+	 * @template GenerateContext
+	 * @param {object} options options
+	 * @param {ModuleGraph} options.moduleGraph the module graph
+	 * @param {Module} options.module the imported module
+	 * @param {string} options.importVar the import variable referencing the module
+	 * @param {InitFragment[]} options.initFragments target array for init fragments
+	 * @param {RuntimeSpec} options.runtime the runtime
+	 * @param {RuntimeRequirements} options.runtimeRequirements runtime requirements
+	 * @returns {string | undefined} expression of the materialized namespace object, or undefined
+	 */
+	_materializedNamespaceObject({
+		moduleGraph,
+		module,
+		importVar,
+		initFragments,
+		runtime,
+		runtimeRequirements
+	}) {
+		const exportsInfo = moduleGraph.getExportsInfo(module);
+		/** @type {string[]} */
+		const definitions = [];
+		let mangled = false;
+		for (const exportInfo of exportsInfo.orderedExports) {
+			if (exportInfo.provided === false) continue;
+			const used = exportsInfo.getUsedName([exportInfo.name], runtime);
+			if (!used) continue;
+			if (used instanceof InlinedUsedName) {
+				// An inlined export isn't reachable by name on the raw exports object,
+				// so the decoupled object must expose the inlined value directly.
+				mangled = true;
+				definitions.push(
+					`${propertyName(exportInfo.name)}: ${this.returningFunction(
+						used.render(
+							Template.toNormalComment(
+								`inlined export ${propertyAccess([exportInfo.name])}`
+							)
+						)
+					)}`
+				);
+				continue;
 			}
-			return access;
-		} else {
-			return importVar;
+			if (used[used.length - 1] !== exportInfo.name) mangled = true;
+			definitions.push(
+				`${propertyName(exportInfo.name)}: ${this.returningFunction(
+					`${importVar}${propertyAccess(/** @type {string[]} */ (used))}`
+				)}`
+			);
 		}
+		if (!mangled) return;
+		const name = `${importVar}_namespace_object`;
+		runtimeRequirements.add(RuntimeGlobals.exports);
+		runtimeRequirements.add(RuntimeGlobals.makeNamespaceObject);
+		runtimeRequirements.add(RuntimeGlobals.definePropertyGetters);
+		initFragments.push(
+			new InitFragment(
+				`var ${name} = {};\n${RuntimeGlobals.makeNamespaceObject}(${name});\n${
+					RuntimeGlobals.definePropertyGetters
+				}(${name}, {\n\t${definitions.join(",\n\t")}\n});\n`,
+				InitFragment.STAGE_PROVIDES,
+				0,
+				name
+			)
+		);
+		return name;
 	}
 
-	blockPromise({ block, message }) {
-		if (!block || !block.chunkGroup || block.chunkGroup.chunks.length === 0) {
+	/**
+	 * Returns expression.
+	 * @param {object} options options
+	 * @param {AsyncDependenciesBlock | undefined} options.block the async block
+	 * @param {string} options.message the message
+	 * @param {ChunkGraph} options.chunkGraph the chunk graph
+	 * @param {RuntimeRequirements} options.runtimeRequirements if set, will be filled with runtime requirements
+	 * @param {Module=} options.originModule the module the `import()` is emitted into
+	 * @returns {string} expression
+	 */
+	blockPromise({
+		block,
+		message,
+		chunkGraph,
+		runtimeRequirements,
+		originModule
+	}) {
+		if (!block) {
+			const comment = this.comment({
+				message
+			});
+			return `Promise.resolve(${comment.trim()})`;
+		}
+		const chunkGroup = chunkGraph.getBlockChunkGroup(block);
+		if (!chunkGroup || chunkGroup.chunks.length === 0) {
 			const comment = this.comment({
 				message
 			});
 			return `Promise.resolve(${comment.trim()})`;
 		}
-		const chunks = block.chunkGroup.chunks.filter(
-			chunk => !chunk.hasRuntime() && chunk.id !== null
+		const chunks = chunkGroup.chunks.filter(
+			(chunk) => !chunk.hasRuntime() && chunk.id !== null
 		);
 		const comment = this.comment({
 			message,
-			chunkName: block.chunkName,
-			chunkReason: block.chunkReason
+			chunkName: block.chunkName
 		});
+		// `fetchPriority` is unsupported for ESM output (a native `import()` can't carry it,
+		// and the ESM chunk loader ignores the argument), so it never blocks the analyzable form.
+		// TODO recheck: browsers honor `fetchpriority` on `modulepreload` only when the
+		// preload scanner sees it, so a runtime-injected hint is inert; support it here if
+		// that changes.
+		const fetchPriority = chunkGroup.options.fetchPriority;
 		if (chunks.length === 1) {
+			const analyzable = this._analyzableChunkImport(
+				chunks[0],
+				comment,
+				runtimeRequirements,
+				originModule,
+				chunkGraph
+			);
+			if (analyzable !== null) {
+				return analyzable;
+			}
 			const chunkId = JSON.stringify(chunks[0].id);
-			return `__webpack_require__.e(${comment}${chunkId})`;
+			runtimeRequirements.add(RuntimeGlobals.ensureChunk);
+
+			if (fetchPriority) {
+				runtimeRequirements.add(RuntimeGlobals.hasFetchPriority);
+			}
+
+			return `${RuntimeGlobals.ensureChunk}(${comment}${chunkId}${
+				fetchPriority ? `, ${JSON.stringify(fetchPriority)}` : ""
+			})`;
 		} else if (chunks.length > 0) {
-			const requireChunkId = chunk =>
-				`__webpack_require__.e(${JSON.stringify(chunk.id)})`;
-			return `Promise.all(${comment.trim()}[${chunks
-				.map(requireChunkId)
-				.join(", ")}])`;
-		} else {
-			return `Promise.resolve(${comment.trim()})`;
+			let needEnsureChunk = false;
+			/**
+			 * Analyzable `import()` for a solely-owned JS chunk, else runtime ensureChunk.
+			 * @param {Chunk} chunk chunk
+			 * @returns {string} require chunk id code
+			 */
+			const requireChunkId = (chunk) => {
+				const analyzable = this._analyzableChunkImport(
+					chunk,
+					"",
+					runtimeRequirements,
+					originModule,
+					chunkGraph
+				);
+				if (analyzable !== null) return analyzable;
+				needEnsureChunk = true;
+				return `${RuntimeGlobals.ensureChunk}(${JSON.stringify(chunk.id)}${
+					fetchPriority ? `, ${JSON.stringify(fetchPriority)}` : ""
+				})`;
+			};
+			const items = chunks.map(requireChunkId);
+			if (needEnsureChunk) {
+				runtimeRequirements.add(RuntimeGlobals.ensureChunk);
+				// Only needed when an `ensureChunk(id, priority)` call is actually emitted.
+				if (fetchPriority) {
+					runtimeRequirements.add(RuntimeGlobals.hasFetchPriority);
+				}
+			}
+			return `Promise.all(${comment.trim()}[${items.join(", ")}])`;
 		}
+		return `Promise.resolve(${comment.trim()})`;
 	}
 
-	onError() {
-		return "__webpack_require__.oe";
+	/**
+	 * For ESM module output, load a single statically-named chunk through the
+	 * `analyzableChunkImport` helper — a literal `import("./chunk.js")` other bundlers
+	 * and webpack itself can follow, wrapped to keep `ensureChunk` timing and deduplication.
+	 * Returns `null` to fall back to the runtime `ensureChunk` form.
+	 * @param {Chunk} chunk the chunk to load
+	 * @param {string} comment leading comment (chunk name / message)
+	 * @param {RuntimeRequirements} runtimeRequirements runtime requirements
+	 * @param {Module | undefined} originModule the module the `import()` is emitted into
+	 * @param {ChunkGraph} chunkGraph the chunk graph
+	 * @returns {string | null} the import expression, or `null`
+	 */
+	_analyzableChunkImport(
+		chunk,
+		comment,
+		runtimeRequirements,
+		originModule,
+		chunkGraph
+	) {
+		// `blockPromise` has already dropped any chunk without an id. Without an origin
+		// there is nothing the specifier could be relative to.
+		if (
+			!originModule ||
+			!this.supportsAnalyzable("import", chunkGraph, originModule)
+		) {
+			return null;
+		}
+		// Prefetch/preload children are injected by the runtime `.f` handlers, which `.ei`
+		// runs too — but they attach to `.f`, so it has to exist.
+		const needsHintHandlers =
+			chunk.hasChildByOrder(chunkGraph, "prefetch", true) ||
+			chunk.hasChildByOrder(chunkGraph, "preload", true) ||
+			chunk.hasChildByOrder(chunkGraph, "cssPreload", true);
+		// `.ei` always performs the import, where `.e` only reaches the javascript
+		// loader when there is javascript to load. A module federation remote is the
+		// case that matters: its chunk is emitted by the container's build, not this one.
+		if (!JavascriptModulesPlugin.chunkHasJs(chunk, chunkGraph)) {
+			this._analyzableBailout(
+				originModule,
+				"this compilation emits no javascript for the chunk, so there is nothing to import"
+			);
+			return null;
+		}
+		let hasNonJsTypes = false;
+		for (const module of chunkGraph.getChunkModulesIterable(chunk)) {
+			for (const type of chunkGraph.getModuleSourceTypes(module)) {
+				if (!TYPES_WITHOUT_CHUNK_HANDLER.has(type)) {
+					// Loaded by that type's own `.f` handler, which `.ei` still dispatches.
+					hasNonJsTypes = true;
+				}
+			}
+		}
+		// Relative to the consuming chunk (`import.meta.url`) for `auto`, else an
+		// absolute publicPath prefix.
+		const specifier = this._getAnalyzableChunkSpecifier(
+			undefined,
+			chunk,
+			originModule,
+			chunkGraph
+		);
+		if (specifier === null) {
+			return null;
+		}
+		// `import()` needs a resolvable specifier — relative (`./`, `../`), absolute (`/`)
+		// or a URL scheme. A bare one is a package name, so make it explicitly relative
+		// the way the chunk loader does. (`new URL(...)` callers accept bare vs their base.)
+		const resolvableSpecifier = /^"(?:\.{0,2}\/|[a-zA-Z][\w+.-]*:)/.test(
+			specifier
+		)
+			? specifier
+			: `"./${specifier.slice(1)}`;
+		runtimeRequirements.add(RuntimeGlobals.analyzableChunkImport);
+		if (needsHintHandlers || hasNonJsTypes) {
+			// Those handlers attach to the `.f` map, which has to exist — but the runtime
+			// `ensureChunk` around it does not, since `.ei` dispatches them itself.
+			runtimeRequirements.add(RuntimeGlobals.ensureChunkHandlers);
+		}
+		// Drop-in for `ensureChunk(id)`: the literal `import()` can be statically
+		// followed, the helper keeps webpack's install timing and deduplication.
+		return `${RuntimeGlobals.analyzableChunkImport}(${JSON.stringify(
+			chunk.id
+		)}, () => import(${comment}${resolvableSpecifier}))`;
 	}
 
-	defineEsModuleFlagStatement({ exportsArgument }) {
-		return `__webpack_require__.r(${exportsArgument});\n`;
+	/**
+	 * Whether this chunk's name sorts before every chunk a stand-in would land in, the
+	 * tie-break that lets one direction of a cycle bake: ids are unique and settled
+	 * here, so the order is total and the same in every build.
+	 * @param {Chunk} chunk the referenced chunk
+	 * @param {Iterable} targets the chunks the stand-in is written into
+	 * @returns {boolean} true when this one may bake and the others may not
+	 */
+	_namesBeforeAll(chunk, targets) {
+		const id = chunk.id;
+		if (id === null) return false;
+		const key = String(id);
+		for (const target of targets) {
+			if (target.id === null || String(target.id) <= key) return false;
+		}
+		return true;
 	}
-};
+
+	/**
+	 * Whether `chunk` can reach any of `targets` by following chunk groups, which is
+	 * what turns a baked hashed name into a hash that depends on itself.
+	 * @param {Chunk} chunk the referenced chunk
+	 * @param {Iterable} targets the chunks the reference is written into
+	 * @returns {boolean} true when one of them is reachable from `chunk`
+	 */
+	_reachesAny(chunk, targets) {
+		const wanted = new Set(targets);
+		if (wanted.size === 0) return false;
+		if (wanted.has(chunk)) return true;
+		/** @type {Set} */
+		const seen = new Set();
+		/** @type {ChunkGroup[]} */
+		const queue = [...chunk.groupsIterable];
+		for (const group of queue) seen.add(group);
+		for (let i = 0; i < queue.length; i++) {
+			for (const child of queue[i].childrenIterable) {
+				if (seen.has(child)) continue;
+				seen.add(child);
+				for (const candidate of child.chunks) {
+					if (wanted.has(candidate)) return true;
+				}
+				queue.push(child);
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * The chunks a reference emitted into `module` is written into — the assets a
+	 * stand-in of ours would land in, and so the only ones a deferred fill could go
+	 * stale in. Resolved through concatenation first: it may have absorbed the module
+	 * that wrote the reference, and an absorbed one is in no chunk at all.
+	 * @param {Module} module the module a reference is emitted into
+	 * @param {ChunkGraph} chunkGraph the chunk graph
+	 * @returns {Iterable} the chunks holding it
+	 */
+	_moduleChunks(module, chunkGraph) {
+		return chunkGraph.getModuleChunksIterable(
+			getConcatenatedModule().getChunkGraphModule(this.compilation, module)
+		);
+	}
+
+	/**
+	 * The `../` path back out of a public path that needs a base, which is where the
+	 * chunk loader puts what it fetches. Reads nothing but `output`, so it is answered
+	 * once: `""` where the path adds no depth, `null` where its shape is unknown.
+	 * @returns {string | null} the path back out of it
+	 */
+	_publicPathClimb() {
+		if (this._publicPathClimbText === undefined) {
+			const shape = this._publicPathShape();
+			const undo =
+				shape === undefined
+					? null
+					: getUndoPath(
+							`${shape}x`,
+							/** @type {string} */ (this.outputOptions.path),
+							true
+						);
+			this._publicPathClimbText = undo === "./" ? "" : undo;
+		}
+		return this._publicPathClimbText;
+	}
+
+	/**
+	 * Where a literal emitted into a chunk is read from: the `../` path back to the
+	 * output root, and whether the chunk loader fetched the chunk through
+	 * `output.publicPath` — such a chunk sits one public path below the root the runtime
+	 * resolves against, where an initial chunk the host fetched does not. Reads only the
+	 * chunk, so it is memoized on it and shared by every module in it.
+	 * @param {Chunk} chunk a chunk a reference is written into
+	 * @returns {Placement} where a literal in it is read from
+	 */
+	_chunkPlacement(chunk) {
+		const cached = this._placementByChunk.get(chunk);
+		if (cached !== undefined) return cached;
+		const { compilation } = this;
+		const { outputOptions } = compilation;
+		const template = JavascriptModulesPlugin.getChunkFilenameTemplate(
+			chunk,
+			outputOptions
+		);
+		const chunkName = compilation.getPath(
+			// Hashes aren't known during code generation and only sit in the basename
+			// (never a directory), so neutralize them — only the `../` depth matters.
+			typeof template === "string"
+				? template.replace(HASH_IN_FILENAME_GLOBAL, "x")
+				: template,
+			{ chunk, runtime: chunk.runtime, contentHashType: JAVASCRIPT_TYPE }
+		);
+		const placement = {
+			undo: getUndoPath(
+				chunkName,
+				/** @type {string} */ (outputOptions.path),
+				true
+			),
+			// A chunk in an initial group and an async one both is served at two urls,
+			// one public path apart, so no one literal in it is right for both.
+			loaded: chunk.isOnlyInitial() ? false : chunk.canBeInitial() ? null : true
+		};
+		this._placementByChunk.set(chunk, placement);
+		return placement;
+	}
+
+	/**
+	 * The one answer every chunk holding a reference agrees on, with either half `null`
+	 * where they do not — then no one literal is right for all of them.
+	 * @param {Module} module the module the reference is emitted into
+	 * @param {ChunkGraph} chunkGraph the chunk graph
+	 * @returns {Placement} where a literal in it is read from
+	 */
+	_modulePlacement(module, chunkGraph) {
+		/** @type {Placement | undefined} */
+		let first;
+		/** @type {string | null} */
+		let undo = null;
+		/** @type {boolean | null} */
+		let loaded = null;
+		for (const chunk of this._moduleChunks(module, chunkGraph)) {
+			const own = this._chunkPlacement(chunk);
+			if (first === undefined) {
+				first = own;
+				undo = own.undo;
+				loaded = own.loaded;
+				continue;
+			}
+			if (undo !== own.undo) undo = null;
+			if (loaded !== own.loaded) loaded = null;
+		}
+		if (first === undefined) return NO_PLACEMENT;
+		// A module in one chunk — nearly all of them — hands back what that chunk said,
+		// so the common answer costs no object of its own.
+		return undo === first.undo && loaded === first.loaded
+			? first
+			: { undo, loaded };
+	}
+
+	/**
+	 * Static literal specifier (already quoted) for a `new URL(, import.meta.url)`
+	 * or `import()` pointing at `chunk`'s JS file, or `null` when it can't be known
+	 * statically — a content hash in the filename, or a dynamic/templated publicPath.
+	 * @param {string | undefined} overridePublicPath per-dependency public path (wins over `output.publicPath`)
+	 * @param {Chunk} chunk the chunk to reference
+	 * @param {Module} consumingModule the module the reference is emitted into
+	 * @param {ChunkGraph} chunkGraph the chunk graph
+	 * @param {RuntimeRequirements=} runtimeRequirements set when the caller wraps the result in
+	 * `new URL(...)` and so accepts a runtime public path prefix around the literal filename
+	 * @returns {string | null} a JS string literal or expression, or `null` to fall back to the runtime form
+	 */
+	_getAnalyzableChunkSpecifier(
+		overridePublicPath,
+		chunk,
+		consumingModule,
+		chunkGraph,
+		runtimeRequirements
+	) {
+		const { compilation } = this;
+		const { outputOptions } = compilation;
+		const filenameTemplate = JavascriptModulesPlugin.getChunkFilenameTemplate(
+			chunk,
+			outputOptions
+		);
+		const template = this._resolveChunkFilenameTemplate(
+			filenameTemplate,
+			chunk
+		);
+		if (template === null) return null;
+		// A hashed name is settled long after this code is generated, so a stand-in is
+		// emitted and filled in once the hash exists.
+		const deferred = template === undefined || HASH_IN_FILENAME.test(template);
+		// An id names the chunk in the stand-in, and one nothing could resolve would
+		// reach the bundle verbatim. The whole name is re-resolved later rather than
+		// carrying a stand-in of its own, so any hash spelling is fine here.
+		const chunks = this._moduleChunks(consumingModule, chunkGraph);
+		// Baking makes the consuming chunk's content depend on this one's hash. A pair that
+		// reaches back has no fixed point if both bake, so only the lower id's name does.
+		const cycles =
+			deferred &&
+			this._reachesAny(chunk, chunks) &&
+			!this._namesBeforeAll(chunk, chunks);
+		// An id names the chunk in the stand-in. No reference reaches here without one —
+		// `blockPromise` drops an id-less chunk before asking, and one the whole build
+		// keeps has no name to be emitted under — so the guard carries no reason of its
+		// own; it only keeps `null` out of a name.
+		const canReserve =
+			chunk.id !== null && !cycles && this._canDeferAnalyzableName(chunks);
+		/**
+		 * @returns {null} always, having recorded why no stand-in may be reserved
+		 */
+		const cannotReserve = () => {
+			this._analyzableBailout(
+				consumingModule,
+				cycles
+					? "this chunk and the one it references name each other, so neither hash could settle"
+					: DEFER_BAILOUT
+			);
+			return null;
+		};
+		if (deferred && !canReserve) return cannotReserve();
+		const filename = deferred
+			? ""
+			: compilation.getPath(/** @type {string} */ (template), {
+					chunk,
+					// Matches what names the asset, or a placeholder resolved here would
+					// not be the one on disk.
+					runtime: chunk.runtime,
+					contentHashType: JAVASCRIPT_TYPE
+				});
+		/**
+		 * @param {SpecifierPart[]} prefix what goes in front of the chunk's filename
+		 * @returns {string | null} the specifier already quoted, or `null` when the
+		 * recipe needs a stand-in that cannot be reserved
+		 */
+		const specifier = (prefix) => {
+			if (!deferred) {
+				const text = literalText(prefix);
+				if (text !== null) return toJsStringLiteral(text + filename);
+			}
+			if (!canReserve) return cannotReserve();
+			return toJsStringLiteral(
+				getAnalyzableChunkHashPlugin().reserveSpecifier([
+					...prefix,
+					["chunk", /** @type {ChunkId} */ (chunk.id)]
+				])
+			);
+		};
+		if (overridePublicPath) {
+			const resolved = this._resolvePublicPathPrefix(
+				overridePublicPath,
+				consumingModule,
+				chunks
+			);
+			return resolved === null ? null : specifier(resolved);
+		}
+		const { publicPath } = outputOptions;
+		if (publicPath === "auto") {
+			const { undo } = this._modulePlacement(consumingModule, chunkGraph);
+			if (undo !== null) return specifier([["literal", undo]]);
+			// Different depths — no one `../` path is right for every asset the reference
+			// lands in, so the deferred pass builds each asset's own.
+			const perAsset = specifier([["undo", ""]]);
+			if (perAsset !== null) return perAsset;
+			// Only a stand-in that could not be reserved reaches here, and `specifier`
+			// has already recorded why.
+			if (deferred || !runtimeRequirements) return null;
+			// No bundler follows a concatenation, but a `new URL(...)` caller still sheds
+			// the `.u(id)` lookup this way; `import()` needs a static specifier.
+			runtimeRequirements.add(RuntimeGlobals.publicPath);
+			return `${RuntimeGlobals.publicPath} + ${toJsStringLiteral(filename)}`;
+		}
+		const prefix = this._analyzablePathPrefix(
+			consumingModule,
+			chunkGraph,
+			chunks
+		);
+		return prefix === null ? null : specifier(prefix);
+	}
+
+	/**
+	 * The parts that go in front of a name so a literal read from the chunk holding it
+	 * reaches what the runtime would. A public path needing no base is the whole answer.
+	 * One that needs a base is read from the output root, so the `../` path back there
+	 * comes first — and is the whole answer again where the chunk loader fetched this
+	 * chunk through that same path, since climbing out of it only to spell it again
+	 * names the place it started from. An entry `baseUri` sits between the two, so
+	 * there both are written. Never asked of an `auto` public path, which is no path
+	 * to walk back over.
+	 * @param {Module} module the module the reference is emitted into
+	 * @param {ChunkGraph} chunkGraph the chunk graph
+	 * @param {Iterable} chunks the chunks a stand-in would be written into
+	 * @param {string=} relativeBase an entry `baseUri` read against the output root
+	 * @returns {SpecifierPart[] | null} the parts, or `null` having recorded why not
+	 */
+	_analyzablePathPrefix(module, chunkGraph, chunks, relativeBase) {
+		const publicPath = /** @type {PublicPath} */ (
+			this.outputOptions.publicPath
+		);
+		const shape = this._publicPathShape();
+		if (shape === undefined) return null;
+		const resolve = () =>
+			this._resolvePublicPathPrefix(publicPath, module, chunks);
+		if (isBaseIndependent(shape)) return resolve();
+		const { undo, loaded } = this._modulePlacement(module, chunkGraph);
+		const climb =
+			loaded === null ? null : loaded ? this._publicPathClimb() : "";
+		if (climb === null) {
+			this._analyzableBailout(module, SERVED_BAILOUT);
+			return null;
+		}
+		/** @type {SpecifierPart[]} */
+		const head = [undo === null ? ["undo", ""] : ["literal", undo]];
+		if (climb !== "" && relativeBase === undefined) return head;
+		const resolved = resolve();
+		if (resolved === null) return null;
+		return [
+			...head,
+			.../** @type {SpecifierPart[]} */ (
+				climb === "" ? [] : [["literal", climb]]
+			),
+			.../** @type {SpecifierPart[]} */ (
+				relativeBase === undefined ? [] : [["literal", relativeBase]]
+			),
+			...rootedParts(resolved)
+		];
+	}
+
+	/**
+	 * What a public path puts in front of a filename, as the parts it is built from.
+	 * A plain one is literal text. A templated one is resolved as far as code
+	 * generation can, leaving the compilation hash it may carry as a stand-in exactly
+	 * as `PublicPathRuntimeModule` resolves the same string once that hash exists — and
+	 * a hash re-encoded to another digest, which no stand-in can carry, is handed to
+	 * the deferred pass whole instead. A function is called for its value rather than
+	 * read as a template, so one whose answer moves with the hash is called again there.
+	 * @param {PublicPath} publicPath the configured public path
+	 * @param {Module=} module the module the reference is emitted into, to record why
+	 * a name no stand-in may be reserved for kept the runtime form
+	 * @param {Iterable=} chunks the chunks a stand-in would be written into
+	 * @returns {SpecifierPart[] | null} the parts, or `null` when it cannot be known
+	 */
+	_resolvePublicPathPrefix(publicPath, module, chunks) {
+		if (typeof publicPath === "function") {
+			// One that answers nothing to a probe cannot be placed, absolute or not.
+			if (this._publicPathShape() === undefined) return null;
+			const resolved = this._resolveHashIndependent(
+				publicPath,
+				this._publicPathShape()
+			);
+			if (resolved !== null) return [["literal", resolved]];
+			if (this._canDeferAnalyzableName(chunks)) return [["publicPath", ""]];
+			this._analyzableBailout(module, DEFER_BAILOUT);
+			return null;
+		}
+		if (!publicPath.includes("[")) return [["literal", publicPath]];
+		if (!this._canDeferAnalyzableName(chunks)) {
+			this._analyzableBailout(module, DEFER_BAILOUT);
+			return null;
+		}
+		const analyzableChunkHashPlugin = getAnalyzableChunkHashPlugin();
+		if (!analyzableChunkHashPlugin.canDeferFullHash(publicPath)) {
+			return [["template", publicPath]];
+		}
+		return [
+			[
+				"literal",
+				this.compilation.getPath(
+					publicPath,
+					analyzableChunkHashPlugin.DEFERRED_FULL_HASH_PATH_DATA
+				)
+			]
+		];
+	}
+
+	/**
+	 * `output.publicPath` as the text it will have the shape of — a function's answer to
+	 * a stand-in hash, which says whether it is absolute even when its value is not
+	 * settled yet. `"auto"` reads as the empty string, which needs a base and so takes
+	 * the path that needs no answer; a function that answers nothing says nothing about
+	 * its shape either, and nothing may be built on it.
+	 * @returns {string | undefined} the shape of the public path
+	 */
+	_publicPathShape() {
+		if (this._publicPathShapeText === undefined) {
+			const { publicPath } = this.outputOptions;
+			if (typeof publicPath !== "function") {
+				this._publicPathShapeText = publicPath === "auto" ? "" : publicPath;
+			} else {
+				try {
+					this._publicPathShapeText = this.compilation.getPath(publicPath, {
+						hash: HASH_PROBE
+					});
+				} catch (_error) {
+					this._publicPathShapeText = false;
+				}
+			}
+		}
+		return this._publicPathShapeText === false
+			? undefined
+			: this._publicPathShapeText;
+	}
+
+	/**
+	 * A path built by a function, resolved to the value it will have — or `null` when
+	 * it moves with the compilation hash, which code generation does not know.
+	 * @param {import("./TemplatedPathPlugin").TemplatePathFn} fn the function
+	 * @param {string | undefined} value its answer to the first probe, or `undefined`
+	 * when it answered nothing
+	 * @returns {string | null} the settled value, or `null` when it is hash-dependent
+	 */
+	_resolveHashIndependent(fn, value) {
+		// The shape is this function's answer to the first probe, already taken and
+		// memoized, so only the second one is new here.
+		if (value === undefined) return null;
+		try {
+			return this.compilation.getPath(fn, { hash: HASH_PROBE_ALTERNATE }) ===
+				value
+				? value
+				: null;
+		} catch (_error) {
+			// One that needs more than a hash can't be resolved here.
+			return null;
+		}
+	}
+
+	/**
+	 * Static literal specifier (already quoted) for a `new URL(, import.meta.url)`
+	 * pointing at an emitted file — an asset, or a wasm binary. The base is the asset the
+	 * reference sits in; the runtime form resolves against the output root instead, so a
+	 * public path that needs a base is put behind the `../` path back to that root rather
+	 * than baked as the whole prefix.
+	 * @param {Module} module the module the reference is emitted into
+	 * @param {ChunkGraph} chunkGraph the chunk graph
+	 * @param {SpecifierPart[]} file the emitted file's name, relative to the output root
+	 * @param {boolean} bakePublicPath whether the public path belongs in the literal
+	 * @param {string=} relativeBase an entry `baseUri` the runtime reads against the
+	 * chunk, so it belongs between the path back to the output root and the public path
+	 * @returns {string | null} a quoted literal, or `null` to fall back
+	 */
+	_getAnalyzableFileSpecifier(
+		module,
+		chunkGraph,
+		file,
+		bakePublicPath,
+		relativeBase
+	) {
+		const { publicPath } = this.outputOptions;
+		const chunks = this._moduleChunks(module, chunkGraph);
+		/**
+		 * @param {SpecifierPart[]} parts the whole specifier
+		 * @returns {string | null} it already quoted, or `null` when no stand-in may be reserved
+		 */
+		const specifier = (parts) => {
+			const text = literalText(parts);
+			if (text !== null) return toJsStringLiteral(text);
+			if (!this._canDeferAnalyzableName(chunks)) {
+				this._analyzableBailout(module, DEFER_BAILOUT);
+				return null;
+			}
+			return toJsStringLiteral(
+				getAnalyzableChunkHashPlugin().reserveSpecifier(parts)
+			);
+		};
+		if (bakePublicPath && publicPath !== "auto") {
+			const prefix = this._analyzablePathPrefix(
+				module,
+				chunkGraph,
+				chunks,
+				relativeBase
+			);
+			return prefix === null ? null : specifier([...prefix, ...file]);
+		}
+		// No public path to place it against, so the `../` path back to the output root
+		// is the whole prefix. Different depths — no one is right for every asset the
+		// reference is emitted into, so the deferred pass builds each asset's own.
+		const { undo } = this._modulePlacement(module, chunkGraph);
+		return specifier([
+			undo === null ? ["undo", ""] : ["literal", undo],
+			...(relativeBase === undefined
+				? []
+				: /** @type {SpecifierPart[]} */ ([["literal", relativeBase]])),
+			...file
+		]);
+	}
+
+	/**
+	 * Static `new URL(, import.meta.url)` for the binary emitted for an async wasm
+	 * module. Only called when `supportsAnalyzable("wasm")` holds.
+	 * @param {Module} module the async wasm module
+	 * @param {ChunkGraph} chunkGraph the chunk graph
+	 * @param {RuntimeSpec} runtime the runtime
+	 * @param {RuntimeRequirements} runtimeRequirements runtime requirements
+	 * @returns {string} expression evaluating to the binary's URL
+	 */
+	getAnalyzableWasmUrl(module, chunkGraph, runtime, runtimeRequirements) {
+		const { compilation } = this;
+		const template = /** @type {string} */ (
+			this.outputOptions.webassemblyModuleFilename
+		);
+		// The module's own hash and id are settled here, the compilation's is not — and
+		// a placeholder nothing answers is left in the name, so what comes back is either
+		// the final name or a template carrying only `[fullhash]`.
+		const filename = compilation.getPath(template, {
+			module,
+			runtime,
+			chunkGraph
+		});
+		// Only a fetched binary carries the public path: `readFile` addresses it relative
+		// to the chunk anyway. One needing a base is spelled as an asset url is.
+		const specifier = this._getAnalyzableFileSpecifier(
+			module,
+			chunkGraph,
+			[[filename.includes("[") ? "template" : "literal", filename]],
+			this._wasmModuleFetches(module, chunkGraph)
+		);
+		if (specifier !== null) return this.importMetaUrl(specifier);
+		// No bundler follows a concatenation, but this still sheds the module id and
+		// hash the runtime form would need.
+		runtimeRequirements.add(RuntimeGlobals.publicPath);
+		return this.importMetaUrl(
+			`${RuntimeGlobals.publicPath} + ${toJsStringLiteral(
+				compilation.getPath(template, {
+					module,
+					runtime,
+					chunkGraph,
+					...getAnalyzableChunkHashPlugin().DEFERRED_FULL_HASH_PATH_DATA
+				})
+			)}`
+		);
+	}
+
+	/**
+	 * The chunk filename template as a plain string. A function is called twice, with
+	 * a different stand-in hash each time; disagreeing answers mean the name depends on
+	 * a hash, which is not knowable during code generation, so it is left to the
+	 * deferred pass to ask again once the hashes are settled.
+	 * @param {ChunkFilenameTemplate} filenameTemplate the configured template
+	 * @param {Chunk} chunk the chunk being referenced
+	 * @returns {string | undefined | null} the template, `undefined` to defer, or
+	 * `null` to fall back
+	 */
+	_resolveChunkFilenameTemplate(filenameTemplate, chunk) {
+		if (typeof filenameTemplate === "string") return filenameTemplate;
+		try {
+			// Everything the naming call is given except the hashes, so only a hash can
+			// make the two answers differ.
+			const template = filenameTemplate({
+				chunk: createHashProbeChunk(chunk, HASH_PROBE, true),
+				runtime: chunk.runtime,
+				contentHashType: JAVASCRIPT_TYPE,
+				hash: HASH_PROBE,
+				contentHash: HASH_PROBE
+			});
+			const probe = filenameTemplate({
+				chunk: createHashProbeChunk(chunk, HASH_PROBE_ALTERNATE, false),
+				runtime: chunk.runtime,
+				contentHashType: JAVASCRIPT_TYPE,
+				hash: HASH_PROBE_ALTERNATE,
+				contentHash: HASH_PROBE_ALTERNATE
+			});
+			return template === probe ? template : undefined;
+		} catch (_error) {
+			// Nothing to report: the probe is given strictly more than the naming call,
+			// so one that throws here throws there too and fails the build regardless.
+			return null;
+		}
+	}
+
+	/**
+	 * Whether `module`'s binary is read through `fetch`, the one loader the public path
+	 * reaches. Asked of its runtimes, not its chunks: only an entry names a loader.
+	 * @param {Module} module the async wasm module
+	 * @param {ChunkGraph} chunkGraph the chunk graph
+	 * @returns {boolean} true when every runtime reaching it fetches
+	 */
+	_wasmModuleFetches(module, chunkGraph) {
+		/** @type {string[]} */
+		const keys = [];
+		for (const moduleRuntime of chunkGraph.getModuleRuntimes(module)) {
+			forEachRuntime(moduleRuntime, (key) => {
+				keys.push(/** @type {string} */ (key));
+			});
+		}
+		// In no runtime at all nothing named a loader, so `output` answers.
+		if (keys.length === 0) return this.outputOptions.wasmLoading === "fetch";
+		const only = this._wasmGroupOnlyFetches();
+		const groups = this._wasmRuntimeGroups();
+		for (const key of keys) {
+			if (!only.get(this._wasmGroupOf(groups, key))) return false;
+		}
+		return true;
+	}
+
+	/**
+	 * The loader a chunk's WebAssembly is read with. An entry names one, and every
+	 * other chunk of the runtime is served by the one its entry asked for — so one
+	 * that is no entry answers with what `output` says.
+	 * @param {Chunk} chunk the chunk
+	 * @returns {WasmLoading} the loader it is served by
+	 */
+	_chunkWasmLoading(chunk) {
+		const entryOptions = chunk.getEntryOptions();
+		return entryOptions && entryOptions.wasmLoading !== undefined
+			? entryOptions.wasmLoading
+			: this.outputOptions.wasmLoading;
+	}
+
+	/**
+	 * Async module factory.
+	 * @param {object} options options
+	 * @param {AsyncDependenciesBlock} options.block the async block
+	 * @param {ChunkGraph} options.chunkGraph the chunk graph
+	 * @param {RuntimeRequirements} options.runtimeRequirements if set, will be filled with runtime requirements
+	 * @param {string=} options.request request string used originally
+	 * @param {Module=} options.originModule the module the factory is emitted into
+	 * @returns {string} expression
+	 */
+	asyncModuleFactory({
+		block,
+		chunkGraph,
+		runtimeRequirements,
+		request,
+		originModule
+	}) {
+		const dep = block.dependencies[0];
+		const module = chunkGraph.moduleGraph.getModule(dep);
+		const ensureChunk = this.blockPromise({
+			block,
+			message: "",
+			chunkGraph,
+			runtimeRequirements,
+			originModule
+		});
+		const factory = this.returningFunction(
+			this.moduleRaw({
+				module,
+				chunkGraph,
+				request,
+				runtimeRequirements
+			})
+		);
+		return this.returningFunction(
+			ensureChunk.startsWith("Promise.resolve(")
+				? `${factory}`
+				: `${ensureChunk}.then(${this.returningFunction(factory)})`
+		);
+	}
+
+	/**
+	 * Sync module factory.
+	 * @param {object} options options
+	 * @param {Dependency} options.dependency the dependency
+	 * @param {ChunkGraph} options.chunkGraph the chunk graph
+	 * @param {RuntimeRequirements} options.runtimeRequirements if set, will be filled with runtime requirements
+	 * @param {string=} options.request request string used originally
+	 * @returns {string} expression
+	 */
+	syncModuleFactory({ dependency, chunkGraph, runtimeRequirements, request }) {
+		const module = chunkGraph.moduleGraph.getModule(dependency);
+		const factory = this.returningFunction(
+			this.moduleRaw({
+				module,
+				chunkGraph,
+				request,
+				runtimeRequirements
+			})
+		);
+		return this.returningFunction(factory);
+	}
+
+	/**
+	 * Define es module flag statement.
+	 * @param {object} options options
+	 * @param {string} options.exportsArgument the name of the exports object
+	 * @param {RuntimeRequirements} options.runtimeRequirements if set, will be filled with runtime requirements
+	 * @returns {string} statement
+	 */
+	defineEsModuleFlagStatement({ exportsArgument, runtimeRequirements }) {
+		runtimeRequirements.add(RuntimeGlobals.makeNamespaceObject);
+		runtimeRequirements.add(RuntimeGlobals.exports);
+		return `${RuntimeGlobals.makeNamespaceObject}(${exportsArgument});\n`;
+	}
+}
+
+module.exports = RuntimeTemplate;
diff --git a/lib/SelfModuleFactory.js b/lib/SelfModuleFactory.js
new file mode 100644
index 00000000000..39b59365633
--- /dev/null
+++ b/lib/SelfModuleFactory.js
@@ -0,0 +1,36 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+/** @typedef {import("./ModuleFactory").ModuleFactoryCallback} ModuleFactoryCallback */
+/** @typedef {import("./ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */
+/** @typedef {import("./ModuleGraph")} ModuleGraph */
+
+class SelfModuleFactory {
+	/**
+	 * Creates an instance of SelfModuleFactory.
+	 * @param {ModuleGraph} moduleGraph module graph
+	 */
+	constructor(moduleGraph) {
+		/** @type {ModuleGraph} */
+		this.moduleGraph = moduleGraph;
+	}
+
+	/**
+	 * Processes the provided data.
+	 * @param {ModuleFactoryCreateData} data data object
+	 * @param {ModuleFactoryCallback} callback callback
+	 * @returns {void}
+	 */
+	create(data, callback) {
+		const module = this.moduleGraph.getParentModule(data.dependencies[0]);
+		callback(null, {
+			module
+		});
+	}
+}
+
+module.exports = SelfModuleFactory;
diff --git a/lib/SetVarMainTemplatePlugin.js b/lib/SetVarMainTemplatePlugin.js
deleted file mode 100644
index 63db2821a54..00000000000
--- a/lib/SetVarMainTemplatePlugin.js
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const { ConcatSource } = require("webpack-sources");
-
-/** @typedef {import("./Compilation")} Compilation */
-
-class SetVarMainTemplatePlugin {
-	/**
-	 * @param {string} varExpression the accessor where the library is exported
-	 * @param {boolean} copyObject specify copying the exports
-	 */
-	constructor(varExpression, copyObject) {
-		/** @type {string} */
-		this.varExpression = varExpression;
-		/** @type {boolean} */
-		this.copyObject = copyObject;
-	}
-
-	/**
-	 * @param {Compilation} compilation the compilation instance
-	 * @returns {void}
-	 */
-	apply(compilation) {
-		const { mainTemplate, chunkTemplate } = compilation;
-
-		const onRenderWithEntry = (source, chunk, hash) => {
-			const varExpression = mainTemplate.getAssetPath(this.varExpression, {
-				hash,
-				chunk
-			});
-			if (this.copyObject) {
-				return new ConcatSource(
-					`(function(e, a) { for(var i in a) e[i] = a[i]; }(${varExpression}, `,
-					source,
-					"))"
-				);
-			} else {
-				const prefix = `${varExpression} =\n`;
-				return new ConcatSource(prefix, source);
-			}
-		};
-
-		for (const template of [mainTemplate, chunkTemplate]) {
-			template.hooks.renderWithEntry.tap(
-				"SetVarMainTemplatePlugin",
-				onRenderWithEntry
-			);
-		}
-
-		mainTemplate.hooks.globalHashPaths.tap(
-			"SetVarMainTemplatePlugin",
-			paths => {
-				if (this.varExpression) paths.push(this.varExpression);
-				return paths;
-			}
-		);
-		mainTemplate.hooks.hash.tap("SetVarMainTemplatePlugin", hash => {
-			hash.update("set var");
-			hash.update(`${this.varExpression}`);
-			hash.update(`${this.copyObject}`);
-		});
-	}
-}
-
-module.exports = SetVarMainTemplatePlugin;
diff --git a/lib/SingleEntryPlugin.js b/lib/SingleEntryPlugin.js
index 755a6b59725..65791735c79 100644
--- a/lib/SingleEntryPlugin.js
+++ b/lib/SingleEntryPlugin.js
@@ -1,44 +1,8 @@
 /*
 	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
+	Author Sean Larkin @thelarkinn
 */
-"use strict";
-const SingleEntryDependency = require("./dependencies/SingleEntryDependency");
-
-class SingleEntryPlugin {
-	constructor(context, entry, name) {
-		this.context = context;
-		this.entry = entry;
-		this.name = name;
-	}
-
-	apply(compiler) {
-		compiler.hooks.compilation.tap(
-			"SingleEntryPlugin",
-			(compilation, { normalModuleFactory }) => {
-				compilation.dependencyFactories.set(
-					SingleEntryDependency,
-					normalModuleFactory
-				);
-			}
-		);
 
-		compiler.hooks.make.tapAsync(
-			"SingleEntryPlugin",
-			(compilation, callback) => {
-				const { entry, name, context } = this;
-
-				const dep = SingleEntryPlugin.createDependency(entry, name);
-				compilation.addEntry(context, dep, name, callback);
-			}
-		);
-	}
-
-	static createDependency(entry, name) {
-		const dep = new SingleEntryDependency(entry);
-		dep.loc = name;
-		return dep;
-	}
-}
+"use strict";
 
-module.exports = SingleEntryPlugin;
+module.exports = require("./EntryPlugin");
diff --git a/lib/SizeFormatHelpers.js b/lib/SizeFormatHelpers.js
deleted file mode 100644
index c4677f60bf8..00000000000
--- a/lib/SizeFormatHelpers.js
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Sean Larkin @thelarkinn
-*/
-"use strict";
-
-const SizeFormatHelpers = exports;
-
-SizeFormatHelpers.formatSize = size => {
-	if (typeof size !== "number" || Number.isNaN(size) === true) {
-		return "unknown size";
-	}
-
-	if (size <= 0) {
-		return "0 bytes";
-	}
-
-	const abbreviations = ["bytes", "KiB", "MiB", "GiB"];
-	const index = Math.floor(Math.log(size) / Math.log(1024));
-
-	return `${+(size / Math.pow(1024, index)).toPrecision(3)} ${
-		abbreviations[index]
-	}`;
-};
diff --git a/lib/SourceMapDevToolModuleOptionsPlugin.js b/lib/SourceMapDevToolModuleOptionsPlugin.js
index e593ced7b3d..7e130de6845 100644
--- a/lib/SourceMapDevToolModuleOptionsPlugin.js
+++ b/lib/SourceMapDevToolModuleOptionsPlugin.js
@@ -2,47 +2,52 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
-const ModuleFilenameHelpers = require("./ModuleFilenameHelpers");
+const JavascriptModulesPlugin = require("./javascript/JavascriptModulesPlugin");
+
+/** @typedef {import("../declarations/plugins/SourceMapDevToolPlugin").SourceMapDevToolPluginOptions} SourceMapDevToolPluginOptions */
+/** @typedef {import("./Compilation")} Compilation */
+
+const PLUGIN_NAME = "SourceMapDevToolModuleOptionsPlugin";
 
 class SourceMapDevToolModuleOptionsPlugin {
-	constructor(options) {
+	/**
+	 * Creates an instance of SourceMapDevToolModuleOptionsPlugin.
+	 * @param {SourceMapDevToolPluginOptions=} options options
+	 */
+	constructor(options = {}) {
+		/** @type {SourceMapDevToolPluginOptions} */
 		this.options = options;
 	}
 
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compilation} compilation the compiler instance
+	 * @returns {void}
+	 */
 	apply(compilation) {
 		const options = this.options;
 		if (options.module !== false) {
-			compilation.hooks.buildModule.tap(
-				"SourceMapDevToolModuleOptionsPlugin",
-				module => {
-					module.useSourceMap = true;
-				}
-			);
-		}
-		if (options.lineToLine === true) {
-			compilation.hooks.buildModule.tap(
-				"SourceMapDevToolModuleOptionsPlugin",
-				module => {
-					module.lineToLine = true;
-				}
-			);
-		} else if (options.lineToLine) {
-			compilation.hooks.buildModule.tap(
-				"SourceMapDevToolModuleOptionsPlugin",
-				module => {
-					if (!module.resource) return;
-					let resourcePath = module.resource;
-					const idx = resourcePath.indexOf("?");
-					if (idx >= 0) resourcePath = resourcePath.substr(0, idx);
-					module.lineToLine = ModuleFilenameHelpers.matchObject(
-						options.lineToLine,
-						resourcePath
-					);
-				}
-			);
+			compilation.hooks.buildModule.tap(PLUGIN_NAME, (module) => {
+				module.useSourceMap = true;
+			});
+			compilation.hooks.runtimeModule.tap(PLUGIN_NAME, (module) => {
+				module.useSourceMap = true;
+			});
+		} else {
+			compilation.hooks.buildModule.tap(PLUGIN_NAME, (module) => {
+				module.useSimpleSourceMap = true;
+			});
+			compilation.hooks.runtimeModule.tap(PLUGIN_NAME, (module) => {
+				module.useSimpleSourceMap = true;
+			});
 		}
+		JavascriptModulesPlugin.getCompilationHooks(compilation).useSourceMap.tap(
+			PLUGIN_NAME,
+			() => true
+		);
 	}
 }
 
diff --git a/lib/SourceMapDevToolPlugin.js b/lib/SourceMapDevToolPlugin.js
index 111d7a877ff..e8c5a5ea5c4 100644
--- a/lib/SourceMapDevToolPlugin.js
+++ b/lib/SourceMapDevToolPlugin.js
@@ -2,81 +2,303 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
-const path = require("path");
+const asyncLib = require("neo-async");
 const { ConcatSource, RawSource } = require("webpack-sources");
+const Compilation = require("./Compilation");
 const ModuleFilenameHelpers = require("./ModuleFilenameHelpers");
+const ProgressPlugin = require("./ProgressPlugin");
 const SourceMapDevToolModuleOptionsPlugin = require("./SourceMapDevToolModuleOptionsPlugin");
+const { getPresentKinds } = require("./TemplatedPathPlugin");
 const createHash = require("./util/createHash");
+const { dirname, relative } = require("./util/fs");
+const generateDebugId = require("./util/generateDebugId");
+const { makePathsAbsolute } = require("./util/identifier");
+
+/** @typedef {import("webpack-sources").MapOptions} MapOptions */
+/** @typedef {import("webpack-sources").Source} Source */
+/** @typedef {import("../declarations/WebpackOptions").DevtoolNamespace} DevtoolNamespace */
+/** @typedef {import("../declarations/WebpackOptions").DevtoolModuleFilenameTemplate} DevtoolModuleFilenameTemplate */
+/** @typedef {import("../declarations/WebpackOptions").DevtoolFallbackModuleFilenameTemplate} DevtoolFallbackModuleFilenameTemplate */
+/** @typedef {import("../declarations/plugins/SourceMapDevToolPlugin").SourceMapDevToolPluginOptions} SourceMapDevToolPluginOptions */
+/** @typedef {import("../declarations/plugins/SourceMapDevToolPlugin").Rules} Rules */
+/** @typedef {import("./CacheFacade").ItemCacheFacade} ItemCacheFacade */
+/** @typedef {import("./Chunk")} Chunk */
+/** @typedef {import("./Compilation").Asset} Asset */
+/** @typedef {import("./Compilation").AssetInfo} AssetInfo */
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./Module")} Module */
+/** @typedef {import("./NormalModule").RawSourceMap} RawSourceMap */
+/** @typedef {import("./TemplatedPathPlugin").TemplatePath} SourceMappingURLComment */
+/** @typedef {import("./util/fs").OutputFileSystem} OutputFileSystem */
+
+/**
+ * Defines the source map task type used by this module.
+ * @typedef {object} SourceMapTask
+ * @property {AssetInfo} assetInfo
+ * @property {(string | Module)[]} modules
+ * @property {string} source
+ * @property {string} file
+ * @property {RawSourceMap} sourceMap
+ * @property {Source} mapSource the Source instance whose `sourceAndMap` we called (the current asset or, when its map was already stripped, the pinned original from `originalSources`) — what `clearCache` should target
+ * @property {ItemCacheFacade} cacheItem cache item
+ */
 
-const validateOptions = require("schema-utils");
-const schema = require("../schemas/plugins/SourceMapDevToolPlugin.json");
+const METACHARACTERS_REGEXP = /[-[\]\\/{}()*+?.^$|]/g;
+const CSS_AND_JS_MODULE_EXTENSIONS_REGEXP = /\.((c|m)?js|css)($|\?)/i;
+const CSS_EXTENSION_DETECT_REGEXP = /\.css(?:$|\?)/i;
+const MAP_URL_COMMENT_REGEXP = /\[map\]/g;
+const URL_COMMENT_REGEXP = /\[url\]/g;
+const URL_FORMATTING_REGEXP = /^\n\/\/(.*)$/;
 
-const basename = name => {
-	if (!name.includes("/")) return name;
-	return name.substr(name.lastIndexOf("/") + 1);
+/**
+ * Reset's .lastIndex of stateful Regular Expressions
+ * For when `test` or `exec` is called on them
+ * @param {RegExp} regexp Stateful Regular Expression to be reset
+ * @returns {void}
+ */
+const resetRegexpState = (regexp) => {
+	regexp.lastIndex = -1;
 };
 
-const assetsCache = new WeakMap();
-
-const getTaskForFile = (file, chunk, options, compilation) => {
-	const asset = compilation.assets[file];
-	const cache = assetsCache.get(asset);
-	if (cache && cache.file === file) {
-		for (const cachedFile in cache.assets) {
-			compilation.assets[cachedFile] = cache.assets[cachedFile];
-			if (cachedFile !== file) chunk.files.push(cachedFile);
-		}
-		return;
+/**
+ * Escapes regular expression metacharacters
+ * @param {string} str String to quote
+ * @returns {string} Escaped string
+ */
+const quoteMeta = (str) => str.replace(METACHARACTERS_REGEXP, "\\$&");
+
+/**
+ * Compilation-scoped registry of original asset sources for multi-plugin
+ * cooperation. The first SourceMapDevToolPlugin instance to see a file pins a
+ * reference to the asset's still-unwrapped {@link Source} object; later
+ * instances whose `asset.source.sourceAndMap()` would now return `null` (the
+ * earlier instance replaced the asset with a `RawSource`) can re-extract the
+ * map from this pinned reference. We keep the registry on a module-scoped
+ * `WeakMap` so the entries are reclaimed automatically when the compilation
+ * itself becomes unreachable; we never store anything on the compilation
+ * object directly.
+ *
+ * Stashing the `Source` object itself rather than an extracted map keeps the
+ * fast path free of cloning and source-map serialization work — the
+ * extraction only happens if a subsequent plugin actually needs the map.
+ * @type {WeakMap>}
+ */
+const originalSourceRegistry = new WeakMap();
+
+/**
+ * Returns (creating if necessary) the per-compilation registry of original
+ * asset {@link Source} objects.
+ * @param {Compilation} compilation compilation
+ * @returns {Map} registry
+ */
+const getOriginalSourceRegistry = (compilation) => {
+	let registry = originalSourceRegistry.get(compilation);
+	if (registry === undefined) {
+		registry = new Map();
+		originalSourceRegistry.set(compilation, registry);
 	}
-	let source, sourceMap;
+	return registry;
+};
+
+/**
+ * Extracts source and source map from a Source object, falling back to a
+ * registered original source for assets that another SourceMapDevToolPlugin
+ * instance has already wrapped (whose internal map is now `null`).
+ *
+ * The returned source is read from the asset as it currently stands — that way
+ * any `sourceMappingURL` comments appended by earlier plugin instances survive
+ * — while the map is taken from the pinned original Source when the current
+ * one no longer carries it. `mapSource` identifies which Source instance was
+ * actually queried for the map (the current asset, or the pinned original);
+ * that's the one whose internal caches the caller should release.
+ * @param {string} file file name
+ * @param {Source} asset source object as currently held by the compilation
+ * @param {MapOptions} options map extraction options
+ * @param {Map} registry compilation-scoped original-source registry
+ * @returns {{ source: string, sourceMap: RawSourceMap, mapSource: Source } | undefined} extracted pair or `undefined` when no map is recoverable
+ */
+const extractSourceAndMap = (file, asset, options, registry) => {
+	/** @type {string | Buffer} */
+	let source;
+	/** @type {null | RawSourceMap} */
+	let sourceMap;
 	if (asset.sourceAndMap) {
 		const sourceAndMap = asset.sourceAndMap(options);
-		sourceMap = sourceAndMap.map;
 		source = sourceAndMap.source;
+		sourceMap = sourceAndMap.map;
 	} else {
-		sourceMap = asset.map(options);
 		source = asset.source();
+		sourceMap = asset.map(options);
 	}
+	// Bail before touching the registry if we can't return a usable string
+	// source — pinning a non-string-producing asset would only waste the slot.
+	if (typeof source !== "string") return;
 	if (sourceMap) {
-		return {
-			chunk,
-			file,
-			asset,
-			source,
-			sourceMap,
-			modules: undefined
-		};
+		// The current asset still owns the original map — pin a reference so
+		// that a later plugin instance (which will see a rewrapped asset
+		// without a map) can recover it on demand.
+		if (!registry.has(file)) registry.set(file, asset);
+		return { source, sourceMap, mapSource: asset };
 	}
+	// The current asset (typically a `RawSource` left by an earlier
+	// SourceMapDevToolPlugin instance) has no internal map. Re-extract
+	// the map from the original Source we pinned earlier. We keep using
+	// `source` from the current asset so that any prior wrappers (e.g.
+	// appended sourceMappingURL comments) are preserved.
+	const original = registry.get(file);
+	if (!original) return;
+	sourceMap = original.sourceAndMap
+		? original.sourceAndMap(options).map
+		: original.map(options);
+	if (!sourceMap) return;
+	return { source, sourceMap, mapSource: original };
 };
 
-class SourceMapDevToolPlugin {
-	constructor(options) {
-		if (arguments.length > 1) {
-			throw new Error(
-				"SourceMapDevToolPlugin only takes one argument (pass an options object)"
-			);
-		}
+/**
+ * Creating {@link SourceMapTask} for given file
+ * @param {string} file current compiled file
+ * @param {Source} asset the asset
+ * @param {AssetInfo} assetInfo the asset info
+ * @param {MapOptions} options source map options
+ * @param {Compilation} compilation compilation instance
+ * @param {ItemCacheFacade} cacheItem cache item
+ * @param {Map} registry compilation-scoped original-source registry
+ * @returns {SourceMapTask | undefined} created task instance or `undefined`
+ */
+const getTaskForFile = (
+	file,
+	asset,
+	assetInfo,
+	options,
+	compilation,
+	cacheItem,
+	registry
+) => {
+	const extracted = extractSourceAndMap(file, asset, options, registry);
+	if (!extracted) return;
+	const { source, sourceMap, mapSource } = extracted;
+	const context = compilation.options.context;
+	const root = compilation.compiler.root;
+	const cachedAbsolutify = makePathsAbsolute.bindContextCache(context, root);
+	const modules = sourceMap.sources.map((source) => {
+		if (!source.startsWith("webpack://")) return source;
+		source = cachedAbsolutify(source.slice(10));
+		const module = compilation.findModule(source);
+		return module || source;
+	});
+
+	return {
+		file,
+		source: /** @type {string} */ (source),
+		assetInfo,
+		sourceMap,
+		mapSource,
+		modules,
+		cacheItem
+	};
+};
 
-		validateOptions(schema, options || {}, "SourceMap DevTool Plugin");
+const PLUGIN_NAME = "SourceMapDevToolPlugin";
+
+/**
+ * Maps a configuration value (string, RegExp, function, nullish, or array of
+ * such) into a JSON-serializable form. Functions and RegExps are turned into
+ * their `.toString()` representation so that changes to inline callbacks
+ * invalidate caches; everything else is returned as-is so that the surrounding
+ * `JSON.stringify` does the escaping.
+ *
+ * The result is used through `JSON.stringify` to build cache identifiers, so
+ * we deliberately avoid any homemade `|` / `,` separators that could collide
+ * with characters appearing inside user-provided values such as `publicPath`,
+ * template strings, or `sourceRoot`.
+ * @param {EXPECTED_ANY} value option value
+ * @returns {EXPECTED_ANY} JSON-serializable representation
+ */
+const toCacheKeyValue = (value) => {
+	if (value === undefined || value === null) return value;
+	if (Array.isArray(value)) return value.map(toCacheKeyValue);
+	if (value instanceof RegExp || typeof value === "function") {
+		return value.toString();
+	}
+	return value;
+};
 
-		if (!options) options = {};
+class SourceMapDevToolPlugin {
+	/**
+	 * Creates an instance of SourceMapDevToolPlugin.
+	 * @param {SourceMapDevToolPluginOptions=} options options object
+	 * @throws {Error} throws error, if got more than 1 arguments
+	 */
+	constructor(options = {}) {
+		/** @type {undefined | null | false | string} */
 		this.sourceMapFilename = options.filename;
+		/** @type {false | SourceMappingURLComment} */
 		this.sourceMappingURLComment =
 			options.append === false
 				? false
-				: options.append || "\n//# sourceMappingURL=[url]";
+				: // eslint-disable-next-line no-useless-concat
+					options.append || "\n//# source" + "MappingURL=[url]";
+		/** @type {DevtoolModuleFilenameTemplate} */
 		this.moduleFilenameTemplate =
-			options.moduleFilenameTemplate || "webpack://[namespace]/[resourcePath]";
+			options.moduleFilenameTemplate ||
+			ModuleFilenameHelpers.DEFAULT_MODULE_FILENAME_TEMPLATE;
+		/** @type {DevtoolFallbackModuleFilenameTemplate} */
 		this.fallbackModuleFilenameTemplate =
 			options.fallbackModuleFilenameTemplate ||
-			"webpack://[namespace]/[resourcePath]?[hash]";
+			ModuleFilenameHelpers.DEFAULT_FALLBACK_MODULE_FILENAME_TEMPLATE;
+		/** @type {DevtoolNamespace} */
 		this.namespace = options.namespace || "";
+		/** @type {SourceMapDevToolPluginOptions} */
 		this.options = options;
+		// Cache salt derived from output-affecting options, so that two
+		// SourceMapDevToolPlugin instances (or `devtool` + a plugin) operating
+		// on the same asset don't share a cache entry. We serialize via
+		// `JSON.stringify` rather than a homemade separator so that any
+		// special characters (e.g. `|` inside a publicPath or sourceRoot)
+		// can't accidentally make two different option sets collide.
+		/** @type {string} */
+		this._cacheSalt = JSON.stringify([
+			toCacheKeyValue(options.filename),
+			toCacheKeyValue(options.append),
+			toCacheKeyValue(this.moduleFilenameTemplate),
+			toCacheKeyValue(this.fallbackModuleFilenameTemplate),
+			toCacheKeyValue(this.namespace),
+			options.module !== false,
+			options.columns !== false,
+			Boolean(options.noSources),
+			Boolean(options.debugIds),
+			options.sourceRoot || "",
+			toCacheKeyValue(options.ignoreList),
+			options.publicPath || "",
+			options.fileContext || ""
+		]);
 	}
 
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler compiler instance
+	 * @returns {void}
+	 */
 	apply(compiler) {
+		compiler.hooks.validate.tap(PLUGIN_NAME, () => {
+			compiler.validate(
+				() => require("../schemas/plugins/SourceMapDevToolPlugin.json"),
+				this.options,
+				{
+					name: "SourceMap DevTool Plugin",
+					baseDataPath: "options"
+				},
+				(options) =>
+					require("../schemas/plugins/SourceMapDevToolPlugin.check")(options)
+			);
+		});
+
+		const outputFs =
+			/** @type {OutputFileSystem} */
+			(compiler.outputFileSystem);
 		const sourceMapFilename = this.sourceMapFilename;
 		const sourceMappingURLComment = this.sourceMappingURLComment;
 		const moduleFilenameTemplate = this.moduleFilenameTemplate;
@@ -84,214 +306,618 @@ class SourceMapDevToolPlugin {
 		const fallbackModuleFilenameTemplate = this.fallbackModuleFilenameTemplate;
 		const requestShortener = compiler.requestShortener;
 		const options = this.options;
-		options.test = options.test || /\.(js|css)($|\?)/i;
+		options.test = options.test || CSS_AND_JS_MODULE_EXTENSIONS_REGEXP;
 
+		/** @type {(filename: string) => boolean} */
 		const matchObject = ModuleFilenameHelpers.matchObject.bind(
 			undefined,
 			options
 		);
 
-		compiler.hooks.compilation.tap("SourceMapDevToolPlugin", compilation => {
+		compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
 			new SourceMapDevToolModuleOptionsPlugin(options).apply(compilation);
 
-			compilation.hooks.afterOptimizeChunkAssets.tap(
+			// All SourceMapDevToolPlugin instances on the same compilation share
+			// a registry of pristine asset sources, so the second instance to
+			// run can still recover the original map after the first instance
+			// has replaced the asset with a `RawSource`. The registry lives on a
+			// module-scoped `WeakMap` keyed by compilation so it is released
+			// automatically and never pollutes the compilation object.
+			const originalSources = getOriginalSourceRegistry(compilation);
+
+			compilation.hooks.processAssets.tapAsync(
 				{
-					name: "SourceMapDevToolPlugin",
-					context: true
+					name: PLUGIN_NAME,
+					stage: Compilation.PROCESS_ASSETS_STAGE_DEV_TOOLING,
+					additionalAssets: true
 				},
-				(context, chunks) => {
+				(assets, callback) => {
+					const chunkGraph = compilation.chunkGraph;
+					const cache = compilation.getCache(PLUGIN_NAME);
+					/** @type {Map} */
 					const moduleToSourceNameMapping = new Map();
 					const reportProgress =
-						context && context.reportProgress
-							? context.reportProgress
-							: () => {};
+						ProgressPlugin.getReporter(compilation.compiler) || (() => {});
 
-					const files = [];
-					for (const chunk of chunks) {
+					/** @type {Map} */
+					const fileToChunk = new Map();
+					for (const chunk of compilation.chunks) {
 						for (const file of chunk.files) {
-							if (matchObject(file)) {
-								files.push({
-									file,
-									chunk
-								});
-							}
+							fileToChunk.set(file, chunk);
+						}
+						for (const file of chunk.auxiliaryFiles) {
+							fileToChunk.set(file, chunk);
+						}
+					}
+
+					/** @type {string[]} */
+					const files = [];
+					for (const file of Object.keys(assets)) {
+						if (matchObject(file)) {
+							files.push(file);
 						}
 					}
 
-					reportProgress(0.0);
+					reportProgress(0);
+					/** @type {SourceMapTask[]} */
 					const tasks = [];
-					files.forEach(({ file, chunk }, idx) => {
-						reportProgress(
-							(0.5 * idx) / files.length,
-							file,
-							"generate SourceMap"
-						);
-						const task = getTaskForFile(file, chunk, options, compilation);
-
-						if (task) {
-							const modules = task.sourceMap.sources.map(source => {
-								const module = compilation.findModule(source);
-								return module || source;
+					let fileIndex = 0;
+
+					// Shared deduplication set for `Source#clearCache` calls below.
+					// Webpack chunks routinely share module-level `CachedSource`
+					// instances. A per-call WeakSet would re-walk those shared
+					// subtrees once per chunk — 50 chunks × thousands of shared
+					// modules in dev/non-minified setups — and worse, every
+					// chunk's `sourceAndMap` would have to recompute the cleared
+					// caches, churning allocations (measured: +700 MB peak RSS,
+					// +6 s wall time on a 50×1000 synthetic build).
+					//
+					// Sharing one set lets each shared subtree be walked exactly
+					// once. The trade-off is that subsequent chunks' `sourceAndMap`
+					// calls can repopulate a shared module's `_cachedMaps` after
+					// its own clear was skipped (because the module is already in
+					// the visited set), leaving at most one populated cache entry
+					// per shared module at the end of the run — bounded to a few
+					// MB even at the scale of #20961. That's strictly preferable
+					// to the alternative's hundreds of MB of transient peak RSS.
+					const clearCacheVisited = new WeakSet();
+
+					asyncLib.each(
+						files,
+						(file, callback) => {
+							const asset =
+								/** @type {Readonly} */
+								(compilation.getAsset(file));
+
+							const chunk = fileToChunk.get(file);
+							const sourceMapNamespace = compilation.getPath(this.namespace, {
+								chunk
 							});
 
-							for (let idx = 0; idx < modules.length; idx++) {
-								const module = modules[idx];
-								if (!moduleToSourceNameMapping.get(module)) {
-									moduleToSourceNameMapping.set(
-										module,
-										ModuleFilenameHelpers.createFilename(
-											module,
-											{
-												moduleFilenameTemplate: moduleFilenameTemplate,
-												namespace: namespace
-											},
-											requestShortener
-										)
+							// The cache item identifier must include the per-instance
+							// salt so two SourceMapDevToolPlugin instances that target
+							// the same `file` don't collide in the persistent cache —
+							// they'd otherwise write different content to the same key
+							// and invalidate every pack on each build. We encode via
+							// `JSON.stringify` so that special characters (e.g. `|`)
+							// in an asset filename can't be spoofed to collide with the
+							// salt portion of the identifier.
+							const cacheItem = cache.getItemCache(
+								JSON.stringify([file, this._cacheSalt]),
+								cache.mergeEtags(
+									cache.getLazyHashedEtag(asset.source),
+									sourceMapNamespace
+								)
+							);
+
+							cacheItem.get((err, cacheEntry) => {
+								if (err) {
+									return callback(err);
+								}
+								/**
+								 * If presented in cache, reassigns assets. Cache assets already have source maps.
+								 */
+								if (cacheEntry) {
+									// Pin the still-unwrapped asset source in the registry
+									// before `compilation.updateAsset` replaces it. This is a
+									// pointer assignment — no source-map extraction work — and
+									// it lets a subsequent SourceMapDevToolPlugin instance
+									// extract the original map on demand even though the
+									// persistent cache hit lets us skip processing here.
+									if (!originalSources.has(file)) {
+										originalSources.set(file, asset.source);
+									}
+
+									const { assets, assetsInfo } = cacheEntry;
+									for (const cachedFile of Object.keys(assets)) {
+										if (cachedFile === file) {
+											compilation.updateAsset(
+												cachedFile,
+												assets[cachedFile],
+												assetsInfo[cachedFile]
+											);
+										} else {
+											compilation.emitAsset(
+												cachedFile,
+												assets[cachedFile],
+												assetsInfo[cachedFile]
+											);
+										}
+										/**
+										 * Add file to chunk, if not presented there
+										 */
+										if (cachedFile !== file && chunk !== undefined) {
+											chunk.auxiliaryFiles.add(cachedFile);
+										}
+									}
+
+									reportProgress(
+										(0.5 * ++fileIndex) / files.length,
+										file,
+										"restored cached SourceMap"
 									);
+
+									return callback();
 								}
-							}
 
-							task.modules = modules;
+								reportProgress(
+									(0.5 * fileIndex) / files.length,
+									file,
+									"generate SourceMap"
+								);
 
-							tasks.push(task);
-						}
-					});
-
-					reportProgress(0.5, "resolve sources");
-					const usedNamesSet = new Set(moduleToSourceNameMapping.values());
-					const conflictDetectionSet = new Set();
-
-					// all modules in defined order (longest identifier first)
-					const allModules = Array.from(moduleToSourceNameMapping.keys()).sort(
-						(a, b) => {
-							const ai = typeof a === "string" ? a : a.identifier();
-							const bi = typeof b === "string" ? b : b.identifier();
-							return ai.length - bi.length;
-						}
-					);
+								/** @type {SourceMapTask | undefined} */
+								const task = getTaskForFile(
+									file,
+									asset.source,
+									asset.info,
+									{
+										module: options.module,
+										columns: options.columns
+									},
+									compilation,
+									cacheItem,
+									originalSources
+								);
 
-					// find modules with conflicting source names
-					for (let idx = 0; idx < allModules.length; idx++) {
-						const module = allModules[idx];
-						let sourceName = moduleToSourceNameMapping.get(module);
-						let hasName = conflictDetectionSet.has(sourceName);
-						if (!hasName) {
-							conflictDetectionSet.add(sourceName);
-							continue;
-						}
+								// Release the per-instance caches that `sourceAndMap`
+								// just populated. The composed map (and, for
+								// `SourceMapSource`, the parsed `_sourceMapAsObject` /
+								// `_innerSourceMapAsObject`) otherwise sit on the
+								// CachedSource — and every shared child — until phase
+								// 2 replaces the asset, which is what causes the OOM
+								// spike on builds with thousands of chunks
+								// (webpack#20961). Keep `source` since downstream
+								// consumers reading the original asset still need it;
+								// `hash`/`size` default to retained because they're
+								// cheap to keep but expensive to rebuild.
+								// `clearCacheVisited` is shared across every call (see
+								// its declaration above for the rationale).
+								//
+								// Target `task.mapSource` (not `asset.source`): when
+								// `extractSourceAndMap` falls back to the pinned
+								// original (the current asset is a `RawSource` left
+								// by an earlier plugin instance), the `sourceAndMap`
+								// call populated the original's caches, not the
+								// current asset's.
+								//
+								// Feature-detected: `clearCache` landed in
+								// `webpack-sources` 3.5, but `compilation.assets` may
+								// hold `Source`-like instances from a third-party
+								// plugin built against an older copy of
+								// `webpack-sources` (or a hand-rolled implementation).
+								// Calling it unconditionally would throw on those.
+								if (task && typeof task.mapSource.clearCache === "function") {
+									task.mapSource.clearCache(
+										{
+											maps: true,
+											source: false,
+											parsedMap: true
+										},
+										clearCacheVisited
+									);
+								}
 
-						// try the fallback name first
-						sourceName = ModuleFilenameHelpers.createFilename(
-							module,
-							{
-								moduleFilenameTemplate: fallbackModuleFilenameTemplate,
-								namespace: namespace
-							},
-							requestShortener
-						);
-						hasName = usedNamesSet.has(sourceName);
-						if (!hasName) {
-							moduleToSourceNameMapping.set(module, sourceName);
-							usedNamesSet.add(sourceName);
-							continue;
-						}
+								if (task) {
+									const modules = task.modules;
 
-						// elsewise just append stars until we have a valid name
-						while (hasName) {
-							sourceName += "*";
-							hasName = usedNamesSet.has(sourceName);
-						}
-						moduleToSourceNameMapping.set(module, sourceName);
-						usedNamesSet.add(sourceName);
-					}
-					tasks.forEach((task, index) => {
-						reportProgress(
-							0.5 + (0.5 * index) / tasks.length,
-							task.file,
-							"attach SourceMap"
-						);
-						const assets = Object.create(null);
-						const chunk = task.chunk;
-						const file = task.file;
-						const asset = task.asset;
-						const sourceMap = task.sourceMap;
-						const source = task.source;
-						const modules = task.modules;
-						const moduleFilenames = modules.map(m =>
-							moduleToSourceNameMapping.get(m)
-						);
-						sourceMap.sources = moduleFilenames;
-						if (options.noSources) {
-							sourceMap.sourcesContent = undefined;
-						}
-						sourceMap.sourceRoot = options.sourceRoot || "";
-						sourceMap.file = file;
-						assetsCache.set(asset, { file, assets });
-						let currentSourceMappingURLComment = sourceMappingURLComment;
-						if (
-							currentSourceMappingURLComment !== false &&
-							/\.css($|\?)/i.test(file)
-						) {
-							currentSourceMappingURLComment = currentSourceMappingURLComment.replace(
-								/^\n\/\/(.*)$/,
-								"\n/*$1*/"
-							);
-						}
-						const sourceMapString = JSON.stringify(sourceMap);
-						if (sourceMapFilename) {
-							let filename = file;
-							let query = "";
-							const idx = filename.indexOf("?");
-							if (idx >= 0) {
-								query = filename.substr(idx);
-								filename = filename.substr(0, idx);
-							}
-							let sourceMapFile = compilation.getPath(sourceMapFilename, {
-								chunk,
-								filename: options.fileContext
-									? path.relative(options.fileContext, filename)
-									: filename,
-								query,
-								basename: basename(filename),
-								contentHash: createHash("md4")
-									.update(sourceMapString)
-									.digest("hex")
+									for (let idx = 0; idx < modules.length; idx++) {
+										const module = modules[idx];
+
+										if (
+											typeof module === "string" &&
+											/^(?:data|https?):/.test(module)
+										) {
+											moduleToSourceNameMapping.set(module, module);
+											continue;
+										}
+
+										if (!moduleToSourceNameMapping.get(module)) {
+											moduleToSourceNameMapping.set(
+												module,
+												ModuleFilenameHelpers.createFilename(
+													module,
+													{
+														moduleFilenameTemplate,
+														namespace: sourceMapNamespace
+													},
+													{
+														requestShortener,
+														chunkGraph,
+														hashFunction: compilation.outputOptions.hashFunction
+													}
+												)
+											);
+										}
+									}
+
+									tasks.push(task);
+								}
+
+								reportProgress(
+									(0.5 * ++fileIndex) / files.length,
+									file,
+									"generated SourceMap"
+								);
+
+								callback();
 							});
-							const sourceMapUrl = options.publicPath
-								? options.publicPath + sourceMapFile.replace(/\\/g, "/")
-								: path
-										.relative(path.dirname(file), sourceMapFile)
-										.replace(/\\/g, "/");
-							if (currentSourceMappingURLComment !== false) {
-								assets[file] = compilation.assets[file] = new ConcatSource(
-									new RawSource(source),
-									currentSourceMappingURLComment.replace(
-										/\[url\]/g,
-										sourceMapUrl
-									)
+						},
+						(err) => {
+							if (err) {
+								return callback(err);
+							}
+
+							reportProgress(0.5, "resolve sources");
+							/** @type {Set} */
+							const usedNamesSet = new Set(moduleToSourceNameMapping.values());
+							/** @type {Set} */
+							const conflictDetectionSet = new Set();
+
+							/**
+							 * all modules in defined order (longest identifier first)
+							 * @type {(string | Module)[]}
+							 */
+							const allModules = [...moduleToSourceNameMapping.keys()].sort(
+								(a, b) => {
+									const ai = typeof a === "string" ? a : a.identifier();
+									const bi = typeof b === "string" ? b : b.identifier();
+									return ai.length - bi.length;
+								}
+							);
+
+							// find modules with conflicting source names
+							for (let idx = 0; idx < allModules.length; idx++) {
+								const module = allModules[idx];
+								let sourceName =
+									/** @type {string} */
+									(moduleToSourceNameMapping.get(module));
+								let hasName = conflictDetectionSet.has(sourceName);
+								if (!hasName) {
+									conflictDetectionSet.add(sourceName);
+									continue;
+								}
+
+								// try the fallback name first
+								sourceName = ModuleFilenameHelpers.createFilename(
+									module,
+									{
+										moduleFilenameTemplate: fallbackModuleFilenameTemplate,
+										namespace
+									},
+									{
+										requestShortener,
+										chunkGraph,
+										hashFunction: compilation.outputOptions.hashFunction
+									}
 								);
+								hasName = usedNamesSet.has(sourceName);
+								if (!hasName) {
+									moduleToSourceNameMapping.set(module, sourceName);
+									usedNamesSet.add(sourceName);
+									continue;
+								}
+
+								// otherwise just append stars until we have a valid name
+								while (hasName) {
+									sourceName += "*";
+									hasName = usedNamesSet.has(sourceName);
+								}
+								moduleToSourceNameMapping.set(module, sourceName);
+								usedNamesSet.add(sourceName);
 							}
-							assets[sourceMapFile] = compilation.assets[
-								sourceMapFile
-							] = new RawSource(sourceMapString);
-							chunk.files.push(sourceMapFile);
-						} else {
-							assets[file] = compilation.assets[file] = new ConcatSource(
-								new RawSource(source),
-								currentSourceMappingURLComment
-									.replace(/\[map\]/g, () => sourceMapString)
-									.replace(
-										/\[url\]/g,
-										() =>
-											`data:application/json;charset=utf-8;base64,${Buffer.from(
-												sourceMapString,
-												"utf-8"
-											).toString("base64")}`
-									)
+
+							let taskIndex = 0;
+
+							asyncLib.each(
+								tasks,
+								(task, callback) => {
+									/** @type {Record} */
+									const assets = Object.create(null);
+									/** @type {Record} */
+									const assetsInfo = Object.create(null);
+									const file = task.file;
+									const chunk = fileToChunk.get(file);
+									const sourceMap = task.sourceMap;
+									const source = task.source;
+									const modules = task.modules;
+
+									reportProgress(
+										0.5 + (0.5 * taskIndex) / tasks.length,
+										file,
+										"attach SourceMap"
+									);
+
+									const moduleFilenames =
+										/** @type {string[]} */
+										(modules.map((m) => moduleToSourceNameMapping.get(m)));
+									// We deliberately do NOT mutate `sourceMap` in place: the
+									// task's `sourceMap` reference may be shared with a
+									// `SourceMapSource` whose internal map cache is the same
+									// object (webpack-sources keeps it cached). A second
+									// `SourceMapDevToolPlugin` instance that reads the original
+									// source through the registry would otherwise see our
+									// rewrites. Instead we build a fresh `outputSourceMap` for
+									// the .map file and leave the original alone.
+									/** @type {number[] | undefined} */
+									let ignoreList;
+									if (options.ignoreList) {
+										const list = moduleFilenames.reduce(
+											/** @type {(acc: number[], sourceName: string, idx: number) => number[]} */ (
+												(acc, sourceName, idx) => {
+													const rule = /** @type {Rules} */ (
+														options.ignoreList
+													);
+													if (
+														ModuleFilenameHelpers.matchPart(sourceName, rule)
+													) {
+														acc.push(idx);
+													}
+													return acc;
+												}
+											),
+											[]
+										);
+										if (list.length > 0) ignoreList = list;
+									}
+
+									const usesContentHash =
+										typeof sourceMapFilename === "string" &&
+										getPresentKinds(sourceMapFilename).has("contenthash");
+
+									let outputFile = file;
+									// If SourceMap and asset uses contenthash, avoid a circular dependency by hiding hash in `file`
+									if (usesContentHash && task.assetInfo.contenthash) {
+										const contenthash = task.assetInfo.contenthash;
+										const pattern = Array.isArray(contenthash)
+											? contenthash.map(quoteMeta).join("|")
+											: quoteMeta(contenthash);
+										outputFile = outputFile.replace(
+											new RegExp(pattern, "g"),
+											(m) => "x".repeat(m.length)
+										);
+									}
+
+									/** @type {false | SourceMappingURLComment} */
+									let currentSourceMappingURLComment = sourceMappingURLComment;
+									const cssExtensionDetected =
+										CSS_EXTENSION_DETECT_REGEXP.test(file);
+									resetRegexpState(CSS_EXTENSION_DETECT_REGEXP);
+									if (
+										currentSourceMappingURLComment !== false &&
+										typeof currentSourceMappingURLComment !== "function" &&
+										cssExtensionDetected
+									) {
+										currentSourceMappingURLComment =
+											currentSourceMappingURLComment.replace(
+												URL_FORMATTING_REGEXP,
+												"\n/*$1*/"
+											);
+									}
+
+									/** @type {string | undefined} */
+									let debugIdValue;
+									if (options.debugIds) {
+										const debugId = generateDebugId(source, outputFile);
+										debugIdValue = debugId;
+
+										const debugIdComment = `\n//# debugId=${debugId}`;
+										if (currentSourceMappingURLComment === false) {
+											currentSourceMappingURLComment = debugIdComment;
+										} else if (
+											typeof currentSourceMappingURLComment === "function"
+										) {
+											// Wrap the user's append function so the debug-id
+											// comment is prepended at call time. Template-string
+											// concatenation would coerce the function to a string
+											// and lose its dynamic behavior.
+											const wrappedFn = currentSourceMappingURLComment;
+											currentSourceMappingURLComment = (pathData, assetInfo) =>
+												`${debugIdComment}${wrappedFn(pathData, assetInfo)}`;
+										} else {
+											currentSourceMappingURLComment = `${debugIdComment}${currentSourceMappingURLComment}`;
+										}
+									}
+
+									/** @type {RawSourceMap} */
+									const outputSourceMap = {
+										...sourceMap,
+										sources: moduleFilenames,
+										sourceRoot: options.sourceRoot || "",
+										file: outputFile
+									};
+									if (ignoreList !== undefined) {
+										outputSourceMap.ignoreList = ignoreList;
+									}
+									if (options.noSources) {
+										outputSourceMap.sourcesContent = undefined;
+									}
+									if (debugIdValue !== undefined) {
+										outputSourceMap.debugId = debugIdValue;
+									}
+
+									if (sourceMapFilename) {
+										// External `.map` file: hold the serialized map as a
+										// `Buffer` instead of a V8 string. `RawSource` accepts
+										// a buffer directly, and the emitted asset stays in
+										// `compilation.assets` until the build finishes — so
+										// storing the bytes off the V8 heap (where Buffers
+										// live, accounted as `external` memory) avoids keeping
+										// a large V8 string alive for the rest of the build
+										// and reduces heap pressure on `--max-old-space-size`.
+										const sourceMapBuffer = Buffer.from(
+											JSON.stringify(outputSourceMap),
+											"utf8"
+										);
+										const filename = file;
+										const sourceMapContentHash = usesContentHash
+											? createHash(compilation.outputOptions.hashFunction)
+													.update(sourceMapBuffer)
+													.digest("hex")
+											: undefined;
+
+										const pathParams = {
+											chunk,
+											filename: options.fileContext
+												? relative(
+														outputFs,
+														`/${options.fileContext}`,
+														`/${filename}`
+													)
+												: filename,
+											contentHash: sourceMapContentHash
+										};
+										const { path: sourceMapFile, info: sourceMapInfo } =
+											compilation.getPathWithInfo(
+												sourceMapFilename,
+												pathParams
+											);
+										const sourceMapUrl = options.publicPath
+											? options.publicPath + sourceMapFile
+											: relative(
+													outputFs,
+													dirname(outputFs, `/${file}`),
+													`/${sourceMapFile}`
+												);
+										/** @type {Source} */
+										let asset = new RawSource(source);
+										if (currentSourceMappingURLComment !== false) {
+											// Add source map url to compilation asset, if currentSourceMappingURLComment is set
+											asset = new ConcatSource(
+												asset,
+												compilation.getPath(currentSourceMappingURLComment, {
+													url: sourceMapUrl,
+													...pathParams
+												})
+											);
+										}
+										// Preserve any existing related.sourceMap entries from
+										// earlier SourceMapDevToolPlugin runs on the same asset so
+										// that all generated maps remain discoverable via asset
+										// info (the schema allows string or string[]).
+										const existingSourceMap =
+											task.assetInfo.related &&
+											task.assetInfo.related.sourceMap;
+										/** @type {string | string[]} */
+										let relatedSourceMap;
+										if (
+											existingSourceMap === undefined ||
+											existingSourceMap === null
+										) {
+											relatedSourceMap = sourceMapFile;
+										} else if (Array.isArray(existingSourceMap)) {
+											relatedSourceMap = existingSourceMap.includes(
+												sourceMapFile
+											)
+												? existingSourceMap
+												: [...existingSourceMap, sourceMapFile];
+										} else {
+											relatedSourceMap =
+												existingSourceMap === sourceMapFile
+													? existingSourceMap
+													: [existingSourceMap, sourceMapFile];
+										}
+										const assetInfo = {
+											related: { sourceMap: relatedSourceMap }
+										};
+										assets[file] = asset;
+										assetsInfo[file] = assetInfo;
+										compilation.updateAsset(file, asset, assetInfo);
+										// Add source map file to compilation assets and chunk files
+										const sourceMapAsset = new RawSource(sourceMapBuffer);
+										const sourceMapAssetInfo = {
+											...sourceMapInfo,
+											development: true
+										};
+										assets[sourceMapFile] = sourceMapAsset;
+										assetsInfo[sourceMapFile] = sourceMapAssetInfo;
+										compilation.emitAsset(
+											sourceMapFile,
+											sourceMapAsset,
+											sourceMapAssetInfo
+										);
+										if (chunk !== undefined) {
+											chunk.auxiliaryFiles.add(sourceMapFile);
+										}
+									} else {
+										if (currentSourceMappingURLComment === false) {
+											throw new Error(
+												`${PLUGIN_NAME}: append can't be false when no filename is provided`
+											);
+										}
+										if (typeof currentSourceMappingURLComment === "function") {
+											throw new Error(
+												`${PLUGIN_NAME}: append can't be a function when no filename is provided`
+											);
+										}
+										// Inline data-URL form: `[map]` gets the raw JSON, `[url]`
+										// gets the same JSON base64-encoded. `URL_COMMENT_REGEXP`
+										// is a `/g` regex, so a user `append` template with more
+										// than one `[url]` placeholder would otherwise re-encode
+										// the same JSON per match. Pre-compute both once.
+										const sourceMapString = JSON.stringify(outputSourceMap);
+										const sourceMapBase64 = Buffer.from(
+											sourceMapString,
+											"utf8"
+										).toString("base64");
+										/**
+										 * Add source map as data url to asset
+										 */
+										const asset = new ConcatSource(
+											new RawSource(source),
+											currentSourceMappingURLComment
+												.replace(MAP_URL_COMMENT_REGEXP, () => sourceMapString)
+												.replace(
+													URL_COMMENT_REGEXP,
+													() =>
+														`data:application/json;charset=utf-8;base64,${sourceMapBase64}`
+												)
+										);
+										assets[file] = asset;
+										assetsInfo[file] = undefined;
+										compilation.updateAsset(file, asset);
+									}
+
+									task.cacheItem.store({ assets, assetsInfo }, (err) => {
+										reportProgress(
+											0.5 + (0.5 * ++taskIndex) / tasks.length,
+											task.file,
+											"attached SourceMap"
+										);
+
+										if (err) {
+											return callback(err);
+										}
+										callback();
+									});
+								},
+								(err) => {
+									reportProgress(1);
+									callback(err);
+								}
 							);
 						}
-					});
-					reportProgress(1.0);
+					);
 				}
 			);
 		});
diff --git a/lib/Stats.js b/lib/Stats.js
index 5179c8108a0..c41a25002d7 100644
--- a/lib/Stats.js
+++ b/lib/Stats.js
@@ -2,1413 +2,93 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
-"use strict";
 
-const RequestShortener = require("./RequestShortener");
-const SizeFormatHelpers = require("./SizeFormatHelpers");
-const formatLocation = require("./formatLocation");
-const identifierUtils = require("./util/identifier");
+"use strict";
 
-const optionsOrFallback = (...args) => {
-	let optionValues = [];
-	optionValues.push(...args);
-	return optionValues.find(optionValue => typeof optionValue !== "undefined");
-};
+/** @typedef {import("../declarations/WebpackOptions").StatsOptions} StatsOptions */
+/** @typedef {import("../declarations/WebpackOptions").StatsValue} StatsValue */
+/** @typedef {import("./Compilation")} Compilation */
+/** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsCompilation} StatsCompilation */
 
 class Stats {
+	/**
+	 * Creates an instance of Stats.
+	 * @param {Compilation} compilation webpack compilation
+	 */
 	constructor(compilation) {
+		/** @type {Compilation} */
 		this.compilation = compilation;
-		this.hash = compilation.hash;
-		this.startTime = undefined;
-		this.endTime = undefined;
 	}
 
-	static filterWarnings(warnings, warningsFilter) {
-		// we dont have anything to filter so all warnings can be shown
-		if (!warningsFilter) {
-			return warnings;
-		}
-
-		// create a chain of filters
-		// if they return "true" a warning should be suppressed
-		const normalizedWarningsFilters = [].concat(warningsFilter).map(filter => {
-			if (typeof filter === "string") {
-				return warning => warning.includes(filter);
-			}
-
-			if (filter instanceof RegExp) {
-				return warning => filter.test(warning);
-			}
-
-			if (typeof filter === "function") {
-				return filter;
-			}
+	get hash() {
+		return this.compilation.hash;
+	}
 
-			throw new Error(
-				`Can only filter warnings with Strings or RegExps. (Given: ${filter})`
-			);
-		});
-		return warnings.filter(warning => {
-			return !normalizedWarningsFilters.some(check => check(warning));
-		});
+	get startTime() {
+		return this.compilation.startTime;
 	}
 
-	formatFilePath(filePath) {
-		const OPTIONS_REGEXP = /^(\s|\S)*!/;
-		return filePath.includes("!")
-			? `${filePath.replace(OPTIONS_REGEXP, "")} (${filePath})`
-			: `${filePath}`;
+	get endTime() {
+		return this.compilation.endTime;
 	}
 
+	/**
+	 * Checks whether this stats has warnings.
+	 * @returns {boolean} true if the compilation had a warning
+	 */
 	hasWarnings() {
 		return (
-			this.compilation.warnings.length > 0 ||
-			this.compilation.children.some(child => child.getStats().hasWarnings())
+			this.compilation.getWarnings().length > 0 ||
+			this.compilation.children.some((child) => child.getStats().hasWarnings())
 		);
 	}
 
+	/**
+	 * Checks whether this stats has errors.
+	 * @returns {boolean} true if the compilation encountered an error
+	 */
 	hasErrors() {
 		return (
 			this.compilation.errors.length > 0 ||
-			this.compilation.children.some(child => child.getStats().hasErrors())
+			this.compilation.children.some((child) => child.getStats().hasErrors())
 		);
 	}
 
-	// remove a prefixed "!" that can be specified to reverse sort order
-	normalizeFieldKey(field) {
-		if (field[0] === "!") {
-			return field.substr(1);
-		}
-		return field;
-	}
-
-	// if a field is prefixed by a "!" reverse sort order
-	sortOrderRegular(field) {
-		if (field[0] === "!") {
-			return false;
-		}
-		return true;
-	}
-
-	toJson(options, forToString) {
-		if (typeof options === "boolean" || typeof options === "string") {
-			options = Stats.presetToOptions(options);
-		} else if (!options) {
-			options = {};
-		}
-
-		const optionOrLocalFallback = (v, def) =>
-			typeof v !== "undefined"
-				? v
-				: typeof options.all !== "undefined"
-					? options.all
-					: def;
-
-		const testAgainstGivenOption = item => {
-			if (typeof item === "string") {
-				const regExp = new RegExp(
-					`[\\\\/]${item.replace(
-						/[-[\]{}()*+?.\\^$|]/g,
-						"\\$&"
-					)}([\\\\/]|$|!|\\?)`
-				); // eslint-disable-line no-useless-escape
-				return ident => regExp.test(ident);
-			}
-			if (item && typeof item === "object" && typeof item.test === "function") {
-				return ident => item.test(ident);
-			}
-			if (typeof item === "function") {
-				return item;
-			}
-			if (typeof item === "boolean") {
-				return () => item;
-			}
-		};
-
-		const compilation = this.compilation;
-		const context = optionsOrFallback(
-			options.context,
-			compilation.compiler.context
-		);
-		const requestShortener =
-			compilation.compiler.context === context
-				? compilation.requestShortener
-				: new RequestShortener(context);
-		const showPerformance = optionOrLocalFallback(options.performance, true);
-		const showHash = optionOrLocalFallback(options.hash, true);
-		const showEnv = optionOrLocalFallback(options.env, false);
-		const showVersion = optionOrLocalFallback(options.version, true);
-		const showTimings = optionOrLocalFallback(options.timings, true);
-		const showBuiltAt = optionOrLocalFallback(options.builtAt, true);
-		const showAssets = optionOrLocalFallback(options.assets, true);
-		const showEntrypoints = optionOrLocalFallback(options.entrypoints, true);
-		const showChunkGroups = optionOrLocalFallback(
-			options.chunkGroups,
-			!forToString
-		);
-		const showChunks = optionOrLocalFallback(options.chunks, !forToString);
-		const showChunkModules = optionOrLocalFallback(options.chunkModules, true);
-		const showChunkOrigins = optionOrLocalFallback(
-			options.chunkOrigins,
-			!forToString
-		);
-		const showModules = optionOrLocalFallback(options.modules, true);
-		const showNestedModules = optionOrLocalFallback(
-			options.nestedModules,
-			true
-		);
-		const showModuleAssets = optionOrLocalFallback(
-			options.moduleAssets,
-			!forToString
-		);
-		const showDepth = optionOrLocalFallback(options.depth, !forToString);
-		const showCachedModules = optionOrLocalFallback(options.cached, true);
-		const showCachedAssets = optionOrLocalFallback(options.cachedAssets, true);
-		const showReasons = optionOrLocalFallback(options.reasons, !forToString);
-		const showUsedExports = optionOrLocalFallback(
-			options.usedExports,
-			!forToString
-		);
-		const showProvidedExports = optionOrLocalFallback(
-			options.providedExports,
-			!forToString
-		);
-		const showOptimizationBailout = optionOrLocalFallback(
-			options.optimizationBailout,
-			!forToString
-		);
-		const showChildren = optionOrLocalFallback(options.children, true);
-		const showSource = optionOrLocalFallback(options.source, !forToString);
-		const showModuleTrace = optionOrLocalFallback(options.moduleTrace, true);
-		const showErrors = optionOrLocalFallback(options.errors, true);
-		const showErrorDetails = optionOrLocalFallback(
-			options.errorDetails,
-			!forToString
-		);
-		const showWarnings = optionOrLocalFallback(options.warnings, true);
-		const warningsFilter = optionsOrFallback(options.warningsFilter, null);
-		const showPublicPath = optionOrLocalFallback(
-			options.publicPath,
-			!forToString
-		);
-		const excludeModules = []
-			.concat(optionsOrFallback(options.excludeModules, options.exclude, []))
-			.map(testAgainstGivenOption);
-		const excludeAssets = []
-			.concat(optionsOrFallback(options.excludeAssets, []))
-			.map(testAgainstGivenOption);
-		const maxModules = optionsOrFallback(
-			options.maxModules,
-			forToString ? 15 : Infinity
-		);
-		const sortModules = optionsOrFallback(options.modulesSort, "id");
-		const sortChunks = optionsOrFallback(options.chunksSort, "id");
-		const sortAssets = optionsOrFallback(options.assetsSort, "");
-		const showOutputPath = optionOrLocalFallback(
-			options.outputPath,
-			!forToString
-		);
-
-		if (!showCachedModules) {
-			excludeModules.push((ident, module) => !module.built);
-		}
-
-		const createModuleFilter = () => {
-			let i = 0;
-			return module => {
-				if (excludeModules.length > 0) {
-					const ident = requestShortener.shorten(module.resource);
-					const excluded = excludeModules.some(fn => fn(ident, module));
-					if (excluded) return false;
-				}
-				const result = i < maxModules;
-				i++;
-				return result;
-			};
-		};
-
-		const createAssetFilter = () => {
-			return asset => {
-				if (excludeAssets.length > 0) {
-					const ident = asset.name;
-					const excluded = excludeAssets.some(fn => fn(ident, asset));
-					if (excluded) return false;
-				}
-				return showCachedAssets || asset.emitted;
-			};
-		};
-
-		const sortByFieldAndOrder = (fieldKey, a, b) => {
-			if (a[fieldKey] === null && b[fieldKey] === null) return 0;
-			if (a[fieldKey] === null) return 1;
-			if (b[fieldKey] === null) return -1;
-			if (a[fieldKey] === b[fieldKey]) return 0;
-			return a[fieldKey] < b[fieldKey] ? -1 : 1;
-		};
-
-		const sortByField = field => (a, b) => {
-			if (!field) {
-				return 0;
-			}
-
-			const fieldKey = this.normalizeFieldKey(field);
-
-			// if a field is prefixed with a "!" the sort is reversed!
-			const sortIsRegular = this.sortOrderRegular(field);
-
-			return sortByFieldAndOrder(
-				fieldKey,
-				sortIsRegular ? a : b,
-				sortIsRegular ? b : a
-			);
-		};
-
-		const formatError = e => {
-			let text = "";
-			if (typeof e === "string") {
-				e = { message: e };
-			}
-			if (e.chunk) {
-				text += `chunk ${e.chunk.name || e.chunk.id}${
-					e.chunk.hasRuntime()
-						? " [entry]"
-						: e.chunk.canBeInitial()
-							? " [initial]"
-							: ""
-				}\n`;
-			}
-			if (e.file) {
-				text += `${e.file}\n`;
-			}
-			if (
-				e.module &&
-				e.module.readableIdentifier &&
-				typeof e.module.readableIdentifier === "function"
-			) {
-				text += this.formatFilePath(
-					e.module.readableIdentifier(requestShortener)
-				);
-				if (typeof e.loc === "object") {
-					const locInfo = formatLocation(e.loc);
-					if (locInfo) text += ` ${locInfo}`;
-				}
-				text += "\n";
-			}
-			text += e.message;
-			if (showErrorDetails && e.details) {
-				text += `\n${e.details}`;
-			}
-			if (showErrorDetails && e.missing) {
-				text += e.missing.map(item => `\n[${item}]`).join("");
-			}
-			if (showModuleTrace && e.origin) {
-				text += `\n @ ${this.formatFilePath(
-					e.origin.readableIdentifier(requestShortener)
-				)}`;
-				if (typeof e.originLoc === "object") {
-					const locInfo = formatLocation(e.originLoc);
-					if (locInfo) text += ` ${locInfo}`;
-				}
-				if (e.dependencies) {
-					for (const dep of e.dependencies) {
-						if (!dep.loc) continue;
-						if (typeof dep.loc === "string") continue;
-						const locInfo = formatLocation(dep.loc);
-						if (!locInfo) continue;
-						text += ` ${locInfo}`;
-					}
-				}
-				let current = e.origin;
-				while (current.issuer) {
-					current = current.issuer;
-					text += `\n @ ${current.readableIdentifier(requestShortener)}`;
-				}
-			}
-			return text;
-		};
-
-		const obj = {
-			errors: compilation.errors.map(formatError),
-			warnings: Stats.filterWarnings(
-				compilation.warnings.map(formatError),
-				warningsFilter
-			)
-		};
-
-		//We just hint other renderers since actually omitting
-		//errors/warnings from the JSON would be kind of weird.
-		Object.defineProperty(obj, "_showWarnings", {
-			value: showWarnings,
-			enumerable: false
-		});
-		Object.defineProperty(obj, "_showErrors", {
-			value: showErrors,
-			enumerable: false
+	/**
+	 * Returns json output.
+	 * @param {StatsValue=} options stats options
+	 * @returns {StatsCompilation} json output
+	 */
+	toJson(options) {
+		const normalizedOptions = this.compilation.createStatsOptions(options, {
+			forToString: false
 		});
 
-		if (showVersion) {
-			obj.version = require("../package.json").version;
-		}
-
-		if (showHash) obj.hash = this.hash;
-		if (showTimings && this.startTime && this.endTime) {
-			obj.time = this.endTime - this.startTime;
-		}
-
-		if (showBuiltAt && this.endTime) {
-			obj.builtAt = this.endTime;
-		}
-
-		if (showEnv && options._env) {
-			obj.env = options._env;
-		}
+		const statsFactory = this.compilation.createStatsFactory(normalizedOptions);
 
-		if (compilation.needAdditionalPass) {
-			obj.needAdditionalPass = true;
-		}
-		if (showPublicPath) {
-			obj.publicPath = this.compilation.mainTemplate.getPublicPath({
-				hash: this.compilation.hash
-			});
-		}
-		if (showOutputPath) {
-			obj.outputPath = this.compilation.mainTemplate.outputOptions.path;
-		}
-		if (showAssets) {
-			const assetsByFile = {};
-			const compilationAssets = Object.keys(compilation.assets);
-			obj.assetsByChunkName = {};
-			obj.assets = compilationAssets
-				.map(asset => {
-					const obj = {
-						name: asset,
-						size: compilation.assets[asset].size(),
-						chunks: [],
-						chunkNames: [],
-						emitted: compilation.assets[asset].emitted
-					};
-
-					if (showPerformance) {
-						obj.isOverSizeLimit = compilation.assets[asset].isOverSizeLimit;
-					}
-
-					assetsByFile[asset] = obj;
-					return obj;
-				})
-				.filter(createAssetFilter());
-			obj.filteredAssets = compilationAssets.length - obj.assets.length;
-
-			for (const chunk of compilation.chunks) {
-				for (const asset of chunk.files) {
-					if (assetsByFile[asset]) {
-						for (const id of chunk.ids) {
-							assetsByFile[asset].chunks.push(id);
-						}
-						if (chunk.name) {
-							assetsByFile[asset].chunkNames.push(chunk.name);
-							if (obj.assetsByChunkName[chunk.name]) {
-								obj.assetsByChunkName[chunk.name] = []
-									.concat(obj.assetsByChunkName[chunk.name])
-									.concat([asset]);
-							} else {
-								obj.assetsByChunkName[chunk.name] = asset;
-							}
-						}
-					}
-				}
-			}
-			obj.assets.sort(sortByField(sortAssets));
-		}
-
-		const fnChunkGroup = groupMap => {
-			const obj = {};
-			for (const keyValuePair of groupMap) {
-				const name = keyValuePair[0];
-				const cg = keyValuePair[1];
-				const children = cg.getChildrenByOrders();
-				obj[name] = {
-					chunks: cg.chunks.map(c => c.id),
-					assets: cg.chunks.reduce(
-						(array, c) => array.concat(c.files || []),
-						[]
-					),
-					children: Object.keys(children).reduce((obj, key) => {
-						const groups = children[key];
-						obj[key] = groups.map(group => ({
-							name: group.name,
-							chunks: group.chunks.map(c => c.id),
-							assets: group.chunks.reduce(
-								(array, c) => array.concat(c.files || []),
-								[]
-							)
-						}));
-						return obj;
-					}, Object.create(null)),
-					childAssets: Object.keys(children).reduce((obj, key) => {
-						const groups = children[key];
-						obj[key] = Array.from(
-							groups.reduce((set, group) => {
-								for (const chunk of group.chunks) {
-									for (const asset of chunk.files) {
-										set.add(asset);
-									}
-								}
-								return set;
-							}, new Set())
-						);
-						return obj;
-					}, Object.create(null))
-				};
-				if (showPerformance) {
-					obj[name].isOverSizeLimit = cg.isOverSizeLimit;
-				}
-			}
-
-			return obj;
-		};
-
-		if (showEntrypoints) {
-			obj.entrypoints = fnChunkGroup(compilation.entrypoints);
-		}
-
-		if (showChunkGroups) {
-			obj.namedChunkGroups = fnChunkGroup(compilation.namedChunkGroups);
-		}
-
-		const fnModule = module => {
-			const path = [];
-			let current = module;
-			while (current.issuer) {
-				path.push((current = current.issuer));
-			}
-			path.reverse();
-			const obj = {
-				id: module.id,
-				identifier: module.identifier(),
-				name: module.readableIdentifier(requestShortener),
-				index: module.index,
-				index2: module.index2,
-				size: module.size(),
-				cacheable: module.buildInfo.cacheable,
-				built: !!module.built,
-				optional: module.optional,
-				prefetched: module.prefetched,
-				chunks: Array.from(module.chunksIterable, chunk => chunk.id),
-				issuer: module.issuer && module.issuer.identifier(),
-				issuerId: module.issuer && module.issuer.id,
-				issuerName:
-					module.issuer && module.issuer.readableIdentifier(requestShortener),
-				issuerPath:
-					module.issuer &&
-					path.map(module => ({
-						id: module.id,
-						identifier: module.identifier(),
-						name: module.readableIdentifier(requestShortener),
-						profile: module.profile
-					})),
-				profile: module.profile,
-				failed: !!module.error,
-				errors: module.errors ? module.errors.length : 0,
-				warnings: module.warnings ? module.warnings.length : 0
-			};
-			if (showModuleAssets) {
-				obj.assets = Object.keys(module.buildInfo.assets || {});
-			}
-			if (showReasons) {
-				obj.reasons = module.reasons
-					.map(reason => {
-						const obj = {
-							moduleId: reason.module ? reason.module.id : null,
-							moduleIdentifier: reason.module
-								? reason.module.identifier()
-								: null,
-							module: reason.module
-								? reason.module.readableIdentifier(requestShortener)
-								: null,
-							moduleName: reason.module
-								? reason.module.readableIdentifier(requestShortener)
-								: null,
-							type: reason.dependency ? reason.dependency.type : null,
-							explanation: reason.explanation,
-							userRequest: reason.dependency
-								? reason.dependency.userRequest
-								: null
-						};
-						if (reason.dependency) {
-							const locInfo = formatLocation(reason.dependency.loc);
-							if (locInfo) {
-								obj.loc = locInfo;
-							}
-						}
-						return obj;
-					})
-					.sort((a, b) => a.moduleId - b.moduleId);
-			}
-			if (showUsedExports) {
-				if (module.used === true) {
-					obj.usedExports = module.usedExports;
-				} else if (module.used === false) {
-					obj.usedExports = false;
-				}
-			}
-			if (showProvidedExports) {
-				obj.providedExports = Array.isArray(module.buildMeta.providedExports)
-					? module.buildMeta.providedExports
-					: null;
-			}
-			if (showOptimizationBailout) {
-				obj.optimizationBailout = module.optimizationBailout.map(item => {
-					if (typeof item === "function") return item(requestShortener);
-					return item;
-				});
-			}
-			if (showDepth) {
-				obj.depth = module.depth;
-			}
-			if (showNestedModules) {
-				if (module.modules) {
-					const modules = module.modules;
-					obj.modules = modules
-						.sort(sortByField("depth"))
-						.filter(createModuleFilter())
-						.map(fnModule);
-					obj.filteredModules = modules.length - obj.modules.length;
-					obj.modules.sort(sortByField(sortModules));
-				}
-			}
-			if (showSource && module._source) {
-				obj.source = module._source.source();
-			}
-			return obj;
-		};
-		if (showChunks) {
-			obj.chunks = compilation.chunks.map(chunk => {
-				const parents = new Set();
-				const children = new Set();
-				const siblings = new Set();
-				const childIdByOrder = chunk.getChildIdsByOrders();
-				for (const chunkGroup of chunk.groupsIterable) {
-					for (const parentGroup of chunkGroup.parentsIterable) {
-						for (const chunk of parentGroup.chunks) {
-							parents.add(chunk.id);
-						}
-					}
-					for (const childGroup of chunkGroup.childrenIterable) {
-						for (const chunk of childGroup.chunks) {
-							children.add(chunk.id);
-						}
-					}
-					for (const sibling of chunkGroup.chunks) {
-						if (sibling !== chunk) siblings.add(sibling.id);
-					}
-				}
-				const obj = {
-					id: chunk.id,
-					rendered: chunk.rendered,
-					initial: chunk.canBeInitial(),
-					entry: chunk.hasRuntime(),
-					recorded: chunk.recorded,
-					reason: chunk.chunkReason,
-					size: chunk.modulesSize(),
-					names: chunk.name ? [chunk.name] : [],
-					files: chunk.files.slice(),
-					hash: chunk.renderedHash,
-					siblings: Array.from(siblings).sort(),
-					parents: Array.from(parents).sort(),
-					children: Array.from(children).sort(),
-					childrenByOrder: childIdByOrder
-				};
-				if (showChunkModules) {
-					obj.modules = chunk
-						.getModules()
-						.sort(sortByField("depth"))
-						.filter(createModuleFilter())
-						.map(fnModule);
-					obj.filteredModules = chunk.getNumberOfModules() - obj.modules.length;
-					obj.modules.sort(sortByField(sortModules));
-				}
-				if (showChunkOrigins) {
-					obj.origins = Array.from(chunk.groupsIterable, g => g.origins)
-						.reduce((a, b) => a.concat(b), [])
-						.map(origin => ({
-							moduleId: origin.module ? origin.module.id : undefined,
-							module: origin.module ? origin.module.identifier() : "",
-							moduleIdentifier: origin.module ? origin.module.identifier() : "",
-							moduleName: origin.module
-								? origin.module.readableIdentifier(requestShortener)
-								: "",
-							loc: formatLocation(origin.loc),
-							request: origin.request,
-							reasons: origin.reasons || []
-						}))
-						.sort((a, b) => {
-							if (
-								typeof a.moduleId === "number" &&
-								typeof b.moduleId !== "number"
-							)
-								return 1;
-							if (
-								typeof a.moduleId !== "number" &&
-								typeof b.moduleId === "number"
-							)
-								return -1;
-							if (
-								typeof a.moduleId === "number" &&
-								typeof b.moduleId === "number"
-							) {
-								const diffId = a.moduleId - b.moduleId;
-								if (diffId !== 0) return diffId;
-							}
-							if (a.loc < b.loc) return -1;
-							if (a.loc > b.loc) return 1;
-							return 0;
-						});
-				}
-				return obj;
-			});
-			obj.chunks.sort(sortByField(sortChunks));
-		}
-		if (showModules) {
-			obj.modules = compilation.modules
-				.slice()
-				.sort(sortByField("depth"))
-				.filter(createModuleFilter())
-				.map(fnModule);
-			obj.filteredModules = compilation.modules.length - obj.modules.length;
-			obj.modules.sort(sortByField(sortModules));
-		}
-		if (showChildren) {
-			obj.children = compilation.children.map((child, idx) => {
-				const childOptions = Stats.getChildOptions(options, idx);
-				const obj = new Stats(child).toJson(childOptions, forToString);
-				delete obj.hash;
-				delete obj.version;
-				if (child.name) {
-					obj.name = identifierUtils.makePathsRelative(
-						context,
-						child.name,
-						compilation.cache
-					);
-				}
-				return obj;
-			});
-		}
-
-		return obj;
+		return statsFactory.create("compilation", this.compilation, {
+			compilation: this.compilation
+		});
 	}
 
+	/**
+	 * Returns a string representation.
+	 * @param {StatsValue=} options stats options
+	 * @returns {string} string output
+	 */
 	toString(options) {
-		if (typeof options === "boolean" || typeof options === "string") {
-			options = Stats.presetToOptions(options);
-		} else if (!options) {
-			options = {};
-		}
-
-		const useColors = optionsOrFallback(options.colors, false);
-
-		const obj = this.toJson(options, true);
-
-		return Stats.jsonToString(obj, useColors);
-	}
-
-	static jsonToString(obj, useColors) {
-		const buf = [];
-
-		const defaultColors = {
-			bold: "\u001b[1m",
-			yellow: "\u001b[1m\u001b[33m",
-			red: "\u001b[1m\u001b[31m",
-			green: "\u001b[1m\u001b[32m",
-			cyan: "\u001b[1m\u001b[36m",
-			magenta: "\u001b[1m\u001b[35m"
-		};
-
-		const colors = Object.keys(defaultColors).reduce(
-			(obj, color) => {
-				obj[color] = str => {
-					if (useColors) {
-						buf.push(
-							useColors === true || useColors[color] === undefined
-								? defaultColors[color]
-								: useColors[color]
-						);
-					}
-					buf.push(str);
-					if (useColors) {
-						buf.push("\u001b[39m\u001b[22m");
-					}
-				};
-				return obj;
-			},
-			{
-				normal: str => buf.push(str)
-			}
-		);
-
-		const coloredTime = time => {
-			let times = [800, 400, 200, 100];
-			if (obj.time) {
-				times = [obj.time / 2, obj.time / 4, obj.time / 8, obj.time / 16];
-			}
-			if (time < times[3]) colors.normal(`${time}ms`);
-			else if (time < times[2]) colors.bold(`${time}ms`);
-			else if (time < times[1]) colors.green(`${time}ms`);
-			else if (time < times[0]) colors.yellow(`${time}ms`);
-			else colors.red(`${time}ms`);
-		};
-
-		const newline = () => buf.push("\n");
-
-		const getText = (arr, row, col) => {
-			return arr[row][col].value;
-		};
-
-		const table = (array, align, splitter) => {
-			const rows = array.length;
-			const cols = array[0].length;
-			const colSizes = new Array(cols);
-			for (let col = 0; col < cols; col++) {
-				colSizes[col] = 0;
-			}
-			for (let row = 0; row < rows; row++) {
-				for (let col = 0; col < cols; col++) {
-					const value = `${getText(array, row, col)}`;
-					if (value.length > colSizes[col]) {
-						colSizes[col] = value.length;
-					}
-				}
-			}
-			for (let row = 0; row < rows; row++) {
-				for (let col = 0; col < cols; col++) {
-					const format = array[row][col].color;
-					const value = `${getText(array, row, col)}`;
-					let l = value.length;
-					if (align[col] === "l") {
-						format(value);
-					}
-					for (; l < colSizes[col] && col !== cols - 1; l++) {
-						colors.normal(" ");
-					}
-					if (align[col] === "r") {
-						format(value);
-					}
-					if (col + 1 < cols && colSizes[col] !== 0) {
-						colors.normal(splitter || "  ");
-					}
-				}
-				newline();
-			}
-		};
-
-		const getAssetColor = (asset, defaultColor) => {
-			if (asset.isOverSizeLimit) {
-				return colors.yellow;
-			}
-
-			return defaultColor;
-		};
-
-		if (obj.hash) {
-			colors.normal("Hash: ");
-			colors.bold(obj.hash);
-			newline();
-		}
-		if (obj.version) {
-			colors.normal("Version: webpack ");
-			colors.bold(obj.version);
-			newline();
-		}
-		if (typeof obj.time === "number") {
-			colors.normal("Time: ");
-			colors.bold(obj.time);
-			colors.normal("ms");
-			newline();
-		}
-		if (typeof obj.builtAt === "number") {
-			const builtAtDate = new Date(obj.builtAt);
-			colors.normal("Built at: ");
-			colors.normal(
-				builtAtDate.toLocaleDateString(undefined, {
-					day: "2-digit",
-					month: "2-digit",
-					year: "numeric"
-				})
-			);
-			colors.normal(" ");
-			colors.bold(builtAtDate.toLocaleTimeString());
-			newline();
-		}
-		if (obj.env) {
-			colors.normal("Environment (--env): ");
-			colors.bold(JSON.stringify(obj.env, null, 2));
-			newline();
-		}
-		if (obj.publicPath) {
-			colors.normal("PublicPath: ");
-			colors.bold(obj.publicPath);
-			newline();
-		}
-
-		if (obj.assets && obj.assets.length > 0) {
-			const t = [
-				[
-					{
-						value: "Asset",
-						color: colors.bold
-					},
-					{
-						value: "Size",
-						color: colors.bold
-					},
-					{
-						value: "Chunks",
-						color: colors.bold
-					},
-					{
-						value: "",
-						color: colors.bold
-					},
-					{
-						value: "",
-						color: colors.bold
-					},
-					{
-						value: "Chunk Names",
-						color: colors.bold
-					}
-				]
-			];
-			for (const asset of obj.assets) {
-				t.push([
-					{
-						value: asset.name,
-						color: getAssetColor(asset, colors.green)
-					},
-					{
-						value: SizeFormatHelpers.formatSize(asset.size),
-						color: getAssetColor(asset, colors.normal)
-					},
-					{
-						value: asset.chunks.join(", "),
-						color: colors.bold
-					},
-					{
-						value: asset.emitted ? "[emitted]" : "",
-						color: colors.green
-					},
-					{
-						value: asset.isOverSizeLimit ? "[big]" : "",
-						color: getAssetColor(asset, colors.normal)
-					},
-					{
-						value: asset.chunkNames.join(", "),
-						color: colors.normal
-					}
-				]);
-			}
-			table(t, "rrrlll");
-		}
-		if (obj.filteredAssets > 0) {
-			colors.normal(" ");
-			if (obj.assets.length > 0) colors.normal("+ ");
-			colors.normal(obj.filteredAssets);
-			if (obj.assets.length > 0) colors.normal(" hidden");
-			colors.normal(obj.filteredAssets !== 1 ? " assets" : " asset");
-			newline();
-		}
-
-		const processChunkGroups = (namedGroups, prefix) => {
-			for (const name of Object.keys(namedGroups)) {
-				const cg = namedGroups[name];
-				colors.normal(`${prefix} `);
-				colors.bold(name);
-				if (cg.isOverSizeLimit) {
-					colors.normal(" ");
-					colors.yellow("[big]");
-				}
-				colors.normal(" =");
-				for (const asset of cg.assets) {
-					colors.normal(" ");
-					colors.green(asset);
-				}
-				for (const name of Object.keys(cg.childAssets)) {
-					const assets = cg.childAssets[name];
-					if (assets && assets.length > 0) {
-						colors.normal(" ");
-						colors.magenta(`(${name}:`);
-						for (const asset of assets) {
-							colors.normal(" ");
-							colors.green(asset);
-						}
-						colors.magenta(")");
-					}
-				}
-				newline();
-			}
-		};
-
-		if (obj.entrypoints) {
-			processChunkGroups(obj.entrypoints, "Entrypoint");
-		}
-
-		if (obj.namedChunkGroups) {
-			let outputChunkGroups = obj.namedChunkGroups;
-			if (obj.entrypoints) {
-				outputChunkGroups = Object.keys(outputChunkGroups)
-					.filter(name => !obj.entrypoints[name])
-					.reduce((result, name) => {
-						result[name] = obj.namedChunkGroups[name];
-						return result;
-					}, {});
-			}
-			processChunkGroups(outputChunkGroups, "Chunk Group");
-		}
-
-		const modulesByIdentifier = {};
-		if (obj.modules) {
-			for (const module of obj.modules) {
-				modulesByIdentifier[`$${module.identifier}`] = module;
-			}
-		} else if (obj.chunks) {
-			for (const chunk of obj.chunks) {
-				if (chunk.modules) {
-					for (const module of chunk.modules) {
-						modulesByIdentifier[`$${module.identifier}`] = module;
-					}
-				}
-			}
-		}
-
-		const processModuleAttributes = module => {
-			colors.normal(" ");
-			colors.normal(SizeFormatHelpers.formatSize(module.size));
-			if (module.chunks) {
-				for (const chunk of module.chunks) {
-					colors.normal(" {");
-					colors.yellow(chunk);
-					colors.normal("}");
-				}
-			}
-			if (typeof module.depth === "number") {
-				colors.normal(` [depth ${module.depth}]`);
-			}
-			if (module.cacheable === false) {
-				colors.red(" [not cacheable]");
-			}
-			if (module.optional) {
-				colors.yellow(" [optional]");
-			}
-			if (module.built) {
-				colors.green(" [built]");
-			}
-			if (module.assets && module.assets.length) {
-				colors.magenta(
-					` [${module.assets.length} asset${
-						module.assets.length === 1 ? "" : "s"
-					}]`
-				);
-			}
-			if (module.prefetched) {
-				colors.magenta(" [prefetched]");
-			}
-			if (module.failed) colors.red(" [failed]");
-			if (module.warnings) {
-				colors.yellow(
-					` [${module.warnings} warning${module.warnings === 1 ? "" : "s"}]`
-				);
-			}
-			if (module.errors) {
-				colors.red(
-					` [${module.errors} error${module.errors === 1 ? "" : "s"}]`
-				);
-			}
-		};
-
-		const processModuleContent = (module, prefix) => {
-			if (Array.isArray(module.providedExports)) {
-				colors.normal(prefix);
-				if (module.providedExports.length === 0) {
-					colors.cyan("[no exports]");
-				} else {
-					colors.cyan(`[exports: ${module.providedExports.join(", ")}]`);
-				}
-				newline();
-			}
-			if (module.usedExports !== undefined) {
-				if (module.usedExports !== true) {
-					colors.normal(prefix);
-					if (module.usedExports === null) {
-						colors.cyan("[used exports unknown]");
-					} else if (module.usedExports === false) {
-						colors.cyan("[no exports used]");
-					} else if (
-						Array.isArray(module.usedExports) &&
-						module.usedExports.length === 0
-					) {
-						colors.cyan("[no exports used]");
-					} else if (Array.isArray(module.usedExports)) {
-						const providedExportsCount = Array.isArray(module.providedExports)
-							? module.providedExports.length
-							: null;
-						if (
-							providedExportsCount !== null &&
-							providedExportsCount === module.usedExports.length
-						) {
-							colors.cyan("[all exports used]");
-						} else {
-							colors.cyan(
-								`[only some exports used: ${module.usedExports.join(", ")}]`
-							);
-						}
-					}
-					newline();
-				}
-			}
-			if (Array.isArray(module.optimizationBailout)) {
-				for (const item of module.optimizationBailout) {
-					colors.normal(prefix);
-					colors.yellow(item);
-					newline();
-				}
-			}
-			if (module.reasons) {
-				for (const reason of module.reasons) {
-					colors.normal(prefix);
-					if (reason.type) {
-						colors.normal(reason.type);
-						colors.normal(" ");
-					}
-					if (reason.userRequest) {
-						colors.cyan(reason.userRequest);
-						colors.normal(" ");
-					}
-					if (reason.moduleId !== null) {
-						colors.normal("[");
-						colors.normal(reason.moduleId);
-						colors.normal("]");
-					}
-					if (reason.module && reason.module !== reason.moduleId) {
-						colors.normal(" ");
-						colors.magenta(reason.module);
-					}
-					if (reason.loc) {
-						colors.normal(" ");
-						colors.normal(reason.loc);
-					}
-					if (reason.explanation) {
-						colors.normal(" ");
-						colors.cyan(reason.explanation);
-					}
-					newline();
-				}
-			}
-			if (module.profile) {
-				colors.normal(prefix);
-				let sum = 0;
-				if (module.issuerPath) {
-					for (const m of module.issuerPath) {
-						colors.normal("[");
-						colors.normal(m.id);
-						colors.normal("] ");
-						if (m.profile) {
-							const time = (m.profile.factory || 0) + (m.profile.building || 0);
-							coloredTime(time);
-							sum += time;
-							colors.normal(" ");
-						}
-						colors.normal("-> ");
-					}
-				}
-				for (const key of Object.keys(module.profile)) {
-					colors.normal(`${key}:`);
-					const time = module.profile[key];
-					coloredTime(time);
-					colors.normal(" ");
-					sum += time;
-				}
-				colors.normal("= ");
-				coloredTime(sum);
-				newline();
-			}
-			if (module.modules) {
-				processModulesList(module, prefix + "| ");
-			}
-		};
-
-		const processModulesList = (obj, prefix) => {
-			if (obj.modules) {
-				let maxModuleId = 0;
-				for (const module of obj.modules) {
-					if (typeof module.id === "number") {
-						if (maxModuleId < module.id) maxModuleId = module.id;
-					}
-				}
-				let contentPrefix = prefix + "    ";
-				if (maxModuleId >= 10) contentPrefix += " ";
-				if (maxModuleId >= 100) contentPrefix += " ";
-				if (maxModuleId >= 1000) contentPrefix += " ";
-				for (const module of obj.modules) {
-					colors.normal(prefix);
-					const name = module.name || module.identifier;
-					if (typeof module.id === "string" || typeof module.id === "number") {
-						if (typeof module.id === "number") {
-							if (module.id < 1000 && maxModuleId >= 1000) colors.normal(" ");
-							if (module.id < 100 && maxModuleId >= 100) colors.normal(" ");
-							if (module.id < 10 && maxModuleId >= 10) colors.normal(" ");
-						} else {
-							if (maxModuleId >= 1000) colors.normal(" ");
-							if (maxModuleId >= 100) colors.normal(" ");
-							if (maxModuleId >= 10) colors.normal(" ");
-						}
-						if (name !== module.id) {
-							colors.normal("[");
-							colors.normal(module.id);
-							colors.normal("]");
-							colors.normal(" ");
-						} else {
-							colors.normal("[");
-							colors.bold(module.id);
-							colors.normal("]");
-						}
-					}
-					if (name !== module.id) {
-						colors.bold(name);
-					}
-					processModuleAttributes(module);
-					newline();
-					processModuleContent(module, contentPrefix);
-				}
-				if (obj.filteredModules > 0) {
-					colors.normal(prefix);
-					colors.normal("   ");
-					if (obj.modules.length > 0) colors.normal(" + ");
-					colors.normal(obj.filteredModules);
-					if (obj.modules.length > 0) colors.normal(" hidden");
-					colors.normal(obj.filteredModules !== 1 ? " modules" : " module");
-					newline();
-				}
-			}
-		};
-
-		if (obj.chunks) {
-			for (const chunk of obj.chunks) {
-				colors.normal("chunk ");
-				if (chunk.id < 1000) colors.normal(" ");
-				if (chunk.id < 100) colors.normal(" ");
-				if (chunk.id < 10) colors.normal(" ");
-				colors.normal("{");
-				colors.yellow(chunk.id);
-				colors.normal("} ");
-				colors.green(chunk.files.join(", "));
-				if (chunk.names && chunk.names.length > 0) {
-					colors.normal(" (");
-					colors.normal(chunk.names.join(", "));
-					colors.normal(")");
-				}
-				colors.normal(" ");
-				colors.normal(SizeFormatHelpers.formatSize(chunk.size));
-				for (const id of chunk.parents) {
-					colors.normal(" <{");
-					colors.yellow(id);
-					colors.normal("}>");
-				}
-				for (const id of chunk.siblings) {
-					colors.normal(" ={");
-					colors.yellow(id);
-					colors.normal("}=");
-				}
-				for (const id of chunk.children) {
-					colors.normal(" >{");
-					colors.yellow(id);
-					colors.normal("}<");
-				}
-				if (chunk.childrenByOrder) {
-					for (const name of Object.keys(chunk.childrenByOrder)) {
-						const children = chunk.childrenByOrder[name];
-						colors.normal(" ");
-						colors.magenta(`(${name}:`);
-						for (const id of children) {
-							colors.normal(" {");
-							colors.yellow(id);
-							colors.normal("}");
-						}
-						colors.magenta(")");
-					}
-				}
-				if (chunk.entry) {
-					colors.yellow(" [entry]");
-				} else if (chunk.initial) {
-					colors.yellow(" [initial]");
-				}
-				if (chunk.rendered) {
-					colors.green(" [rendered]");
-				}
-				if (chunk.recorded) {
-					colors.green(" [recorded]");
-				}
-				if (chunk.reason) {
-					colors.yellow(` ${chunk.reason}`);
-				}
-				newline();
-				if (chunk.origins) {
-					for (const origin of chunk.origins) {
-						colors.normal("    > ");
-						if (origin.reasons && origin.reasons.length) {
-							colors.yellow(origin.reasons.join(" "));
-							colors.normal(" ");
-						}
-						if (origin.request) {
-							colors.normal(origin.request);
-							colors.normal(" ");
-						}
-						if (origin.module) {
-							colors.normal("[");
-							colors.normal(origin.moduleId);
-							colors.normal("] ");
-							const module = modulesByIdentifier[`$${origin.module}`];
-							if (module) {
-								colors.bold(module.name);
-								colors.normal(" ");
-							}
-						}
-						if (origin.loc) {
-							colors.normal(origin.loc);
-						}
-						newline();
-					}
-				}
-				processModulesList(chunk, " ");
-			}
-		}
-
-		processModulesList(obj, "");
-
-		if (obj._showWarnings && obj.warnings) {
-			for (const warning of obj.warnings) {
-				newline();
-				colors.yellow(`WARNING in ${warning}`);
-				newline();
-			}
-		}
-		if (obj._showErrors && obj.errors) {
-			for (const error of obj.errors) {
-				newline();
-				colors.red(`ERROR in ${error}`);
-				newline();
-			}
-		}
-		if (obj.children) {
-			for (const child of obj.children) {
-				const childString = Stats.jsonToString(child, useColors);
-				if (childString) {
-					if (child.name) {
-						colors.normal("Child ");
-						colors.bold(child.name);
-						colors.normal(":");
-					} else {
-						colors.normal("Child");
-					}
-					newline();
-					buf.push("    ");
-					buf.push(childString.replace(/\n/g, "\n    "));
-					newline();
-				}
-			}
-		}
-		if (obj.needAdditionalPass) {
-			colors.yellow(
-				"Compilation needs an additional pass and will compile again."
-			);
-		}
+		const normalizedOptions = this.compilation.createStatsOptions(options, {
+			forToString: true
+		});
 
-		while (buf[buf.length - 1] === "\n") {
-			buf.pop();
-		}
-		return buf.join("");
-	}
+		const statsFactory = this.compilation.createStatsFactory(normalizedOptions);
+		const statsPrinter = this.compilation.createStatsPrinter(normalizedOptions);
 
-	static presetToOptions(name) {
-		// Accepted values: none, errors-only, minimal, normal, detailed, verbose
-		// Any other falsy value will behave as 'none', truthy values as 'normal'
-		const pn =
-			(typeof name === "string" && name.toLowerCase()) || name || "none";
-		switch (pn) {
-			case "none":
-				return {
-					all: false
-				};
-			case "verbose":
-				return {
-					entrypoints: true,
-					chunkGroups: true,
-					modules: false,
-					chunks: true,
-					chunkModules: true,
-					chunkOrigins: true,
-					depth: true,
-					env: true,
-					reasons: true,
-					usedExports: true,
-					providedExports: true,
-					optimizationBailout: true,
-					errorDetails: true,
-					publicPath: true,
-					exclude: false,
-					maxModules: Infinity
-				};
-			case "detailed":
-				return {
-					entrypoints: true,
-					chunkGroups: true,
-					chunks: true,
-					chunkModules: false,
-					chunkOrigins: true,
-					depth: true,
-					usedExports: true,
-					providedExports: true,
-					optimizationBailout: true,
-					errorDetails: true,
-					publicPath: true,
-					exclude: false,
-					maxModules: Infinity
-				};
-			case "minimal":
-				return {
-					all: false,
-					modules: true,
-					maxModules: 0,
-					errors: true,
-					warnings: true
-				};
-			case "errors-only":
-				return {
-					all: false,
-					errors: true,
-					moduleTrace: true
-				};
-			default:
-				return {};
-		}
-	}
-
-	static getChildOptions(options, idx) {
-		let innerOptions;
-		if (Array.isArray(options.children)) {
-			if (idx < options.children.length) {
-				innerOptions = options.children[idx];
-			}
-		} else if (typeof options.children === "object" && options.children) {
-			innerOptions = options.children;
-		}
-		if (typeof innerOptions === "boolean" || typeof innerOptions === "string") {
-			innerOptions = Stats.presetToOptions(innerOptions);
-		}
-		if (!innerOptions) {
-			return options;
-		}
-		const childOptions = Object.assign({}, options);
-		delete childOptions.children; // do not inherit children
-		return Object.assign(childOptions, innerOptions);
+		const data = statsFactory.create("compilation", this.compilation, {
+			compilation: this.compilation
+		});
+		const result = statsPrinter.print("compilation", data);
+		return result === undefined ? "" : result;
 	}
 }
 
diff --git a/lib/Template.js b/lib/Template.js
index 443e357678c..e0f0f8940c5 100644
--- a/lib/Template.js
+++ b/lib/Template.js
@@ -2,62 +2,138 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
-/** @typedef {import("./Module")} Module */
+
+"use strict";
+
+const { ConcatSource, PrefixSource } = require("webpack-sources");
+const { WEBPACK_MODULE_TYPE_RUNTIME } = require("./ModuleTypeConstants");
+const RuntimeGlobals = require("./RuntimeGlobals");
+
+/** @typedef {import("webpack-sources").Source} Source */
+/** @typedef {import("./config/defaults").OutputNormalizedWithDefaults} OutputOptions */
 /** @typedef {import("./Chunk")} Chunk */
+/** @typedef {import("./ChunkGraph")} ChunkGraph */
+/** @typedef {import("./ChunkGraph").ModuleId} ModuleId */
+/** @typedef {import("./CodeGenerationResults")} CodeGenerationResults */
+/** @typedef {import("./Compilation").AssetInfo} AssetInfo */
+/** @typedef {import("./Compilation").PathData} PathData */
+/** @typedef {import("./DependencyTemplates")} DependencyTemplates */
+/** @typedef {import("./Module")} Module */
+/** @typedef {import("./ModuleGraph")} ModuleGraph */
 /** @typedef {import("./ModuleTemplate")} ModuleTemplate */
-/** @typedef {import("webpack-sources").ConcatSource} ConcatSource */
-
-const { ConcatSource } = require("webpack-sources");
-const HotUpdateChunk = require("./HotUpdateChunk");
+/** @typedef {import("./RuntimeModule")} RuntimeModule */
+/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */
+/** @typedef {import("./TemplatedPathPlugin").TemplatePath} TemplatePath */
+/** @typedef {import("./javascript/JavascriptModulesPlugin").ChunkRenderContext} ChunkRenderContext */
+/** @typedef {import("./javascript/JavascriptModulesPlugin").RenderContext} RenderContext */
 
 const START_LOWERCASE_ALPHABET_CODE = "a".charCodeAt(0);
 const START_UPPERCASE_ALPHABET_CODE = "A".charCodeAt(0);
 const DELTA_A_TO_Z = "z".charCodeAt(0) - START_LOWERCASE_ALPHABET_CODE + 1;
+const NUMBER_OF_IDENTIFIER_START_CHARS = DELTA_A_TO_Z * 2 + 2; // a-z A-Z _ $
+const NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS =
+	NUMBER_OF_IDENTIFIER_START_CHARS + 10; // a-z A-Z _ $ 0-9
 const FUNCTION_CONTENT_REGEX = /^function\s?\(\)\s?\{\r?\n?|\r?\n?\}$/g;
+// JSDoc type annotations exist only to type the runtime template; strip them so
+// they are never emitted into the bundle. Whole-line blocks drop the line too.
+const JSDOC_LINE_REGEX = /^[ \t]*\/\*\*(?:[^*]|\*(?!\/))*\*\/[ \t]*\r?\n/gm;
+const JSDOC_INLINE_REGEX = /\/\*\*(?:[^*]|\*(?!\/))*\*\/[ \t]*/g;
 const INDENT_MULTILINE_REGEX = /^\t/gm;
+// Start of every non-blank line after the first. A lookahead keeps the next
+// character out of the match, so the replacement neither captures nor re-emits it.
+const LINE_START_REGEX = /\n(?=[^\n])/g;
 const LINE_SEPARATOR_REGEX = /\r?\n/g;
-const IDENTIFIER_NAME_REPLACE_REGEX = /^([^a-zA-Z$_])/;
-const IDENTIFIER_ALPHA_NUMERIC_NAME_REPLACE_REGEX = /[^a-zA-Z0-9$]+/g;
+const IDENTIFIER_NAME_REPLACE_REGEX = /^([^a-z$_])/i;
+const IDENTIFIER_ALPHA_NUMERIC_NAME_REPLACE_REGEX = /[^a-z0-9$]+/gi;
 const COMMENT_END_REGEX = /\*\//g;
-const PATH_NAME_NORMALIZE_REPLACE_REGEX = /[^a-zA-Z0-9_!§$()=\-^°]+/g;
+const PATH_NAME_NORMALIZE_REPLACE_REGEX = /[^a-z0-9_!§$()=\-^°]+/gi;
 const MATCH_PADDED_HYPHENS_REPLACE_REGEX = /^-|-$/g;
 
 /**
- * @typedef {Object} HasId
- * @property {number | string} id
- * */
+ * Decimal digit count of a non-negative integer, without allocating a string.
+ * @param {number} n non-negative integer
+ * @returns {number} number of decimal digits
+ */
+const numberLength = (n) => {
+	if (n < 10) return 1;
+	if (n < 100) return 2;
+	if (n < 1000) return 3;
+	if (n < 10000) return 4;
+	if (n < 100000) return 5;
+	if (n < 1000000) return 6;
+	if (n < 10000000) return 7;
+	return String(n).length;
+};
 
 /**
- * @typedef {function(Module, number): boolean} ModuleFilterPredicate
+ * Defines the render manifest options type used by this module.
+ * @typedef {object} RenderManifestOptions
+ * @property {Chunk} chunk the chunk used to render
+ * @property {string} hash
+ * @property {string} fullHash
+ * @property {OutputOptions} outputOptions
+ * @property {CodeGenerationResults} codeGenerationResults
+ * @property {{ javascript: ModuleTemplate }} moduleTemplates
+ * @property {DependencyTemplates} dependencyTemplates
+ * @property {RuntimeTemplate} runtimeTemplate
+ * @property {ModuleGraph} moduleGraph
+ * @property {ChunkGraph} chunkGraph
  */
 
+/** @typedef {RenderManifestEntryTemplated | RenderManifestEntryStatic} RenderManifestEntry */
+
 /**
- * @param {HasId} a first id object to be sorted
- * @param {HasId} b second id object to be sorted against
- * @returns {-1|0|1} the sort value
+ * Defines the render manifest entry templated type used by this module.
+ * @typedef {object} RenderManifestEntryTemplated
+ * @property {() => Source} render
+ * @property {string | import("./TemplatedPathPlugin").TemplatePathFn} filenameTemplate
+ * @property {PathData=} pathOptions
+ * @property {AssetInfo=} info
+ * @property {string} identifier
+ * @property {string=} hash
+ * @property {boolean=} auxiliary
+ */
+
+/**
+ * Defines the render manifest entry static type used by this module.
+ * @typedef {object} RenderManifestEntryStatic
+ * @property {() => Source} render
+ * @property {string} filename
+ * @property {AssetInfo} info
+ * @property {string} identifier
+ * @property {string=} hash
+ * @property {boolean=} auxiliary
+ */
+
+/**
+ * Defines the module filter predicate type used by this module.
+ * @typedef {(module: Module) => boolean} ModuleFilterPredicate
+ */
+
+/**
+ * Represents the template runtime component.
+ * @typedef {object} Stringable
+ * @property {() => string} toString
  */
-const stringifyIdSortPredicate = (a, b) => {
-	var aId = a.id + "";
-	var bId = b.id + "";
-	if (aId < bId) return -1;
-	if (aId > bId) return 1;
-	return 0;
-};
 
 class Template {
 	/**
-	 *
-	 * @param {Function} fn - a runtime function (.runtime.js) "template"
+	 * Gets function content.
+	 * @param {Stringable} fn a runtime function (.runtime.js) "template"
 	 * @returns {string} the updated and normalized function string
 	 */
 	static getFunctionContent(fn) {
 		return fn
 			.toString()
+			.replace(JSDOC_LINE_REGEX, "")
+			.replace(JSDOC_INLINE_REGEX, "")
 			.replace(FUNCTION_CONTENT_REGEX, "")
 			.replace(INDENT_MULTILINE_REGEX, "")
 			.replace(LINE_SEPARATOR_REGEX, "\n");
 	}
+
 	/**
+	 * Returns created identifier.
 	 * @param {string} str the string converted to identifier
 	 * @returns {string} created identifier
 	 */
@@ -67,27 +143,29 @@ class Template {
 			.replace(IDENTIFIER_NAME_REPLACE_REGEX, "_$1")
 			.replace(IDENTIFIER_ALPHA_NUMERIC_NAME_REPLACE_REGEX, "_");
 	}
+
 	/**
-	 *
+	 * Returns a commented version of string.
 	 * @param {string} str string to be converted to commented in bundle code
 	 * @returns {string} returns a commented version of string
 	 */
 	static toComment(str) {
 		if (!str) return "";
-		return `/*! ${str.replace(COMMENT_END_REGEX, "* /")} */`;
+		return `/*! ${str.includes("*/") ? str.replace(COMMENT_END_REGEX, "* /") : str} */`;
 	}
 
 	/**
-	 *
+	 * Returns a commented version of string.
 	 * @param {string} str string to be converted to "normal comment"
 	 * @returns {string} returns a commented version of string
 	 */
 	static toNormalComment(str) {
 		if (!str) return "";
-		return `/* ${str.replace(COMMENT_END_REGEX, "* /")} */`;
+		return `/* ${str.includes("*/") ? str.replace(COMMENT_END_REGEX, "* /") : str} */`;
 	}
 
 	/**
+	 * Returns normalized bundle-safe path.
 	 * @param {string} str string path to be normalized
 	 * @returns {string} normalized bundle-safe path
 	 */
@@ -98,67 +176,110 @@ class Template {
 			.replace(MATCH_PADDED_HYPHENS_REPLACE_REGEX, "");
 	}
 
-	// map number to a single character a-z, A-Z or <_ + number> if number is too big
+	// map number to a single character a-z, A-Z or multiple characters if number is too big
 	/**
-	 *
+	 * Number to identifier.
 	 * @param {number} n number to convert to ident
 	 * @returns {string} returns single character ident
 	 */
-	static numberToIdentifer(n) {
+	static numberToIdentifier(n) {
+		if (n >= NUMBER_OF_IDENTIFIER_START_CHARS) {
+			// use multiple letters
+			return (
+				Template.numberToIdentifier(n % NUMBER_OF_IDENTIFIER_START_CHARS) +
+				Template.numberToIdentifierContinuation(
+					Math.floor(n / NUMBER_OF_IDENTIFIER_START_CHARS)
+				)
+			);
+		}
+
 		// lower case
 		if (n < DELTA_A_TO_Z) {
 			return String.fromCharCode(START_LOWERCASE_ALPHABET_CODE + n);
 		}
+		n -= DELTA_A_TO_Z;
 
 		// upper case
-		if (n < DELTA_A_TO_Z * 2) {
-			return String.fromCharCode(
-				START_UPPERCASE_ALPHABET_CODE + n - DELTA_A_TO_Z
+		if (n < DELTA_A_TO_Z) {
+			return String.fromCharCode(START_UPPERCASE_ALPHABET_CODE + n);
+		}
+
+		if (n === DELTA_A_TO_Z) return "_";
+		return "$";
+	}
+
+	/**
+	 * Number to identifier continuation.
+	 * @param {number} n number to convert to ident
+	 * @returns {string} returns single character ident
+	 */
+	static numberToIdentifierContinuation(n) {
+		if (n >= NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS) {
+			// use multiple letters
+			return (
+				Template.numberToIdentifierContinuation(
+					n % NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS
+				) +
+				Template.numberToIdentifierContinuation(
+					Math.floor(n / NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS)
+				)
 			);
 		}
 
-		// use multiple letters
-		return (
-			Template.numberToIdentifer(n % (2 * DELTA_A_TO_Z)) +
-			Template.numberToIdentifer(Math.floor(n / (2 * DELTA_A_TO_Z)))
-		);
+		// lower case
+		if (n < DELTA_A_TO_Z) {
+			return String.fromCharCode(START_LOWERCASE_ALPHABET_CODE + n);
+		}
+		n -= DELTA_A_TO_Z;
+
+		// upper case
+		if (n < DELTA_A_TO_Z) {
+			return String.fromCharCode(START_UPPERCASE_ALPHABET_CODE + n);
+		}
+		n -= DELTA_A_TO_Z;
+
+		// numbers
+		if (n < 10) {
+			return `${n}`;
+		}
+
+		if (n === 10) return "_";
+		return "$";
 	}
 
 	/**
-	 *
-	 * @param {string | string[]} str string to convert to identity
+	 * Returns converted identity.
+	 * @param {string | string[]} s string to convert to identity
 	 * @returns {string} converted identity
 	 */
-	static indent(str) {
-		if (Array.isArray(str)) {
-			return str.map(Template.indent).join("\n");
-		} else {
-			str = str.trimRight();
-			if (!str) return "";
-			var ind = str[0] === "\n" ? "" : "\t";
-			return ind + str.replace(/\n([^\n])/g, "\n\t$1");
+	static indent(s) {
+		if (Array.isArray(s)) {
+			return s.map(Template.indent).join("\n");
 		}
+		const str = s.trimEnd();
+		if (!str) return "";
+		const ind = str[0] === "\n" ? "" : "\t";
+		return ind + str.replace(LINE_START_REGEX, "\n\t");
 	}
 
 	/**
-	 *
-	 * @param {string|string[]} str string to create prefix for
+	 * Returns new prefix string.
+	 * @param {string | string[]} s string to create prefix for
 	 * @param {string} prefix prefix to compose
 	 * @returns {string} returns new prefix string
 	 */
-	static prefix(str, prefix) {
-		if (Array.isArray(str)) {
-			str = str.join("\n");
-		}
-		str = str.trim();
+	static prefix(s, prefix) {
+		const str = Template.asString(s).trim();
 		if (!str) return "";
 		const ind = str[0] === "\n" ? "" : prefix;
-		return ind + str.replace(/\n([^\n])/g, "\n" + prefix + "$1");
+		// Keeps the capture group: `prefix` is caller-supplied and may itself hold
+		// `$&`/`$1`, which expand differently against a zero-width lookahead.
+		return ind + str.replace(/\n([^\n])/g, `\n${prefix}$1`);
 	}
 
 	/**
-	 *
-	 * @param {string|string[]} str string or string collection
+	 * Returns a single string from array.
+	 * @param {string | string[]} str string or string collection
 	 * @returns {string} returns a single string from array
 	 */
 	static asString(str) {
@@ -169,118 +290,189 @@ class Template {
 	}
 
 	/**
-	 * @typedef {Object} WithId
-	 * @property {string|number} id
+	 * Defines the with id type used by this module.
+	 * @typedef {object} WithId
+	 * @property {string | number} id
 	 */
 
 	/**
+	 * Gets modules array bounds.
 	 * @param {WithId[]} modules a collection of modules to get array bounds for
 	 * @returns {[number, number] | false} returns the upper and lower array bounds
 	 * or false if not every module has a number based id
 	 */
 	static getModulesArrayBounds(modules) {
-		var maxId = -Infinity;
-		var minId = Infinity;
+		let maxId = -Infinity;
+		let minId = Infinity;
 		for (const module of modules) {
-			if (typeof module.id !== "number") return false;
-			if (maxId < module.id) maxId = /** @type {number} */ (module.id);
-			if (minId > module.id) minId = /** @type {number} */ (module.id);
+			const moduleId = module.id;
+			if (typeof moduleId !== "number") return false;
+			if (maxId < moduleId) maxId = moduleId;
+			if (minId > moduleId) minId = moduleId;
 		}
-		if (minId < 16 + ("" + minId).length) {
+		if (minId < 16 + String(minId).length) {
 			// add minId x ',' instead of 'Array(minId).concat(…)'
 			minId = 0;
 		}
-		var objectOverhead = modules
-			.map(module => {
-				var idLength = (module.id + "").length;
-				return idLength + 2;
-			})
-			.reduce((a, b) => {
-				return a + b;
-			}, -1);
-		var arrayOverhead = minId === 0 ? maxId : 16 + ("" + minId).length + maxId;
+		// start with -1 because the first module needs no comma
+		let objectOverhead = -1;
+		for (const module of modules) {
+			// module id digits + colon + comma; ids are non-negative integers here
+			// (non-number ids already returned false above), so count digits
+			// arithmetically instead of allocating a string per module.
+			const id = /** @type {number} */ (module.id);
+			objectOverhead += numberLength(id) + 2;
+		}
+		// number of commas, or when starting non-zero the length of Array(minId).concat()
+		const arrayOverhead =
+			minId === 0 ? maxId : 16 + numberLength(minId) + maxId;
 		return arrayOverhead < objectOverhead ? [minId, maxId] : false;
 	}
 
 	/**
-	 * @param {Chunk} chunk chunk whose modules will be rendered
-	 * @param {ModuleFilterPredicate} filterFn function used to filter modules from chunk to render
-	 * @param {ModuleTemplate} moduleTemplate ModuleTemplate instance used to render modules
-	 * @param {TODO | TODO[]} dependencyTemplates templates needed for each module to render dependencies
+	 * Renders chunk modules.
+	 * @param {ChunkRenderContext} renderContext render context
+	 * @param {Module[]} modules modules to render (should be ordered by identifier)
+	 * @param {(module: Module, renderInArray?: boolean) => Source | null} renderModule function to render a module
 	 * @param {string=} prefix applying prefix strings
-	 * @returns {ConcatSource} rendered chunk modules in a Source object
+	 * @returns {Source | null} rendered chunk modules in a Source object or null if no modules
 	 */
-	static renderChunkModules(
-		chunk,
-		filterFn,
-		moduleTemplate,
-		dependencyTemplates,
-		prefix
-	) {
-		if (!prefix) prefix = "";
-		var source = new ConcatSource();
-		const modules = chunk.getModules().filter(filterFn);
-		if (chunk instanceof HotUpdateChunk) {
-			var removedModules = chunk.removedModules;
+	static renderChunkModules(renderContext, modules, renderModule, prefix = "") {
+		const { chunkGraph } = renderContext;
+		const source = new ConcatSource();
+		if (modules.length === 0) {
+			return null;
 		}
-		if (
-			modules.length === 0 &&
-			(!removedModules || removedModules.length === 0)
-		) {
-			source.add("[]");
-			return source;
-		}
-		var allModules = modules.map(module => {
-			return {
-				id: module.id,
-				source: moduleTemplate.render(module, dependencyTemplates, {
-					chunk
-				})
-			};
-		});
-		if (removedModules && removedModules.length > 0) {
-			for (const id of removedModules) {
-				allModules.push({
-					id: id,
-					source: "false"
-				});
-			}
-		}
-		var bounds = Template.getModulesArrayBounds(allModules);
+		/** @type {{ id: ModuleId, module: Module }[]} */
+		const modulesWithId = modules.map((m) => ({
+			id: /** @type {ModuleId} */ (chunkGraph.getModuleId(m)),
+			module: m
+		}));
+		const bounds = Template.getModulesArrayBounds(modulesWithId);
+		const renderInObject = bounds === false;
+
+		/** @type {{ id: ModuleId, source: Source | "false" }[]} */
+		const allModules = modulesWithId.map(({ id, module }) => ({
+			id,
+			source: renderModule(module, renderInObject) || "false"
+		}));
 
 		if (bounds) {
 			// Render a spare array
-			var minId = bounds[0];
-			var maxId = bounds[1];
-			if (minId !== 0) source.add("Array(" + minId + ").concat(");
+			const minId = bounds[0];
+			const maxId = bounds[1];
+			if (minId !== 0) {
+				source.add(`Array(${minId}).concat(`);
+			}
 			source.add("[\n");
+			/** @type {Map} */
 			const modules = new Map();
 			for (const module of allModules) {
 				modules.set(module.id, module);
 			}
-			for (var idx = minId; idx <= maxId; idx++) {
-				var module = modules.get(idx);
-				if (idx !== minId) source.add(",\n");
-				source.add("/* " + idx + " */");
+			for (let idx = minId; idx <= maxId; idx++) {
+				const module = modules.get(idx);
+				if (idx !== minId) {
+					source.add(",\n");
+				}
+				source.add(`/* ${idx} */`);
 				if (module) {
 					source.add("\n");
 					source.add(module.source);
 				}
 			}
-			source.add("\n" + prefix + "]");
-			if (minId !== 0) source.add(")");
+			source.add(`\n${prefix}]`);
+			if (minId !== 0) {
+				source.add(")");
+			}
 		} else {
 			// Render an object
 			source.add("{\n");
-			allModules.sort(stringifyIdSortPredicate).forEach((module, idx) => {
-				if (idx !== 0) source.add(",\n");
-				source.add(`\n/***/ ${JSON.stringify(module.id)}:\n`);
+			for (let i = 0; i < allModules.length; i++) {
+				const module = allModules[i];
+				if (i !== 0) {
+					source.add(",\n");
+				}
+				source.add(
+					`\n/***/ ${JSON.stringify(module.id)}${renderContext.runtimeTemplate.supportsMethodShorthand() && module.source !== "false" ? "" : ":"}\n`
+				);
 				source.add(module.source);
-			});
-			source.add("\n\n" + prefix + "}");
+			}
+			source.add(`\n\n${prefix}}`);
 		}
 		return source;
 	}
+
+	/**
+	 * Renders runtime modules.
+	 * @param {RuntimeModule[]} runtimeModules array of runtime modules in order
+	 * @param {RenderContext & { codeGenerationResults?: CodeGenerationResults }} renderContext render context
+	 * @returns {Source} rendered runtime modules in a Source object
+	 */
+	static renderRuntimeModules(runtimeModules, renderContext) {
+		const source = new ConcatSource();
+		for (const module of runtimeModules) {
+			const codeGenerationResults = renderContext.codeGenerationResults;
+			/** @type {undefined | Source} */
+			let runtimeSource;
+			if (codeGenerationResults) {
+				runtimeSource = codeGenerationResults.getSource(
+					module,
+					renderContext.chunk.runtime,
+					WEBPACK_MODULE_TYPE_RUNTIME
+				);
+			} else {
+				const codeGenResult = module.codeGeneration({
+					chunkGraph: renderContext.chunkGraph,
+					dependencyTemplates: renderContext.dependencyTemplates,
+					moduleGraph: renderContext.moduleGraph,
+					runtimeTemplate: renderContext.runtimeTemplate,
+					runtime: renderContext.chunk.runtime,
+					runtimes: [renderContext.chunk.runtime],
+					codeGenerationResults
+				});
+				if (!codeGenResult) continue;
+				runtimeSource = codeGenResult.sources.get("runtime");
+			}
+			if (runtimeSource) {
+				source.add(`${Template.toNormalComment(module.identifier())}\n`);
+				if (!module.shouldIsolate()) {
+					source.add(runtimeSource);
+					source.add("\n\n");
+				} else if (renderContext.runtimeTemplate.supportsArrowFunction()) {
+					source.add("(() => {\n");
+					source.add(new PrefixSource("\t", runtimeSource));
+					source.add("\n})();\n\n");
+				} else {
+					source.add("!function() {\n");
+					source.add(new PrefixSource("\t", runtimeSource));
+					source.add("\n}();\n\n");
+				}
+			}
+		}
+		return source;
+	}
+
+	/**
+	 * Renders chunk runtime modules.
+	 * @param {RuntimeModule[]} runtimeModules array of runtime modules in order
+	 * @param {RenderContext} renderContext render context
+	 * @returns {Source} rendered chunk runtime modules in a Source object
+	 */
+	static renderChunkRuntimeModules(runtimeModules, renderContext) {
+		return new PrefixSource(
+			"/******/ ",
+			new ConcatSource(
+				`function(${RuntimeGlobals.require}) { // webpackRuntimeModules\n`,
+				this.renderRuntimeModules(runtimeModules, renderContext),
+				"}\n"
+			)
+		);
+	}
 }
 
 module.exports = Template;
+module.exports.NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS =
+	NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS;
+module.exports.NUMBER_OF_IDENTIFIER_START_CHARS =
+	NUMBER_OF_IDENTIFIER_START_CHARS;
diff --git a/lib/TemplatedPathPlugin.js b/lib/TemplatedPathPlugin.js
index 8cd1ef40a44..d537ee19a1e 100644
--- a/lib/TemplatedPathPlugin.js
+++ b/lib/TemplatedPathPlugin.js
@@ -2,172 +2,846 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Jason Anderson @diurnalist
 */
+
 "use strict";
 
-const REGEXP_HASH = /\[hash(?::(\d+))?\]/gi,
-	REGEXP_CHUNKHASH = /\[chunkhash(?::(\d+))?\]/gi,
-	REGEXP_MODULEHASH = /\[modulehash(?::(\d+))?\]/gi,
-	REGEXP_CONTENTHASH = /\[contenthash(?::(\d+))?\]/gi,
-	REGEXP_NAME = /\[name\]/gi,
-	REGEXP_ID = /\[id\]/gi,
-	REGEXP_MODULEID = /\[moduleid\]/gi,
-	REGEXP_FILE = /\[file\]/gi,
-	REGEXP_QUERY = /\[query\]/gi,
-	REGEXP_FILEBASE = /\[filebase\]/gi;
-
-// Using global RegExp for .test is dangerous
-// We use a normal RegExp instead of .test
-const REGEXP_HASH_FOR_TEST = new RegExp(REGEXP_HASH.source, "i"),
-	REGEXP_CHUNKHASH_FOR_TEST = new RegExp(REGEXP_CHUNKHASH.source, "i"),
-	REGEXP_CONTENTHASH_FOR_TEST = new RegExp(REGEXP_CONTENTHASH.source, "i"),
-	REGEXP_NAME_FOR_TEST = new RegExp(REGEXP_NAME.source, "i");
-
-const withHashLength = (replacer, handlerFn) => {
-	const fn = (match, hashLength, ...args) => {
-		const length = hashLength && parseInt(hashLength, 10);
-		if (length && handlerFn) {
-			return handlerFn(length);
-		}
-		const hash = replacer(match, hashLength, ...args);
-		return length ? hash.slice(0, length) : hash;
+const { basename, extname, posix } = require("path");
+const util = require("util");
+const Chunk = require("./Chunk");
+const Module = require("./Module");
+const {
+	decode: decodeBase,
+	encode: encodeBase
+} = require("./util/hash/hash-digest");
+const {
+	ABSOLUTE_PATH_REGEXP,
+	WINDOWS_ABS_PATH_REGEXP,
+	WINDOWS_PATH_SEPARATOR_REGEXP,
+	parseResource
+} = require("./util/identifier");
+const memoize = require("./util/memoize");
+
+const getMimeTypes = memoize(() => require("./util/mimeTypes"));
+
+/** @typedef {import("./ChunkGraph")} ChunkGraph */
+/** @typedef {import("./ChunkGraph").ModuleId} ModuleId */
+/** @typedef {import("./Compilation")} Compilation */
+/** @typedef {import("./Compilation").AssetInfo} AssetInfo */
+/** @typedef {import("./Compilation").HashWithDigestFunction} HashWithDigestFunction */
+/** @typedef {import("./Compilation").PathData} PathData */
+/** @typedef {import("./Compilation").PathDataChunk} PathDataChunk */
+/** @typedef {import("./Compilation").PathDataModule} PathDataModule */
+/** @typedef {import("./Compiler")} Compiler */
+
+const REGEXP = /\[\\*([\w:]+)\\*\]/g;
+
+/**
+ * Placeholder kinds present in a template string, cached so the scan is not
+ * repeated for reused templates (e.g. `output.filename`, `localIdentName`).
+ * Bounded so dynamic (function-built) paths can't grow it without limit.
+ * @type {Map>}
+ */
+const presentKindsCache = new Map();
+const PRESENT_KINDS_CACHE_MAX = 1000;
+
+/**
+ * Returns the placeholder kinds (`[kind]` / `[kind:arg]`) a template references,
+ * matching the replace pass below so guarding replacer construction by it stays
+ * output-identical.
+ * @param {string} path template string (already known to contain `[`)
+ * @returns {Set} placeholder kinds present
+ */
+const getPresentKinds = (path) => {
+	const cached = presentKindsCache.get(path);
+	if (cached !== undefined) return cached;
+	/** @type {Set} */
+	const kinds = new Set();
+	// `RegExp.exec` loop rather than `String.matchAll` (Node.js 12+) so this
+	// stays compatible with the supported Node.js 10 range.
+	REGEXP.lastIndex = 0;
+	/** @type {RegExpExecArray | null} */
+	let m;
+	while ((m = REGEXP.exec(path)) !== null) {
+		const content = /** @type {string} */ (m[1]);
+		if (content.length + 2 === m[0].length) {
+			const cm = /^(\w+)(?::\w+)?(?::\w+)?$/.exec(content);
+			if (cm) kinds.add(cm[1]);
+		}
+	}
+	if (presentKindsCache.size >= PRESENT_KINDS_CACHE_MAX) {
+		presentKindsCache.clear();
+	}
+	presentKindsCache.set(path, kinds);
+	return kinds;
+};
+
+// `[fullhash:]`/`[hash:]` — a non-numeric first argument is a digest
+// (a numeric one is just a length). A digest re-encodes the hash rather than reading
+// it, so no stand-in or runtime expression can take its place: the settled value has
+// to be inlined instead.
+const FULL_HASH_DIGEST_REGEXP = /\[(?:fullhash|hash):(?!\d+\])\w/;
+
+/**
+ * @param {string} template path template
+ * @returns {boolean} true when it references `[fullhash:]`/`[hash:]`
+ */
+const usesFullHashDigest = (template) => FULL_HASH_DIGEST_REGEXP.test(template);
+
+const PARENT_SEGMENTS_REGEXP = /^(?:\.\.\/)+/;
+
+/**
+ * Maps a `[path]`/`[file]` value into the output directory: an absolute root or
+ * a leading `..` becomes `_` (as `file-loader` did), so a module resolved
+ * outside of `context` can't emit its asset outside of `output.path`.
+ * @param {string} path relative path of the source file
+ * @returns {string} path that stays inside the output directory
+ */
+const toContainedPath = (path) => {
+	if (!path) return path;
+	// A resource on another Windows drive stays absolute and win32-separated
+	// when made relative to the context.
+	const normalized = posix.normalize(
+		WINDOWS_ABS_PATH_REGEXP.test(path)
+			? path.replace(WINDOWS_PATH_SEPARATOR_REGEXP, "/")
+			: path
+	);
+	return normalized
+		.replace(ABSOLUTE_PATH_REGEXP, "_/")
+		.replace(PARENT_SEGMENTS_REGEXP, (match) => "_/".repeat(match.length / 3));
+};
+
+/**
+ * Source file name a filename template sees. `[path]`/`[file]` of a module
+ * resolved outside of `context` start with `..` and would emit the asset
+ * outside of `output.path`, so they are contained like
+ * `[containedpath]`/`[containedfile]` with `experiments.futureDefaults`.
+ * @param {string} sourceFilename context-relative path of the source file
+ * @param {Compilation} compilation compilation
+ * @returns {string} source file name for the filename template
+ */
+const toTemplateSourceFileName = (sourceFilename, compilation) =>
+	// TODO webpack 6: remove the check, always contain
+	compilation.options.experiments.futureDefaults
+		? toContainedPath(sourceFilename)
+		: sourceFilename;
+
+/** @type {PathData["prepareId"]} */
+const prepareId = (id) => {
+	if (typeof id !== "string") return id;
+
+	if (/^"\s\+*.*\+\s*"$/.test(id)) {
+		const match = /^"\s\+*\s*(.*)\s*\+\s*"$/.exec(id);
+
+		return `" + (${
+			/** @type {string[]} */ (match)[1]
+		} + "").replace(/(^[.-]|[^a-zA-Z0-9_-])+/g, "_") + "`;
+	}
+
+	return id.replace(/(^[.-]|[^a-z0-9_-])+/gi, "_");
+};
+
+/**
+ * Defines the replacer function callback.
+ * @callback ReplacerFunction
+ * @param {string} match
+ * @param {string | undefined} arg
+ * @param {string} input
+ */
+
+/** @typedef {"26" | "32" | "36" | "49" | "52" | "58" | "62"} Base */
+
+// `[]` placeholders may request a digest webpack does not store the
+// hash in; loader-utils calls the URL-safe base64 `base64safe`.
+const BASE_DIGEST = /^base(\d+)$/;
+const SUPPORTED_BASES = new Set(["26", "32", "36", "49", "52", "58", "62"]);
+
+// Node < 14.18 lacks the `base64url` Buffer encoding; fall back to base64 + swap.
+let isBase64UrlSupported = false;
+try {
+	isBase64UrlSupported = Boolean(Buffer.from("", "base64url"));
+} catch (_err) {
+	// Nothing
+}
+
+/**
+ * @param {string} digest digest name from a `[:]` placeholder
+ * @returns {boolean} whether a hash can be re-encoded into this digest
+ */
+const isSupportedDigest = (digest) => {
+	if (digest === "base64url" || digest === "base64safe") return true;
+	const base = BASE_DIGEST.exec(digest);
+	if (base) return base[1] === "64" || SUPPORTED_BASES.has(base[1]);
+	return Buffer.isEncoding(digest);
+};
+
+/**
+ * Decodes an already-digested hash string back into its raw bytes.
+ * @param {string} value digested hash
+ * @param {string} digest digest the value is encoded in
+ * @returns {Buffer} raw bytes
+ */
+const digestToBuffer = (value, digest) => {
+	const base = BASE_DIGEST.exec(digest);
+	if (base && Number(base[1]) !== 64) {
+		return decodeBase(value, /** @type {Base} */ (base[1]));
+	}
+	if (
+		(digest === "base64url" || digest === "base64safe") &&
+		!isBase64UrlSupported
+	) {
+		return Buffer.from(value.replace(/-/g, "+").replace(/_/g, "/"), "base64");
+	}
+	return Buffer.from(
+		value,
+		/** @type {BufferEncoding} */ (
+			digest === "base64safe" ? "base64url" : digest
+		)
+	);
+};
+
+/**
+ * Encodes raw bytes into the requested digest.
+ * @param {Buffer} buffer raw bytes
+ * @param {string} digest target digest
+ * @returns {string} encoded hash
+ */
+const bufferToDigest = (buffer, digest) => {
+	const base = BASE_DIGEST.exec(digest);
+	if (base && Number(base[1]) !== 64) {
+		return encodeBase(buffer, /** @type {Base} */ (base[1]));
+	}
+	if (
+		(digest === "base64url" || digest === "base64safe") &&
+		!isBase64UrlSupported
+	) {
+		return buffer
+			.toString("base64")
+			.replace(/\+/g, "-")
+			.replace(/\//g, "_")
+			.replace(/[=]+$/, "");
+	}
+	return buffer.toString(
+		/** @type {BufferEncoding} */ (
+			digest === "base64safe" ? "base64url" : digest
+		)
+	);
+};
+
+/**
+ * Re-encodes a digested hash into another digest (e.g. `[contenthash:base64]`).
+ * The source is already truncated to `output.hashDigestLength`, so the result is
+ * derived from those bytes, not the full content. Throws on an unknown digest so
+ * a typo fails loudly rather than silently keeping the original encoding.
+ * @param {string} value digested hash
+ * @param {string} fromDigest digest the value is encoded in
+ * @param {string} toDigest requested digest
+ * @returns {string} re-encoded hash
+ */
+const reEncodeDigest = (value, fromDigest, toDigest) => {
+	if (toDigest === fromDigest) return value;
+	if (!isSupportedDigest(toDigest)) {
+		throw new Error(
+			`Unsupported hash digest "${toDigest}" in path template (use hex, base64, base64url, or base26/32/36/49/52/58/62)`
+		);
+	}
+	return bufferToDigest(digestToBuffer(value, fromDigest), toDigest);
+};
+
+/**
+ * Returns hash replacer function.
+ * @param {ReplacerFunction} replacer replacer
+ * @param {((arg0: number) => string) | undefined} handler handler
+ * @param {AssetInfo | undefined} assetInfo asset info
+ * @param {string} hashName hash name
+ * @param {string} sourceDigest digest the stored hash is encoded in
+ * @param {string=} fullValue untruncated hash, re-encoded for `[:]` so the result carries full entropy instead of the `hashDigestLength`-truncated value
+ * @param {boolean=} recordDigest record the inline digest on `assetInfo.contenthashDigest` so `RealContentHashPlugin` re-encodes the recomputed hash in it
+ * @param {HashWithDigestFunction=} digestHandler builds the value for `[:]` in a per-chunk runtime context (the runtime chunk-filename map), where a single re-encode of the whole expression is impossible
+ * @returns {Replacer} hash replacer function
+ */
+const hashLength = (
+	replacer,
+	handler,
+	assetInfo,
+	hashName,
+	sourceDigest,
+	fullValue,
+	recordDigest,
+	digestHandler
+) => {
+	/** @type {Replacer} */
+	const fn = (match, arg, input, digest) => {
+		/** @type {string} */
+		let result;
+		const length = arg && Number.parseInt(arg, 10);
+
+		if (digest && digestHandler) {
+			result = digestHandler(digest, length || undefined);
+		} else if (digest) {
+			const hash = reEncodeDigest(
+				fullValue !== undefined ? fullValue : replacer(match, arg, input),
+				sourceDigest,
+				digest
+			);
+			result = length ? hash.slice(0, length) : hash;
+		} else if (length && handler) {
+			result = handler(length);
+		} else {
+			const hash = replacer(match, arg, input);
+
+			result = length ? hash.slice(0, length) : hash;
+		}
+		if (assetInfo) {
+			assetInfo.immutable = true;
+			if (digest && recordDigest) {
+				// `base64safe` is encoded as `base64url`; record what the value is in.
+				(assetInfo.contenthashDigest || (assetInfo.contenthashDigest = {}))[
+					result
+				] = digest === "base64safe" ? "base64url" : digest;
+			}
+			if (Array.isArray(assetInfo[hashName])) {
+				assetInfo[hashName] = [...assetInfo[hashName], result];
+			} else if (assetInfo[hashName]) {
+				assetInfo[hashName] = [assetInfo[hashName], result];
+			} else {
+				assetInfo[hashName] = result;
+			}
+		}
+		return result;
 	};
+
 	return fn;
 };
 
-const getReplacer = (value, allowEmpty) => {
-	const fn = (match, ...args) => {
-		// last argument in replacer is the entire input string
-		const input = args[args.length - 1];
+/** @typedef {(match: string, arg: string | undefined, input: string, digest?: string) => string} Replacer */
+
+/**
+ * Returns replacer.
+ * @param {string | number | null | undefined | (() => string | number | null | undefined)} value value
+ * @param {boolean=} allowEmpty allow empty
+ * @returns {Replacer} replacer
+ */
+const replacer = (value, allowEmpty) => {
+	/** @type {Replacer} */
+	const fn = (match, arg, input) => {
+		if (typeof value === "function") {
+			value = value();
+		}
 		if (value === null || value === undefined) {
 			if (!allowEmpty) {
 				throw new Error(
 					`Path variable ${match} not implemented in this context: ${input}`
 				);
 			}
+
 			return "";
-		} else {
-			return `${value}`;
 		}
+
+		return `${value}`;
 	};
+
 	return fn;
 };
 
-const replacePathVariables = (path, data) => {
-	const chunk = data.chunk;
-	const chunkId = chunk && chunk.id;
-	const chunkName = chunk && (chunk.name || chunk.id);
-	const chunkHash = chunk && (chunk.renderedHash || chunk.hash);
-	const chunkHashWithLength = chunk && chunk.hashWithLength;
-	const contentHashType = data.contentHashType;
-	const contentHash =
-		(chunk && chunk.contentHash && chunk.contentHash[contentHashType]) ||
-		data.contentHash;
-	const contentHashWithLength =
-		(chunk &&
-			chunk.contentHashWithLength &&
-			chunk.contentHashWithLength[contentHashType]) ||
-		data.contentHashWithLength;
-	const module = data.module;
-	const moduleId = module && module.id;
-	const moduleHash = module && (module.renderedHash || module.hash);
-	const moduleHashWithLength = module && module.hashWithLength;
+/** @type {Map EXPECTED_ANY>} */
+const deprecationCache = new Map();
+const deprecatedFunction = (() => () => {})();
+/**
+ * Returns function with deprecation output.
+ * @template {(...args: EXPECTED_ANY[]) => EXPECTED_ANY} T
+ * @param {T} fn function
+ * @param {string} message message
+ * @param {string} code code
+ * @returns {T} function with deprecation output
+ */
+const deprecated = (fn, message, code) => {
+	let d = deprecationCache.get(message);
+	if (d === undefined) {
+		d = util.deprecate(deprecatedFunction, message, code);
+		deprecationCache.set(message, d);
+	}
+	return /** @type {T} */ (
+		(...args) => {
+			d();
+			return fn(...args);
+		}
+	);
+};
+
+/**
+ * Callback used to compute a path from contextual data. The type parameter
+ * narrows the `pathData` shape when the caller knows it operates in a chunk
+ * (`PathDataChunk`) or module (`PathDataModule`) context — defaults to the
+ * fully-optional `PathData` for backward compatibility.
+ * @template {PathData} [T=PathData]
+ * @typedef {(pathData: T, assetInfo?: AssetInfo) => string} TemplatePathFn
+ */
+
+/**
+ * Either a raw template string (e.g. `"[name].[contenthash].js"`) or a
+ * generic `TemplatePathFn`. Method signatures that need to thread a narrowed
+ * `PathData` shape spell the function side out as `TemplatePathFn`
+ * directly — `TemplatePath` itself stays a plain alias so local JSDoc
+ * re-imports keep a single shared identity.
+ * @typedef {string | TemplatePathFn} TemplatePath
+ */
 
+/**
+ * Returns the interpolated path.
+ * @template {PathData} [T=PathData]
+ * @param {string | TemplatePathFn} path the raw path
+ * @param {T} data context data
+ * @param {AssetInfo=} assetInfo extra info about the asset (will be written to)
+ * @returns {string} the interpolated path
+ */
+const interpolate = (path, data, assetInfo) => {
 	if (typeof path === "function") {
-		path = path(data);
+		path = path(data, assetInfo);
+	}
+
+	// Literal paths carry no `[placeholder]`, so the whole replacement table
+	// and regex pass are pure overhead — building replacers has no side effects
+	// (those only fire when a replacer is invoked), so the output is identical.
+	if (!path.includes("[")) {
+		return path;
 	}
 
+	// Only build replacers for placeholders the template actually uses — most
+	// templates reference a handful, so building the whole table per call is
+	// wasted work. Replacer construction has no side effects (those fire only
+	// when a replacer is invoked, which happens for present kinds), so this is
+	// output-identical.
+	const presentKinds = getPresentKinds(path);
+
+	const chunkGraph = data.chunkGraph;
+	// Digest the stored hashes are encoded in, so `[hash:]` can re-encode.
+	const sourceDigest = data.hashDigest || "hex";
+
+	/** @type {Map} */
+	const replacements = new Map();
+
+	// Filename context
+	//
+	// Placeholders
+	//
+	// for /some/path/file.js?query#fragment:
+	// [file] - /some/path/file.js
+	// [query] - ?query
+	// [fragment] - #fragment
+	// [base] - file.js
+	// [path] - /some/path/
+	// [name] - file
+	// [ext] - .js
+	//
+	// [containedfile] and [containedpath] are [file] and [path] kept inside
+	// `output.path`
 	if (
-		data.noChunkHash &&
-		(REGEXP_CHUNKHASH_FOR_TEST.test(path) ||
-			REGEXP_CONTENTHASH_FOR_TEST.test(path))
+		typeof data.filename === "string" &&
+		(presentKinds.has("file") ||
+			presentKinds.has("query") ||
+			presentKinds.has("fragment") ||
+			presentKinds.has("path") ||
+			presentKinds.has("base") ||
+			presentKinds.has("name") ||
+			presentKinds.has("ext") ||
+			presentKinds.has("filebase") ||
+			presentKinds.has("containedfile") ||
+			presentKinds.has("containedpath"))
 	) {
-		throw new Error(
-			`Cannot use [chunkhash] or [contenthash] for chunk in '${path}' (use [hash] instead)`
+		// check that filename is data uri
+		const match = data.filename.match(/^data:([^;,]+)/);
+		if (match) {
+			const ext = getMimeTypes().extension(match[1]);
+			const emptyReplacer = replacer("", true);
+			// "XXXX" used for `updateHash`, so we don't need it here
+			const contentHash =
+				data.contentHash && !/X+/.test(data.contentHash)
+					? data.contentHash
+					: false;
+			const baseReplacer = contentHash ? replacer(contentHash) : emptyReplacer;
+
+			if (presentKinds.has("file")) replacements.set("file", emptyReplacer);
+			if (presentKinds.has("query")) replacements.set("query", emptyReplacer);
+			if (presentKinds.has("fragment")) {
+				replacements.set("fragment", emptyReplacer);
+			}
+			if (presentKinds.has("path")) replacements.set("path", emptyReplacer);
+			if (presentKinds.has("containedfile")) {
+				replacements.set("containedfile", emptyReplacer);
+			}
+			if (presentKinds.has("containedpath")) {
+				replacements.set("containedpath", emptyReplacer);
+			}
+			if (presentKinds.has("base")) replacements.set("base", baseReplacer);
+			if (presentKinds.has("name")) replacements.set("name", baseReplacer);
+			if (presentKinds.has("ext")) {
+				replacements.set("ext", replacer(ext ? `.${ext}` : "", true));
+			}
+			// Legacy
+			if (presentKinds.has("filebase")) {
+				replacements.set(
+					"filebase",
+					deprecated(
+						baseReplacer,
+						"[filebase] is now [base]",
+						"DEP_WEBPACK_TEMPLATE_PATH_PLUGIN_REPLACE_PATH_VARIABLES_FILENAME"
+					)
+				);
+			}
+		} else {
+			const { path: file, query, fragment } = parseResource(data.filename);
+
+			const ext = extname(file);
+			const base = basename(file);
+			const name = base.slice(0, base.length - ext.length);
+			const path = file.slice(0, file.length - base.length);
+
+			if (presentKinds.has("file")) replacements.set("file", replacer(file));
+			if (presentKinds.has("query")) {
+				replacements.set("query", replacer(query, true));
+			}
+			if (presentKinds.has("fragment")) {
+				replacements.set("fragment", replacer(fragment, true));
+			}
+			if (presentKinds.has("path")) {
+				replacements.set("path", replacer(path, true));
+			}
+			if (presentKinds.has("containedfile")) {
+				replacements.set("containedfile", replacer(toContainedPath(file)));
+			}
+			if (presentKinds.has("containedpath")) {
+				replacements.set(
+					"containedpath",
+					replacer(toContainedPath(path), true)
+				);
+			}
+			if (presentKinds.has("base")) replacements.set("base", replacer(base));
+			if (presentKinds.has("name")) replacements.set("name", replacer(name));
+			if (presentKinds.has("ext")) replacements.set("ext", replacer(ext, true));
+			// Legacy
+			if (presentKinds.has("filebase")) {
+				replacements.set(
+					"filebase",
+					deprecated(
+						replacer(base),
+						"[filebase] is now [base]",
+						"DEP_WEBPACK_TEMPLATE_PATH_PLUGIN_REPLACE_PATH_VARIABLES_FILENAME"
+					)
+				);
+			}
+		}
+	}
+
+	// Compilation context
+	//
+	// Placeholders
+	//
+	// [fullhash] - data.hash (3a4b5c6e7f)
+	//
+	// Legacy Placeholders
+	//
+	// [hash] - data.hash (3a4b5c6e7f)
+	if (data.hash && (presentKinds.has("fullhash") || presentKinds.has("hash"))) {
+		const hashReplacer = hashLength(
+			replacer(data.hash),
+			data.hashWithLength,
+			assetInfo,
+			"fullhash",
+			data.fullHashDigest || sourceDigest,
+			data.fullHash,
+			undefined,
+			data.hashWithDigest
 		);
+
+		if (presentKinds.has("fullhash")) {
+			replacements.set("fullhash", hashReplacer);
+		}
+
+		// Legacy — but a css-loader-style `[hash]` local ident is not deprecated
+		if (presentKinds.has("hash")) {
+			replacements.set(
+				"hash",
+				data.hashAsFullHash
+					? hashReplacer
+					: deprecated(
+							hashReplacer,
+							"[hash] is now [fullhash] (also consider using [chunkhash] or [contenthash], see documentation for details)",
+							"DEP_WEBPACK_TEMPLATE_PATH_PLUGIN_REPLACE_PATH_VARIABLES_HASH"
+						)
+			);
+		}
 	}
 
-	return (
-		path
-			.replace(
-				REGEXP_HASH,
-				withHashLength(getReplacer(data.hash), data.hashWithLength)
-			)
-			.replace(
-				REGEXP_CHUNKHASH,
-				withHashLength(getReplacer(chunkHash), chunkHashWithLength)
-			)
-			.replace(
-				REGEXP_CONTENTHASH,
-				withHashLength(getReplacer(contentHash), contentHashWithLength)
-			)
-			.replace(
-				REGEXP_MODULEHASH,
-				withHashLength(getReplacer(moduleHash), moduleHashWithLength)
-			)
-			.replace(REGEXP_ID, getReplacer(chunkId))
-			.replace(REGEXP_MODULEID, getReplacer(moduleId))
-			.replace(REGEXP_NAME, getReplacer(chunkName))
-			.replace(REGEXP_FILE, getReplacer(data.filename))
-			.replace(REGEXP_FILEBASE, getReplacer(data.basename))
-			// query is optional, it's OK if it's in a path but there's nothing to replace it with
-			.replace(REGEXP_QUERY, getReplacer(data.query, true))
-	);
-};
+	// Chunk Context
+	//
+	// Placeholders
+	//
+	// [id] - chunk.id (0.js)
+	// [name] - chunk.name (app.js)
+	// [chunkhash] - chunk.hash (7823t4t4.js)
+	// [contenthash] - chunk.contentHash[type] (3256u3zg.js)
+	if (data.chunk) {
+		const chunk = data.chunk;
 
-class TemplatedPathPlugin {
-	apply(compiler) {
-		compiler.hooks.compilation.tap("TemplatedPathPlugin", compilation => {
-			const mainTemplate = compilation.mainTemplate;
+		const contentHashType = data.contentHashType;
 
-			mainTemplate.hooks.assetPath.tap(
-				"TemplatedPathPlugin",
-				replacePathVariables
+		if (presentKinds.has("id")) replacements.set("id", replacer(chunk.id));
+		if (presentKinds.has("name")) {
+			replacements.set("name", replacer(chunk.name || chunk.id));
+		}
+		if (presentKinds.has("chunkhash")) {
+			replacements.set(
+				"chunkhash",
+				hashLength(
+					replacer(chunk instanceof Chunk ? chunk.renderedHash : chunk.hash),
+					"hashWithLength" in chunk ? chunk.hashWithLength : undefined,
+					assetInfo,
+					"chunkhash",
+					sourceDigest,
+					chunk.hash,
+					undefined,
+					"hashWithDigest" in chunk ? chunk.hashWithDigest : undefined
+				)
 			);
+		}
+		if (presentKinds.has("contenthash")) {
+			const ct = /** @type {string} */ (contentHashType);
+			replacements.set(
+				"contenthash",
+				hashLength(
+					replacer(
+						data.contentHash ||
+							(contentHashType &&
+								chunk.contentHash &&
+								chunk.contentHash[contentHashType])
+					),
+					data.contentHashWithLength ||
+						("contentHashWithLength" in chunk && chunk.contentHashWithLength
+							? chunk.contentHashWithLength[ct]
+							: undefined),
+					assetInfo,
+					"contenthash",
+					sourceDigest,
+					// full content digest, so a static `[contenthash:]` re-encodes
+					// from full entropy (the runtime path uses `contentHashWithDigest`)
+					"contentHashFull" in chunk && chunk.contentHashFull
+						? chunk.contentHashFull[ct]
+						: undefined,
+					data.realContentHash,
+					"contentHashWithDigest" in chunk && chunk.contentHashWithDigest
+						? chunk.contentHashWithDigest[ct]
+						: undefined
+				)
+			);
+		}
+	}
+
+	// Module Context
+	//
+	// Placeholders
+	//
+	// [id] - module.id (2.png)
+	// [hash] - module.hash (6237543873.png)
+	//
+	// Legacy Placeholders
+	//
+	// [moduleid] - module.id (2.png)
+	// [modulehash] - module.hash (6237543873.png)
+	if (data.module) {
+		const module = data.module;
+		const needId = presentKinds.has("id");
+		const needModuleId = presentKinds.has("moduleid");
+		// `data.hashAsFullHash` keeps `[hash]` as the `[fullhash]` alias (CSS local
+		// idents) instead of repurposing it to the module hash; `[modulehash]` stays.
+		const needHash = presentKinds.has("hash") && !data.hashAsFullHash;
 
-			mainTemplate.hooks.globalHash.tap(
-				"TemplatedPathPlugin",
-				(chunk, paths) => {
-					const outputOptions = mainTemplate.outputOptions;
-					const publicPath = outputOptions.publicPath || "";
-					const filename = outputOptions.filename || "";
-					const chunkFilename =
-						outputOptions.chunkFilename || outputOptions.filename;
-					if (
-						REGEXP_HASH_FOR_TEST.test(publicPath) ||
-						REGEXP_CHUNKHASH_FOR_TEST.test(publicPath) ||
-						REGEXP_CONTENTHASH_FOR_TEST.test(publicPath) ||
-						REGEXP_NAME_FOR_TEST.test(publicPath)
+		if (needId || needModuleId) {
+			const idReplacer = replacer(() =>
+				(data.prepareId || prepareId)(
+					module instanceof Module
+						? /** @type {ModuleId} */
+							(/** @type {ChunkGraph} */ (chunkGraph).getModuleId(module))
+						: module.id
+				)
+			);
+			if (needId) replacements.set("id", idReplacer);
+			// Legacy
+			if (needModuleId) {
+				replacements.set(
+					"moduleid",
+					deprecated(
+						idReplacer,
+						"[moduleid] is now [id]",
+						"DEP_WEBPACK_TEMPLATE_PATH_PLUGIN_REPLACE_PATH_VARIABLES_MODULE_ID"
 					)
-						return true;
-					if (REGEXP_HASH_FOR_TEST.test(filename)) return true;
-					if (REGEXP_HASH_FOR_TEST.test(chunkFilename)) return true;
-					if (REGEXP_HASH_FOR_TEST.test(paths.join("|"))) return true;
-				}
+				);
+			}
+		}
+
+		// `[hash]` aliases module content hash when present, else module hash.
+		const wantModuleHash =
+			presentKinds.has("modulehash") || (needHash && !data.contentHash);
+		const wantContentHash =
+			presentKinds.has("contenthash") || (needHash && data.contentHash);
+		/** @type {Replacer | undefined} */
+		let moduleHashReplacer;
+		/** @type {Replacer | undefined} */
+		let contentHashReplacer;
+		if (wantModuleHash) {
+			moduleHashReplacer = hashLength(
+				replacer(() =>
+					module instanceof Module
+						? /** @type {ChunkGraph} */
+							(chunkGraph).getRenderedModuleHash(module, data.runtime)
+						: module.hash
+				),
+				"hashWithLength" in module ? module.hashWithLength : undefined,
+				assetInfo,
+				"modulehash",
+				sourceDigest,
+				// `getModuleHash` is the untruncated digest (`getRenderedModuleHash` is
+				// the truncated one), so `[modulehash:]` re-encodes full entropy
+				module instanceof Module
+					? /** @type {ChunkGraph} */ (chunkGraph).getModuleHash(
+							module,
+							data.runtime
+						)
+					: module.hash
 			);
+			if (presentKinds.has("modulehash")) {
+				replacements.set("modulehash", moduleHashReplacer);
+			}
+		}
+		if (wantContentHash) {
+			contentHashReplacer = hashLength(
+				replacer(/** @type {string} */ (data.contentHash)),
+				undefined,
+				assetInfo,
+				"contenthash",
+				sourceDigest,
+				// full content digest, so a static `[contenthash:]` re-encodes
+				// from full entropy (asset modules supply it via `contentHashFull`)
+				data.contentHashFull,
+				data.realContentHash
+			);
+			if (presentKinds.has("contenthash")) {
+				replacements.set("contenthash", contentHashReplacer);
+			}
+		}
+		if (needHash) {
+			replacements.set(
+				"hash",
+				/** @type {Replacer} */
+				(data.contentHash ? contentHashReplacer : moduleHashReplacer)
+			);
+		}
+	}
 
-			mainTemplate.hooks.hashForChunk.tap(
-				"TemplatedPathPlugin",
-				(hash, chunk) => {
-					const outputOptions = mainTemplate.outputOptions;
-					const chunkFilename =
-						outputOptions.chunkFilename || outputOptions.filename;
-					if (REGEXP_CHUNKHASH_FOR_TEST.test(chunkFilename)) {
-						hash.update(JSON.stringify(chunk.getChunkMaps(true).hash));
-					}
-					if (REGEXP_CONTENTHASH_FOR_TEST.test(chunkFilename)) {
-						hash.update(
-							JSON.stringify(
-								chunk.getChunkMaps(true).contentHash.javascript || {}
-							)
-						);
-					}
-					if (REGEXP_NAME_FOR_TEST.test(chunkFilename)) {
-						hash.update(JSON.stringify(chunk.getChunkMaps(true).name));
-					}
-				}
+	// Other things
+	//
+	// Placeholders
+	//
+	// [url] - data.url
+	// [uniqueName] - data.uniqueName (output.uniqueName)
+	// [uniquename] - alias of [uniqueName]
+	if (data.url && presentKinds.has("url")) {
+		replacements.set("url", replacer(data.url));
+	}
+	if (
+		data.uniqueName !== undefined &&
+		(presentKinds.has("uniqueName") || presentKinds.has("uniquename"))
+	) {
+		const uniqueNameReplacer = replacer(data.uniqueName);
+		if (presentKinds.has("uniqueName")) {
+			replacements.set("uniqueName", uniqueNameReplacer);
+		}
+		if (presentKinds.has("uniquename")) {
+			replacements.set("uniquename", uniqueNameReplacer);
+		}
+	}
+	if (presentKinds.has("runtime")) {
+		if (typeof data.runtime === "string") {
+			replacements.set(
+				"runtime",
+				replacer(() =>
+					(data.prepareId || prepareId)(/** @type {string} */ (data.runtime))
+				)
 			);
+		} else {
+			replacements.set("runtime", replacer("_"));
+		}
+	}
+
+	path = path.replace(REGEXP, (match, content) => {
+		if (content.length + 2 === match.length) {
+			const contentMatch = /^(\w+)(?::(\w+))?(?::(\w+))?$/.exec(content);
+			if (!contentMatch) return match;
+			const [, kind, arg1, arg2] = contentMatch;
+			const replacer = replacements.get(kind);
+			if (replacer !== undefined) {
+				// `[kind:length]`, `[kind:digest]` or `[kind:digest:length]`.
+				/** @type {string | undefined} */
+				let digest;
+				/** @type {string | undefined} */
+				let length = arg1;
+				if (arg2 !== undefined) {
+					digest = arg1;
+					length = arg2;
+				} else if (arg1 !== undefined && !/^\d+$/.test(arg1)) {
+					digest = arg1;
+					length = undefined;
+				}
+				return replacer(match, length, /** @type {string} */ (path), digest);
+			}
+		} else if (match.startsWith("[\\") && match.endsWith("\\]")) {
+			return `[${match.slice(2, -2)}]`;
+		}
+		return match;
+	});
+
+	return path;
+};
+
+const plugin = "TemplatedPathPlugin";
+
+class TemplatedPathPlugin {
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
+	apply(compiler) {
+		compiler.hooks.compilation.tap(plugin, (compilation) => {
+			compilation.hooks.assetPath.tap(plugin, (path, data, assetInfo) => {
+				// Default from output options so `[uniqueName]` resolves in every template
+				if (data.uniqueName === undefined) {
+					data.uniqueName = compilation.outputOptions.uniqueName;
+				}
+				// Digest the stored hashes use, so `[hash:]` can re-encode them
+				if (data.hashDigest === undefined) {
+					data.hashDigest = compilation.outputOptions.hashDigest;
+				}
+				// Untruncated compilation hash, so `[fullhash:]` keeps full entropy
+				if (data.fullHash === undefined && data.hash === compilation.hash) {
+					data.fullHash = compilation.fullHash;
+				}
+				// `RealContentHashPlugin` rehashes content; flag it so an inline digest
+				// on `[contenthash]` is recorded and the recomputed hash re-encodes in it.
+				if (data.realContentHash === undefined) {
+					data.realContentHash = Boolean(
+						compilation.options.optimization.realContentHash
+					);
+				}
+				return interpolate(path, data, assetInfo);
+			});
 		});
 	}
 }
 
 module.exports = TemplatedPathPlugin;
+module.exports.getPresentKinds = getPresentKinds;
+module.exports.interpolate = interpolate;
+module.exports.reEncodeDigest = reEncodeDigest;
+module.exports.toContainedPath = toContainedPath;
+module.exports.toTemplateSourceFileName = toTemplateSourceFileName;
+module.exports.usesFullHashDigest = usesFullHashDigest;
diff --git a/lib/UmdMainTemplatePlugin.js b/lib/UmdMainTemplatePlugin.js
deleted file mode 100644
index 795ae278272..00000000000
--- a/lib/UmdMainTemplatePlugin.js
+++ /dev/null
@@ -1,304 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const { ConcatSource, OriginalSource } = require("webpack-sources");
-const Template = require("./Template");
-
-/** @typedef {import("./Compilation")} Compilation */
-
-/**
- * @param {string[]} accessor the accessor to convert to path
- * @returns {string} the path
- */
-const accessorToObjectAccess = accessor => {
-	return accessor.map(a => `[${JSON.stringify(a)}]`).join("");
-};
-
-/**
- * @param {string=} base the path prefix
- * @param {string|string[]} accessor the accessor
- * @param {string=} joinWith the element separator
- * @returns {string} the path
- */
-const accessorAccess = (base, accessor, joinWith = ", ") => {
-	const accessors = Array.isArray(accessor) ? accessor : [accessor];
-	return accessors
-		.map((_, idx) => {
-			const a = base
-				? base + accessorToObjectAccess(accessors.slice(0, idx + 1))
-				: accessors[0] + accessorToObjectAccess(accessors.slice(1, idx + 1));
-			if (idx === accessors.length - 1) return a;
-			if (idx === 0 && typeof base === "undefined")
-				return `${a} = typeof ${a} === "object" ? ${a} : {}`;
-			return `${a} = ${a} || {}`;
-		})
-		.join(joinWith);
-};
-
-/** @typedef {string | string[] | Record} UmdMainTemplatePluginName */
-
-/**
- * @typedef {Object} AuxiliaryCommentObject
- * @property {string} root
- * @property {string} commonjs
- * @property {string} commonjs2
- * @property {string} amd
- */
-
-/**
- * @typedef {Object} UmdMainTemplatePluginOption
- * @property {boolean=} optionalAmdExternalAsGlobal
- * @property {boolean} namedDefine
- * @property {string | AuxiliaryCommentObject} auxiliaryComment
- */
-
-class UmdMainTemplatePlugin {
-	/**
-	 * @param {UmdMainTemplatePluginName} name the name of the UMD library
-	 * @param {UmdMainTemplatePluginOption} options the plugin option
-	 */
-	constructor(name, options) {
-		if (typeof name === "object" && !Array.isArray(name)) {
-			this.name = name.root || name.amd || name.commonjs;
-			this.names = name;
-		} else {
-			this.name = name;
-			this.names = {
-				commonjs: name,
-				root: name,
-				amd: name
-			};
-		}
-		this.optionalAmdExternalAsGlobal = options.optionalAmdExternalAsGlobal;
-		this.namedDefine = options.namedDefine;
-		this.auxiliaryComment = options.auxiliaryComment;
-	}
-
-	/**
-	 * @param {Compilation} compilation the compilation instance
-	 * @returns {void}
-	 */
-	apply(compilation) {
-		const { mainTemplate, chunkTemplate, runtimeTemplate } = compilation;
-
-		const onRenderWithEntry = (source, chunk, hash) => {
-			let externals = chunk
-				.getModules()
-				.filter(
-					m =>
-						m.external &&
-						(m.externalType === "umd" || m.externalType === "umd2")
-				);
-			const optionalExternals = [];
-			let requiredExternals = [];
-			if (this.optionalAmdExternalAsGlobal) {
-				for (const m of externals) {
-					if (m.optional) {
-						optionalExternals.push(m);
-					} else {
-						requiredExternals.push(m);
-					}
-				}
-				externals = requiredExternals.concat(optionalExternals);
-			} else {
-				requiredExternals = externals;
-			}
-
-			function replaceKeys(str) {
-				return mainTemplate.getAssetPath(str, {
-					hash,
-					chunk
-				});
-			}
-
-			function externalsDepsArray(modules) {
-				return `[${replaceKeys(
-					modules
-						.map(m =>
-							JSON.stringify(
-								typeof m.request === "object" ? m.request.amd : m.request
-							)
-						)
-						.join(", ")
-				)}]`;
-			}
-
-			function externalsRootArray(modules) {
-				return replaceKeys(
-					modules
-						.map(m => {
-							let request = m.request;
-							if (typeof request === "object") request = request.root;
-							return `root${accessorToObjectAccess([].concat(request))}`;
-						})
-						.join(", ")
-				);
-			}
-
-			function externalsRequireArray(type) {
-				return replaceKeys(
-					externals
-						.map(m => {
-							let expr;
-							let request = m.request;
-							if (typeof request === "object") {
-								request = request[type];
-							}
-							if (typeof request === "undefined") {
-								throw new Error(
-									"Missing external configuration for type:" + type
-								);
-							}
-							if (Array.isArray(request)) {
-								expr = `require(${JSON.stringify(
-									request[0]
-								)})${accessorToObjectAccess(request.slice(1))}`;
-							} else {
-								expr = `require(${JSON.stringify(request)})`;
-							}
-							if (m.optional) {
-								expr = `(function webpackLoadOptionalExternalModule() { try { return ${expr}; } catch(e) {} }())`;
-							}
-							return expr;
-						})
-						.join(", ")
-				);
-			}
-
-			function externalsArguments(modules) {
-				return modules
-					.map(
-						m =>
-							`__WEBPACK_EXTERNAL_MODULE_${Template.toIdentifier(`${m.id}`)}__`
-					)
-					.join(", ");
-			}
-
-			function libraryName(library) {
-				return JSON.stringify(replaceKeys([].concat(library).pop()));
-			}
-
-			let amdFactory;
-			if (optionalExternals.length > 0) {
-				const wrapperArguments = externalsArguments(requiredExternals);
-				const factoryArguments =
-					requiredExternals.length > 0
-						? externalsArguments(requiredExternals) +
-						  ", " +
-						  externalsRootArray(optionalExternals)
-						: externalsRootArray(optionalExternals);
-				amdFactory =
-					`function webpackLoadOptionalExternalModuleAmd(${wrapperArguments}) {\n` +
-					`			return factory(${factoryArguments});\n` +
-					"		}";
-			} else {
-				amdFactory = "factory";
-			}
-
-			const auxiliaryComment = this.auxiliaryComment;
-
-			const getAuxilaryComment = type => {
-				if (auxiliaryComment) {
-					if (typeof auxiliaryComment === "string")
-						return "\t//" + auxiliaryComment + "\n";
-					if (auxiliaryComment[type])
-						return "\t//" + auxiliaryComment[type] + "\n";
-				}
-				return "";
-			};
-
-			return new ConcatSource(
-				new OriginalSource(
-					"(function webpackUniversalModuleDefinition(root, factory) {\n" +
-						getAuxilaryComment("commonjs2") +
-						"	if(typeof exports === 'object' && typeof module === 'object')\n" +
-						"		module.exports = factory(" +
-						externalsRequireArray("commonjs2") +
-						");\n" +
-						getAuxilaryComment("amd") +
-						"	else if(typeof define === 'function' && define.amd)\n" +
-						(requiredExternals.length > 0
-							? this.names.amd && this.namedDefine === true
-								? "		define(" +
-								  libraryName(this.names.amd) +
-								  ", " +
-								  externalsDepsArray(requiredExternals) +
-								  ", " +
-								  amdFactory +
-								  ");\n"
-								: "		define(" +
-								  externalsDepsArray(requiredExternals) +
-								  ", " +
-								  amdFactory +
-								  ");\n"
-							: this.names.amd && this.namedDefine === true
-								? "		define(" +
-								  libraryName(this.names.amd) +
-								  ", [], " +
-								  amdFactory +
-								  ");\n"
-								: "		define([], " + amdFactory + ");\n") +
-						(this.names.root || this.names.commonjs
-							? getAuxilaryComment("commonjs") +
-							  "	else if(typeof exports === 'object')\n" +
-							  "		exports[" +
-							  libraryName(this.names.commonjs || this.names.root) +
-							  "] = factory(" +
-							  externalsRequireArray("commonjs") +
-							  ");\n" +
-							  getAuxilaryComment("root") +
-							  "	else\n" +
-							  "		" +
-							  replaceKeys(
-									accessorAccess("root", this.names.root || this.names.commonjs)
-							  ) +
-							  " = factory(" +
-							  externalsRootArray(externals) +
-							  ");\n"
-							: "	else {\n" +
-							  (externals.length > 0
-									? "		var a = typeof exports === 'object' ? factory(" +
-									  externalsRequireArray("commonjs") +
-									  ") : factory(" +
-									  externalsRootArray(externals) +
-									  ");\n"
-									: "		var a = factory();\n") +
-							  "		for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i];\n" +
-							  "	}\n") +
-						`})(${
-							runtimeTemplate.outputOptions.globalObject
-						}, function(${externalsArguments(externals)}) {\nreturn `,
-					"webpack/universalModuleDefinition"
-				),
-				source,
-				";\n})"
-			);
-		};
-
-		for (const template of [mainTemplate, chunkTemplate]) {
-			template.hooks.renderWithEntry.tap(
-				"UmdMainTemplatePlugin",
-				onRenderWithEntry
-			);
-		}
-
-		mainTemplate.hooks.globalHashPaths.tap("UmdMainTemplatePlugin", paths => {
-			if (this.names.root) paths = paths.concat(this.names.root);
-			if (this.names.amd) paths = paths.concat(this.names.amd);
-			if (this.names.commonjs) paths = paths.concat(this.names.commonjs);
-			return paths;
-		});
-
-		mainTemplate.hooks.hash.tap("UmdMainTemplatePlugin", hash => {
-			hash.update("umd");
-			hash.update(`${this.names.root}`);
-			hash.update(`${this.names.amd}`);
-			hash.update(`${this.names.commonjs}`);
-		});
-	}
-}
-
-module.exports = UmdMainTemplatePlugin;
diff --git a/lib/UnsupportedFeatureWarning.js b/lib/UnsupportedFeatureWarning.js
deleted file mode 100644
index 284429ea94d..00000000000
--- a/lib/UnsupportedFeatureWarning.js
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const WebpackError = require("./WebpackError");
-
-class UnsupportedFeatureWarning extends WebpackError {
-	constructor(module, message, loc) {
-		super(message);
-
-		this.name = "UnsupportedFeatureWarning";
-		this.module = module;
-		this.loc = loc;
-		this.hideStack = true;
-
-		Error.captureStackTrace(this, this.constructor);
-	}
-}
-
-module.exports = UnsupportedFeatureWarning;
diff --git a/lib/UseStrictPlugin.js b/lib/UseStrictPlugin.js
index 175c6028350..338f10a577e 100644
--- a/lib/UseStrictPlugin.js
+++ b/lib/UseStrictPlugin.js
@@ -2,23 +2,42 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
+const {
+	JAVASCRIPT_MODULE_TYPE_AUTO,
+	JAVASCRIPT_MODULE_TYPE_DYNAMIC,
+	JAVASCRIPT_MODULE_TYPE_ESM
+} = require("./ModuleTypeConstants");
 const ConstDependency = require("./dependencies/ConstDependency");
 
-/** @typedef {import("./Compiler.js")} Compiler */
+/** @typedef {import("../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */
+/** @typedef {import("./Module").BuildInfo} BuildInfo */
+/** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */
+/** @typedef {import("./javascript/JavascriptParser").Range} Range */
+
+const PLUGIN_NAME = "UseStrictPlugin";
 
 class UseStrictPlugin {
 	/**
-	 * @param {Compiler} compiler Webpack Compiler
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
 	 * @returns {void}
 	 */
 	apply(compiler) {
 		compiler.hooks.compilation.tap(
-			"UseStrictPlugin",
+			PLUGIN_NAME,
 			(compilation, { normalModuleFactory }) => {
-				const handler = parser => {
-					parser.hooks.program.tap("UseStrictPlugin", ast => {
+				/**
+				 * Handles the hook callback for this code path.
+				 * @param {JavascriptParser} parser the parser
+				 * @param {JavascriptParserOptions} parserOptions the javascript parser options
+				 */
+				const handler = (parser, parserOptions) => {
+					parser.hooks.program.tap(PLUGIN_NAME, (ast) => {
 						const firstNode = ast.body[0];
 						if (
 							firstNode &&
@@ -29,23 +48,32 @@ class UseStrictPlugin {
 							// Remove "use strict" expression. It will be added later by the renderer again.
 							// This is necessary in order to not break the strict mode when webpack prepends code.
 							// @see https://github.com/webpack/webpack/issues/1970
-							const dep = new ConstDependency("", firstNode.range);
-							dep.loc = firstNode.loc;
-							parser.state.current.addDependency(dep);
-							parser.state.module.buildInfo.strict = true;
+							const dep = new ConstDependency(
+								"",
+								/** @type {Range} */ (firstNode.range)
+							);
+							dep.loc = parser.getLocation(firstNode);
+							parser.state.module.addPresentationalDependency(dep);
+							/** @type {BuildInfo} */
+							(parser.state.module.buildInfo).strict = true;
+						}
+						if (parserOptions.overrideStrict) {
+							/** @type {BuildInfo} */
+							(parser.state.module.buildInfo).strict =
+								parserOptions.overrideStrict === "strict";
 						}
 					});
 				};
 
 				normalModuleFactory.hooks.parser
-					.for("javascript/auto")
-					.tap("UseStrictPlugin", handler);
+					.for(JAVASCRIPT_MODULE_TYPE_AUTO)
+					.tap(PLUGIN_NAME, handler);
 				normalModuleFactory.hooks.parser
-					.for("javascript/dynamic")
-					.tap("UseStrictPlugin", handler);
+					.for(JAVASCRIPT_MODULE_TYPE_DYNAMIC)
+					.tap(PLUGIN_NAME, handler);
 				normalModuleFactory.hooks.parser
-					.for("javascript/esm")
-					.tap("UseStrictPlugin", handler);
+					.for(JAVASCRIPT_MODULE_TYPE_ESM)
+					.tap(PLUGIN_NAME, handler);
 			}
 		);
 	}
diff --git a/lib/WarnCaseSensitiveModulesPlugin.js b/lib/WarnCaseSensitiveModulesPlugin.js
index 867a33fe31e..40157f7f071 100644
--- a/lib/WarnCaseSensitiveModulesPlugin.js
+++ b/lib/WarnCaseSensitiveModulesPlugin.js
@@ -2,35 +2,131 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
-const CaseSensitiveModulesWarning = require("./CaseSensitiveModulesWarning");
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./Module")} Module */
+/** @typedef {import("./ModuleGraph")} ModuleGraph */
+/** @typedef {import("./NormalModule")} NormalModule */
+
+const WebpackError = require("./errors/WebpackError");
+
+/**
+ * Sorts the conflicting modules by identifier to keep warning output stable.
+ * @param {Module[]} modules the modules to be sorted
+ * @returns {Module[]} sorted version of original modules
+ */
+const sortModules = (modules) =>
+	modules.sort((a, b) => {
+		const aIdent = a.identifier();
+		const bIdent = b.identifier();
+		/* istanbul ignore next */
+		if (aIdent < bIdent) return -1;
+		/* istanbul ignore next */
+		if (aIdent > bIdent) return 1;
+		/* istanbul ignore next */
+		return 0;
+	});
+
+/**
+ * Formats the conflicting modules and one representative incoming reason for
+ * each module into the warning body.
+ * @param {Module[]} modules each module from throw
+ * @param {ModuleGraph} moduleGraph the module graph
+ * @returns {string} each message from provided modules
+ */
+const createModulesListMessage = (modules, moduleGraph) =>
+	modules
+		.map((m) => {
+			let message = `* ${m.identifier()}`;
+			const validReasons = [
+				...moduleGraph.getIncomingConnectionsByOriginModule(m).keys()
+			].filter(Boolean);
+
+			if (validReasons.length > 0) {
+				message += `\n    Used by ${validReasons.length} module(s), i. e.`;
+				message += `\n    ${
+					/** @type {Module[]} */ (validReasons)[0].identifier()
+				}`;
+			}
+			return message;
+		})
+		.join("\n");
+
+/**
+ * Warning emitted when webpack finds modules whose identifiers differ only by
+ * letter casing, which can behave inconsistently across filesystems.
+ */
+class CaseSensitiveModulesWarning extends WebpackError {
+	/**
+	 * Builds a warning message that lists the case-conflicting modules and
+	 * representative importers that caused them to be included.
+	 * @param {Iterable} modules modules that were detected
+	 * @param {ModuleGraph} moduleGraph the module graph
+	 */
+	constructor(modules, moduleGraph) {
+		const sortedModules = sortModules([...modules]);
+		const modulesList = createModulesListMessage(sortedModules, moduleGraph);
+		super(`There are multiple modules with names that only differ in casing.
+This can lead to unexpected behavior when compiling on a filesystem with other case-semantic.
+Use equal casing. Compare these module identifiers:
+${modulesList}`);
+
+		/** @type {string} */
+		this.name = "CaseSensitiveModulesWarning";
+		/** @type {Module} */
+		this.module = sortedModules[0];
+	}
+}
+
+const PLUGIN_NAME = "WarnCaseSensitiveModulesPlugin";
 
 class WarnCaseSensitiveModulesPlugin {
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
 	apply(compiler) {
-		compiler.hooks.compilation.tap(
-			"WarnCaseSensitiveModulesPlugin",
-			compilation => {
-				compilation.hooks.seal.tap("WarnCaseSensitiveModulesPlugin", () => {
-					const moduleWithoutCase = new Map();
-					for (const module of compilation.modules) {
-						const identifier = module.identifier().toLowerCase();
-						const array = moduleWithoutCase.get(identifier);
-						if (array) {
-							array.push(module);
-						} else {
-							moduleWithoutCase.set(identifier, [module]);
-						}
+		compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
+			compilation.hooks.seal.tap(PLUGIN_NAME, () => {
+				/** @type {Map>} */
+				const moduleWithoutCase = new Map();
+				for (const module of compilation.modules) {
+					const identifier = module.identifier();
+
+					// Ignore `data:` URLs, because it's not a real path
+					if (
+						/** @type {NormalModule} */
+						(module).resourceResolveData !== undefined &&
+						/** @type {NormalModule} */
+						(module).resourceResolveData.encodedContent !== undefined
+					) {
+						continue;
 					}
-					for (const pair of moduleWithoutCase) {
-						const array = pair[1];
-						if (array.length > 1) {
-							compilation.warnings.push(new CaseSensitiveModulesWarning(array));
-						}
+
+					const lowerIdentifier = identifier.toLowerCase();
+					let map = moduleWithoutCase.get(lowerIdentifier);
+					if (map === undefined) {
+						map = new Map();
+						moduleWithoutCase.set(lowerIdentifier, map);
 					}
-				});
-			}
-		);
+					map.set(identifier, module);
+				}
+				for (const pair of moduleWithoutCase) {
+					const map = pair[1];
+					if (map.size > 1) {
+						compilation.warnings.push(
+							new CaseSensitiveModulesWarning(
+								map.values(),
+								compilation.moduleGraph
+							)
+						);
+					}
+				}
+			});
+		});
 	}
 }
 
diff --git a/lib/WarnDeprecatedOptionPlugin.js b/lib/WarnDeprecatedOptionPlugin.js
new file mode 100644
index 00000000000..5f19e3cdc7f
--- /dev/null
+++ b/lib/WarnDeprecatedOptionPlugin.js
@@ -0,0 +1,64 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Florent Cailhol @ooflorent
+*/
+
+"use strict";
+
+const WebpackError = require("./errors/WebpackError");
+
+/** @typedef {import("./Compiler")} Compiler */
+
+const PLUGIN_NAME = "WarnDeprecatedOptionPlugin";
+
+class WarnDeprecatedOptionPlugin {
+	/**
+	 * Create an instance of the plugin
+	 * @param {string} option the target option
+	 * @param {string | number} value the deprecated option value
+	 * @param {string} suggestion the suggestion replacement
+	 */
+	constructor(option, value, suggestion) {
+		/** @type {string} */
+		this.option = option;
+		/** @type {string | number} */
+		this.value = value;
+		/** @type {string} */
+		this.suggestion = suggestion;
+	}
+
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
+	apply(compiler) {
+		compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
+			compilation.warnings.push(
+				new DeprecatedOptionWarning(this.option, this.value, this.suggestion)
+			);
+		});
+	}
+}
+
+class DeprecatedOptionWarning extends WebpackError {
+	/**
+	 * Create an instance deprecated option warning
+	 * @param {string} option the target option
+	 * @param {string | number} value the deprecated option value
+	 * @param {string} suggestion the suggestion replacement
+	 */
+	constructor(option, value, suggestion) {
+		super();
+
+		/** @type {string} */
+		this.name = "DeprecatedOptionWarning";
+		/** @type {string} */
+		this.message =
+			"configuration\n" +
+			`The value '${value}' for option '${option}' is deprecated. ` +
+			`Use '${suggestion}' instead.`;
+	}
+}
+
+module.exports = WarnDeprecatedOptionPlugin;
diff --git a/lib/WarnNoModeSetPlugin.js b/lib/WarnNoModeSetPlugin.js
index 4a69a8cca4b..0cfe99e54d6 100644
--- a/lib/WarnNoModeSetPlugin.js
+++ b/lib/WarnNoModeSetPlugin.js
@@ -2,13 +2,39 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
-const NoModeWarning = require("./NoModeWarning");
+const WebpackError = require("./errors/WebpackError");
+
+class NoModeWarning extends WebpackError {
+	constructor() {
+		super();
+
+		/** @type {string} */
+		this.name = "NoModeWarning";
+		/** @type {string} */
+		this.message =
+			"configuration\n" +
+			"The 'mode' option has not been set, webpack will fallback to 'production' for this value.\n" +
+			"Set 'mode' option to 'development' or 'production' to enable defaults for each environment.\n" +
+			"You can also set it to 'none' to disable any default behavior. " +
+			"Learn more: https://webpack.js.org/configuration/mode/";
+	}
+}
+
+/** @typedef {import("./Compiler")} Compiler */
+
+const PLUGIN_NAME = "WarnNoModeSetPlugin";
 
 class WarnNoModeSetPlugin {
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
 	apply(compiler) {
-		compiler.hooks.thisCompilation.tap("WarnNoModeSetPlugin", compilation => {
+		compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
 			compilation.warnings.push(new NoModeWarning());
 		});
 	}
diff --git a/lib/WatchIgnorePlugin.js b/lib/WatchIgnorePlugin.js
index 7a00e998f05..14cc4705ee3 100644
--- a/lib/WatchIgnorePlugin.js
+++ b/lib/WatchIgnorePlugin.js
@@ -2,77 +2,81 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
-"use strict";
 
-const validateOptions = require("schema-utils");
-const schema = require("../schemas/plugins/WatchIgnorePlugin.json");
+"use strict";
 
-class WatchIgnorePlugin {
-	constructor(paths) {
-		validateOptions(schema, paths, "Watch Ignore Plugin");
-		this.paths = paths;
-	}
+const { groupBy } = require("./util/ArrayHelpers");
 
-	apply(compiler) {
-		compiler.hooks.afterEnvironment.tap("WatchIgnorePlugin", () => {
-			compiler.watchFileSystem = new IgnoringWatchFileSystem(
-				compiler.watchFileSystem,
-				this.paths
-			);
-		});
-	}
-}
+/** @typedef {import("watchpack").TimeInfoEntries} TimeInfoEntries */
+/** @typedef {import("../declarations/plugins/WatchIgnorePlugin").WatchIgnorePluginOptions} WatchIgnorePluginOptions */
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./util/fs").WatchFileSystem} WatchFileSystem */
+/** @typedef {import("./util/fs").WatchMethod} WatchMethod */
+/** @typedef {import("./util/fs").Watcher} Watcher */
 
-module.exports = WatchIgnorePlugin;
+const IGNORE_TIME_ENTRY = "ignore";
 
 class IgnoringWatchFileSystem {
+	/**
+	 * Creates an instance of IgnoringWatchFileSystem.
+	 * @param {WatchFileSystem} wfs original file system
+	 * @param {WatchIgnorePluginOptions["paths"]} paths ignored paths
+	 */
 	constructor(wfs, paths) {
+		/** @type {WatchFileSystem} */
 		this.wfs = wfs;
 		this.paths = paths;
 	}
 
+	/** @type {WatchMethod} */
 	watch(files, dirs, missing, startTime, options, callback, callbackUndelayed) {
-		const ignored = path =>
-			this.paths.some(
-				p => (p instanceof RegExp ? p.test(path) : path.indexOf(p) === 0)
+		files = [...files];
+		dirs = [...dirs];
+		/**
+		 * Returns true, if path is ignored.
+		 * @param {string} path path to check
+		 * @returns {boolean} true, if path is ignored
+		 */
+		const ignored = (path) =>
+			this.paths.some((p) =>
+				p instanceof RegExp ? p.test(path) : path.indexOf(p) === 0
 			);
 
-		const notIgnored = path => !ignored(path);
-
-		const ignoredFiles = files.filter(ignored);
-		const ignoredDirs = dirs.filter(ignored);
+		const [ignoredFiles, notIgnoredFiles] = groupBy(
+			/** @type {string[]} */
+			(files),
+			ignored
+		);
+		const [ignoredDirs, notIgnoredDirs] = groupBy(
+			/** @type {string[]} */
+			(dirs),
+			ignored
+		);
 
 		const watcher = this.wfs.watch(
-			files.filter(notIgnored),
-			dirs.filter(notIgnored),
+			notIgnoredFiles,
+			notIgnoredDirs,
 			missing,
 			startTime,
 			options,
-			(
-				err,
-				filesModified,
-				dirsModified,
-				missingModified,
-				fileTimestamps,
-				dirTimestamps
-			) => {
+			(err, fileTimestamps, dirTimestamps, changedFiles, removedFiles) => {
 				if (err) return callback(err);
-
 				for (const path of ignoredFiles) {
-					fileTimestamps.set(path, 1);
+					/** @type {TimeInfoEntries} */
+					(fileTimestamps).set(path, IGNORE_TIME_ENTRY);
 				}
 
 				for (const path of ignoredDirs) {
-					dirTimestamps.set(path, 1);
+					/** @type {TimeInfoEntries} */
+					(dirTimestamps).set(path, IGNORE_TIME_ENTRY);
 				}
 
 				callback(
-					err,
-					filesModified,
-					dirsModified,
-					missingModified,
+					null,
 					fileTimestamps,
-					dirTimestamps
+					dirTimestamps,
+					changedFiles,
+					removedFiles
 				);
 			},
 			callbackUndelayed
@@ -81,20 +85,77 @@ class IgnoringWatchFileSystem {
 		return {
 			close: () => watcher.close(),
 			pause: () => watcher.pause(),
-			getContextTimestamps: () => {
-				const dirTimestamps = watcher.getContextTimestamps();
+			getContextTimeInfoEntries: () => {
+				const dirTimestamps = watcher.getContextTimeInfoEntries();
 				for (const path of ignoredDirs) {
-					dirTimestamps.set(path, 1);
+					dirTimestamps.set(path, IGNORE_TIME_ENTRY);
 				}
 				return dirTimestamps;
 			},
-			getFileTimestamps: () => {
-				const fileTimestamps = watcher.getFileTimestamps();
+			getFileTimeInfoEntries: () => {
+				const fileTimestamps = watcher.getFileTimeInfoEntries();
 				for (const path of ignoredFiles) {
-					fileTimestamps.set(path, 1);
+					fileTimestamps.set(path, IGNORE_TIME_ENTRY);
 				}
 				return fileTimestamps;
-			}
+			},
+			getInfo:
+				watcher.getInfo &&
+				(() => {
+					const info =
+						/** @type {NonNullable} */
+						(watcher.getInfo)();
+					const { fileTimeInfoEntries, contextTimeInfoEntries } = info;
+					for (const path of ignoredFiles) {
+						fileTimeInfoEntries.set(path, IGNORE_TIME_ENTRY);
+					}
+					for (const path of ignoredDirs) {
+						contextTimeInfoEntries.set(path, IGNORE_TIME_ENTRY);
+					}
+					return info;
+				})
 		};
 	}
 }
+
+const PLUGIN_NAME = "WatchIgnorePlugin";
+
+class WatchIgnorePlugin {
+	/**
+	 * Creates an instance of WatchIgnorePlugin.
+	 * @param {WatchIgnorePluginOptions} options options
+	 */
+	constructor(options) {
+		/** @type {WatchIgnorePluginOptions} */
+		this.options = options;
+	}
+
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
+	apply(compiler) {
+		compiler.hooks.validate.tap(PLUGIN_NAME, () => {
+			compiler.validate(
+				() => require("../schemas/plugins/WatchIgnorePlugin.json"),
+				this.options,
+				{
+					name: "Watch Ignore Plugin",
+					baseDataPath: "options"
+				},
+				(options) =>
+					require("../schemas/plugins/WatchIgnorePlugin.check")(options)
+			);
+		});
+		compiler.hooks.afterEnvironment.tap(PLUGIN_NAME, () => {
+			compiler.watchFileSystem = new IgnoringWatchFileSystem(
+				/** @type {WatchFileSystem} */
+				(compiler.watchFileSystem),
+				this.options.paths
+			);
+		});
+	}
+}
+
+module.exports = WatchIgnorePlugin;
diff --git a/lib/Watching.js b/lib/Watching.js
index 92e682146f2..be78777f003 100644
--- a/lib/Watching.js
+++ b/lib/Watching.js
@@ -2,175 +2,532 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
 const Stats = require("./Stats");
 
+/** @typedef {import("../declarations/WebpackOptions").WatchOptions} WatchOptions */
+/** @typedef {import("./Compilation")} Compilation */
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./Compiler").ErrorCallback} ErrorCallback */
+/** @typedef {import("./logging/Logger").Logger} Logger */
+/** @typedef {import("./util/fs").TimeInfoEntries} TimeInfoEntries */
+/** @typedef {import("./util/fs").WatchFileSystem} WatchFileSystem */
+/** @typedef {import("./util/fs").Watcher} Watcher */
+
+/**
+ * Defines the callback type used by this module.
+ * @template T
+ * @template [R=void]
+ * @typedef {import("./webpack").Callback} Callback
+ */
+
+/** @typedef {Set} CollectedFiles */
+
 class Watching {
+	/**
+	 * Creates an instance of Watching.
+	 * @param {Compiler} compiler the compiler
+	 * @param {WatchOptions} watchOptions options
+	 * @param {Callback} handler completion handler
+	 */
 	constructor(compiler, watchOptions, handler) {
+		/** @type {null | number} */
 		this.startTime = null;
+		/** @type {boolean} */
 		this.invalid = false;
+		/** @type {Callback} */
 		this.handler = handler;
+		/** @type {ErrorCallback[]} */
 		this.callbacks = [];
+		/** @type {ErrorCallback[] | undefined} */
+		this._closeCallbacks = undefined;
+		/** @type {boolean} */
 		this.closed = false;
+		/** @type {boolean} */
+		this.suspended = false;
+		/** @type {boolean} */
+		this.blocked = false;
+		this._isBlocked = () => false;
+		this._onChange = () => {};
+		this._onInvalid = () => {};
 		if (typeof watchOptions === "number") {
+			/** @type {WatchOptions} */
 			this.watchOptions = {
 				aggregateTimeout: watchOptions
 			};
 		} else if (watchOptions && typeof watchOptions === "object") {
-			this.watchOptions = Object.assign({}, watchOptions);
+			/** @type {WatchOptions} */
+			this.watchOptions = { ...watchOptions };
 		} else {
+			/** @type {WatchOptions} */
 			this.watchOptions = {};
 		}
-		this.watchOptions.aggregateTimeout =
-			this.watchOptions.aggregateTimeout || 200;
+		if (typeof this.watchOptions.aggregateTimeout !== "number") {
+			this.watchOptions.aggregateTimeout = 20;
+		}
+		/** @type {Compiler} */
 		this.compiler = compiler;
-		this.running = true;
-		this.compiler.readRecords(err => {
-			if (err) return this._done(err);
-
-			this._go();
+		/** @type {boolean} */
+		this.running = false;
+		/** @type {boolean} */
+		this._initial = true;
+		/** @type {boolean} */
+		this._invalidReported = true;
+		/** @type {boolean} */
+		this._needRecords = true;
+		/** @type {undefined | null | Watcher} */
+		this.watcher = undefined;
+		/** @type {undefined | null | Watcher} */
+		this.pausedWatcher = undefined;
+		/** @type {CollectedFiles | undefined} */
+		this._collectedChangedFiles = undefined;
+		/** @type {CollectedFiles | undefined} */
+		this._collectedRemovedFiles = undefined;
+		this._done = this._done.bind(this);
+		process.nextTick(() => {
+			if (this._initial) this._invalidate();
 		});
 	}
 
-	_go() {
-		this.startTime = Date.now();
+	/**
+	 * Merge with collected.
+	 * @param {ReadonlySet | undefined | null} changedFiles changed files
+	 * @param {ReadonlySet | undefined | null} removedFiles removed files
+	 */
+	_mergeWithCollected(changedFiles, removedFiles) {
+		if (!changedFiles) return;
+		if (!this._collectedChangedFiles) {
+			this._collectedChangedFiles = new Set(changedFiles);
+			this._collectedRemovedFiles = new Set(removedFiles);
+		} else {
+			for (const file of changedFiles) {
+				this._collectedChangedFiles.add(file);
+				/** @type {CollectedFiles} */
+				(this._collectedRemovedFiles).delete(file);
+			}
+			for (const file of /** @type {ReadonlySet} */ (removedFiles)) {
+				this._collectedChangedFiles.delete(file);
+				/** @type {CollectedFiles} */
+				(this._collectedRemovedFiles).add(file);
+			}
+		}
+	}
+
+	/**
+	 * Processes the provided file time info entries.
+	 * @param {TimeInfoEntries=} fileTimeInfoEntries info for files
+	 * @param {TimeInfoEntries=} contextTimeInfoEntries info for directories
+	 * @param {ReadonlySet=} changedFiles changed files
+	 * @param {ReadonlySet=} removedFiles removed files
+	 * @returns {void}
+	 */
+	_go(fileTimeInfoEntries, contextTimeInfoEntries, changedFiles, removedFiles) {
+		this._initial = false;
+		if (this.startTime === null) this.startTime = Date.now();
+		// Whatever held this back let go — a build is starting.
+		this.blocked = false;
 		this.running = true;
-		this.invalid = false;
-		this.compiler.hooks.watchRun.callAsync(this.compiler, err => {
-			if (err) return this._done(err);
-			const onCompiled = (err, compilation) => {
+		if (this.watcher) {
+			this.pausedWatcher = this.watcher;
+			this.lastWatcherStartTime = Date.now();
+			this.watcher.pause();
+			this.watcher = null;
+		} else if (!this.lastWatcherStartTime) {
+			this.lastWatcherStartTime = Date.now();
+		}
+		this.compiler.fsStartTime = Date.now();
+		if (
+			changedFiles &&
+			removedFiles &&
+			fileTimeInfoEntries &&
+			contextTimeInfoEntries
+		) {
+			this._mergeWithCollected(changedFiles, removedFiles);
+			this.compiler.fileTimestamps = fileTimeInfoEntries;
+			this.compiler.contextTimestamps = contextTimeInfoEntries;
+		} else if (this.pausedWatcher) {
+			if (this.pausedWatcher.getInfo) {
+				const {
+					changes,
+					removals,
+					fileTimeInfoEntries,
+					contextTimeInfoEntries
+				} = this.pausedWatcher.getInfo();
+				this._mergeWithCollected(changes, removals);
+				this.compiler.fileTimestamps = fileTimeInfoEntries;
+				this.compiler.contextTimestamps = contextTimeInfoEntries;
+			} else {
+				this._mergeWithCollected(
+					this.pausedWatcher.getAggregatedChanges &&
+						this.pausedWatcher.getAggregatedChanges(),
+					this.pausedWatcher.getAggregatedRemovals &&
+						this.pausedWatcher.getAggregatedRemovals()
+				);
+				this.compiler.fileTimestamps =
+					this.pausedWatcher.getFileTimeInfoEntries();
+				this.compiler.contextTimestamps =
+					this.pausedWatcher.getContextTimeInfoEntries();
+			}
+		}
+		this.compiler.modifiedFiles = this._collectedChangedFiles;
+		this._collectedChangedFiles = undefined;
+		this.compiler.removedFiles = this._collectedRemovedFiles;
+		this._collectedRemovedFiles = undefined;
+
+		const run = () => {
+			if (this.compiler.idle) {
+				return this.compiler.cache.endIdle((err) => {
+					if (err) return this._done(err);
+					this.compiler.idle = false;
+					run();
+				});
+			}
+			if (this._needRecords) {
+				return this.compiler.readRecords((err) => {
+					if (err) return this._done(err);
+
+					this._needRecords = false;
+					run();
+				});
+			}
+			this.invalid = false;
+			this._invalidReported = false;
+			this.compiler.hooks.watchRun.callAsync(this.compiler, (err) => {
 				if (err) return this._done(err);
-				if (this.invalid) return this._done();
+				/**
+				 * Processes the provided err.
+				 * @param {Error | null} err error
+				 * @param {Compilation=} _compilation compilation
+				 * @returns {void}
+				 */
+				const onCompiled = (err, _compilation) => {
+					if (err) return this._done(err, _compilation);
 
-				if (this.compiler.hooks.shouldEmit.call(compilation) === false) {
-					return this._done(null, compilation);
-				}
+					const compilation = /** @type {Compilation} */ (_compilation);
 
-				this.compiler.emitAssets(compilation, err => {
-					if (err) return this._done(err);
-					if (this.invalid) return this._done();
+					if (this.compiler.hooks.shouldEmit.call(compilation) === false) {
+						return this._done(null, compilation);
+					}
+
+					process.nextTick(() => {
+						const logger = compilation.getLogger("webpack.Compiler");
+						logger.time("emitAssets");
+						this.compiler.emitAssets(compilation, (err) => {
+							logger.timeEnd("emitAssets");
+							if (err) return this._done(err, compilation);
+							if (this.invalid) return this._done(null, compilation);
 
-					this.compiler.emitRecords(err => {
-						if (err) return this._done(err);
+							logger.time("emitRecords");
+							this.compiler.emitRecords((err) => {
+								logger.timeEnd("emitRecords");
+								if (err) return this._done(err, compilation);
 
-						if (compilation.hooks.needAdditionalPass.call()) {
-							compilation.needAdditionalPass = true;
+								if (compilation.hooks.needAdditionalPass.call()) {
+									compilation.needAdditionalPass = true;
 
-							const stats = new Stats(compilation);
-							stats.startTime = this.startTime;
-							stats.endTime = Date.now();
-							this.compiler.hooks.done.callAsync(stats, err => {
-								if (err) return this._done(err);
+									compilation.startTime = /** @type {number} */ (
+										this.startTime
+									);
+									compilation.endTime = Date.now();
+									logger.time("done hook");
+									const stats = new Stats(compilation);
+									this.compiler.hooks.done.callAsync(stats, (err) => {
+										logger.timeEnd("done hook");
+										if (err) return this._done(err, compilation);
 
-								this.compiler.hooks.additionalPass.callAsync(err => {
-									if (err) return this._done(err);
-									this.compiler.compile(onCompiled);
-								});
+										this.compiler.hooks.additionalPass.callAsync((err) => {
+											if (err) return this._done(err, compilation);
+											this.compiler.compile(onCompiled);
+										});
+									});
+									return;
+								}
+								return this._done(null, compilation);
 							});
-							return;
-						}
-						return this._done(null, compilation);
+						});
 					});
-				});
-			};
-			this.compiler.compile(onCompiled);
-		});
+				};
+				this.compiler.compile(onCompiled);
+			});
+		};
+
+		run();
 	}
 
+	/**
+	 * Returns the compilation stats.
+	 * @param {Compilation} compilation the compilation
+	 * @returns {Stats} the compilation stats
+	 */
 	_getStats(compilation) {
 		const stats = new Stats(compilation);
-		stats.startTime = this.startTime;
-		stats.endTime = Date.now();
 		return stats;
 	}
 
+	/**
+	 * Processes the provided err.
+	 * @param {(Error | null)=} err an optional error
+	 * @param {Compilation=} compilation the compilation
+	 * @returns {void}
+	 */
 	_done(err, compilation) {
 		this.running = false;
-		if (this.invalid) return this._go();
 
-		const stats = compilation ? this._getStats(compilation) : null;
-		if (err) {
+		const logger =
+			/** @type {Logger} */
+			(compilation && compilation.getLogger("webpack.Watching"));
+
+		/** @type {Stats | undefined} */
+		let stats;
+
+		/**
+		 * Processes the provided err.
+		 * @param {Error} err error
+		 * @param {ErrorCallback[]=} cbs callbacks
+		 */
+		const handleError = (err, cbs) => {
 			this.compiler.hooks.failed.call(err);
-			this.handler(err, stats);
+			this.compiler.cache.beginIdle();
+			this.compiler.idle = true;
+			this.handler(err, /** @type {Stats} */ (stats));
+			if (!cbs) {
+				cbs = this.callbacks;
+				this.callbacks = [];
+			}
+			for (const cb of cbs) cb(err);
+		};
+
+		if (
+			this.invalid &&
+			!this.suspended &&
+			!this.blocked &&
+			!(this._isBlocked() && (this.blocked = true))
+		) {
+			if (compilation) {
+				logger.time("storeBuildDependencies");
+				this.compiler.cache.storeBuildDependencies(
+					compilation.buildDependencies,
+					(err) => {
+						logger.timeEnd("storeBuildDependencies");
+						if (err) return handleError(err);
+						this._go();
+					}
+				);
+			} else {
+				this._go();
+			}
 			return;
 		}
 
-		this.compiler.hooks.done.callAsync(stats, () => {
+		if (compilation) {
+			compilation.startTime = /** @type {number} */ (this.startTime);
+			compilation.endTime = Date.now();
+			stats = new Stats(compilation);
+		}
+		this.startTime = null;
+		if (err) return handleError(err);
+
+		const cbs = this.callbacks;
+		this.callbacks = [];
+		logger.time("done hook");
+		this.compiler.hooks.done.callAsync(/** @type {Stats} */ (stats), (err) => {
+			logger.timeEnd("done hook");
+			if (err) return handleError(err, cbs);
 			this.handler(null, stats);
-			if (!this.closed) {
-				this.watch(
-					Array.from(compilation.fileDependencies),
-					Array.from(compilation.contextDependencies),
-					Array.from(compilation.missingDependencies)
-				);
-			}
-			for (const cb of this.callbacks) cb();
-			this.callbacks.length = 0;
+			logger.time("storeBuildDependencies");
+			this.compiler.cache.storeBuildDependencies(
+				/** @type {Compilation} */
+				(compilation).buildDependencies,
+				(err) => {
+					logger.timeEnd("storeBuildDependencies");
+					if (err) return handleError(err, cbs);
+					logger.time("beginIdle");
+					this.compiler.cache.beginIdle();
+					this.compiler.idle = true;
+					logger.timeEnd("beginIdle");
+					process.nextTick(() => {
+						if (!this.closed) {
+							this.watch(
+								/** @type {Compilation} */
+								(compilation).fileDependencies,
+								/** @type {Compilation} */
+								(compilation).contextDependencies,
+								/** @type {Compilation} */
+								(compilation).missingDependencies
+							);
+						}
+					});
+					for (const cb of cbs) cb(null);
+					this.compiler.hooks.afterDone.call(/** @type {Stats} */ (stats));
+				}
+			);
 		});
 	}
 
+	/**
+	 * Processes the provided file.
+	 * @param {Iterable} files watched files
+	 * @param {Iterable} dirs watched directories
+	 * @param {Iterable} missing watched existence entries
+	 * @returns {void}
+	 */
 	watch(files, dirs, missing) {
 		this.pausedWatcher = null;
-		this.watcher = this.compiler.watchFileSystem.watch(
-			files,
-			dirs,
-			missing,
-			this.startTime,
-			this.watchOptions,
-			(
-				err,
-				filesModified,
-				contextModified,
-				missingModified,
-				fileTimestamps,
-				contextTimestamps
-			) => {
-				this.pausedWatcher = this.watcher;
-				this.watcher = null;
-				if (err) {
-					return this.handler(err);
+		this.watcher =
+			/** @type {WatchFileSystem} */
+			(this.compiler.watchFileSystem).watch(
+				files,
+				dirs,
+				missing,
+				/** @type {number} */ (this.lastWatcherStartTime),
+				this.watchOptions,
+				(
+					err,
+					fileTimeInfoEntries,
+					contextTimeInfoEntries,
+					changedFiles,
+					removedFiles
+				) => {
+					if (err) {
+						this.compiler.modifiedFiles = undefined;
+						this.compiler.removedFiles = undefined;
+						this.compiler.fileTimestamps = undefined;
+						this.compiler.contextTimestamps = undefined;
+						this.compiler.fsStartTime = undefined;
+						return this.handler(err);
+					}
+					this._invalidate(
+						fileTimeInfoEntries,
+						contextTimeInfoEntries,
+						changedFiles,
+						removedFiles
+					);
+					this._onChange();
+				},
+				(fileName, changeTime) => {
+					if (!this._invalidReported) {
+						this._invalidReported = true;
+						this.compiler.hooks.invalid.call(fileName, changeTime);
+					}
+					this._onInvalid();
 				}
-				this.compiler.fileTimestamps = fileTimestamps;
-				this.compiler.contextTimestamps = contextTimestamps;
-				this._invalidate();
-			},
-			(fileName, changeTime) => {
-				this.compiler.hooks.invalid.call(fileName, changeTime);
-			}
-		);
+			);
 	}
 
+	/**
+	 * Processes the provided error callback.
+	 * @param {ErrorCallback=} callback signals when the build has completed again
+	 * @returns {void}
+	 */
 	invalidate(callback) {
 		if (callback) {
 			this.callbacks.push(callback);
 		}
-		if (this.watcher) {
-			this.compiler.fileTimestamps = this.watcher.getFileTimestamps();
-			this.compiler.contextTimestamps = this.watcher.getContextTimestamps();
+		if (!this._invalidReported) {
+			this._invalidReported = true;
+			this.compiler.hooks.invalid.call(null, Date.now());
 		}
-		return this._invalidate();
+		this._onChange();
+		this._invalidate();
 	}
 
-	_invalidate() {
-		if (this.watcher) {
-			this.pausedWatcher = this.watcher;
-			this.watcher.pause();
-			this.watcher = null;
+	/**
+	 * Processes the provided file time info entries.
+	 * @param {TimeInfoEntries=} fileTimeInfoEntries info for files
+	 * @param {TimeInfoEntries=} contextTimeInfoEntries info for directories
+	 * @param {ReadonlySet=} changedFiles changed files
+	 * @param {ReadonlySet=} removedFiles removed files
+	 * @returns {void}
+	 */
+	_invalidate(
+		fileTimeInfoEntries,
+		contextTimeInfoEntries,
+		changedFiles,
+		removedFiles
+	) {
+		if (this.suspended || (this._isBlocked() && (this.blocked = true))) {
+			this._mergeWithCollected(changedFiles, removedFiles);
+			return;
 		}
+
 		if (this.running) {
+			this._mergeWithCollected(changedFiles, removedFiles);
 			this.invalid = true;
-			return false;
 		} else {
-			this._go();
+			this._go(
+				fileTimeInfoEntries,
+				contextTimeInfoEntries,
+				changedFiles,
+				removedFiles
+			);
 		}
 	}
 
+	suspend() {
+		this.suspended = true;
+	}
+
+	resume() {
+		if (this.suspended) {
+			this.suspended = false;
+			this._invalidate();
+		}
+	}
+
+	/**
+	 * Processes the provided error callback.
+	 * @param {ErrorCallback} callback signals when the watcher is closed
+	 * @returns {void}
+	 */
 	close(callback) {
-		const finalCallback = () => {
-			this.compiler.hooks.watchClose.call();
+		if (this._closeCallbacks) {
+			if (callback) {
+				this._closeCallbacks.push(callback);
+			}
+			return;
+		}
+		/**
+		 * Processes the provided err.
+		 * @param {Error | null} err error if any
+		 * @param {Compilation=} compilation compilation if any
+		 */
+		const finalCallback = (err, compilation) => {
+			this.running = false;
 			this.compiler.running = false;
-			if (callback !== undefined) callback();
+			this.compiler.watching = undefined;
+			this.compiler.watchMode = false;
+			this.compiler.modifiedFiles = undefined;
+			this.compiler.removedFiles = undefined;
+			this.compiler.fileTimestamps = undefined;
+			this.compiler.contextTimestamps = undefined;
+			this.compiler.fsStartTime = undefined;
+			/**
+			 * Processes the provided err.
+			 * @param {Error | null} err error if any
+			 */
+			const shutdown = (err) => {
+				this.compiler.hooks.watchClose.call();
+				const closeCallbacks =
+					/** @type {ErrorCallback[]} */
+					(this._closeCallbacks);
+				this._closeCallbacks = undefined;
+				for (const cb of closeCallbacks) cb(err);
+			};
+			if (compilation) {
+				const logger = compilation.getLogger("webpack.Watching");
+				logger.time("storeBuildDependencies");
+				this.compiler.cache.storeBuildDependencies(
+					compilation.buildDependencies,
+					(err2) => {
+						logger.timeEnd("storeBuildDependencies");
+						shutdown(err || err2);
+					}
+				);
+			} else {
+				shutdown(err);
+			}
 		};
 
 		this.closed = true;
@@ -182,11 +539,15 @@ class Watching {
 			this.pausedWatcher.close();
 			this.pausedWatcher = null;
 		}
+		this._closeCallbacks = [];
+		if (callback) {
+			this._closeCallbacks.push(callback);
+		}
 		if (this.running) {
 			this.invalid = true;
 			this._done = finalCallback;
 		} else {
-			finalCallback();
+			finalCallback(null);
 		}
 	}
 }
diff --git a/lib/WebpackError.js b/lib/WebpackError.js
index 8451fcb7d74..22cbd20a463 100644
--- a/lib/WebpackError.js
+++ b/lib/WebpackError.js
@@ -2,24 +2,9 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Jarid Margolin @jaridmargolin
 */
-"use strict";
-
-class WebpackError extends Error {
-	/**
-	 * Creates an instance of WebpackError.
-	 * @param {string=} message error message
-	 */
-	constructor(message) {
-		super(message);
-
-		this.details = undefined;
 
-		Error.captureStackTrace(this, this.constructor);
-	}
-
-	inspect() {
-		return this.stack + (this.details ? `\n${this.details}` : "");
-	}
-}
+"use strict";
 
-module.exports = WebpackError;
+// TODO remove in webpack 6
+// Some old plugins use `require("webpack/lib/WebpackError")`, in webpack@6 developer should migrate to `compiler.webpack.WebpackError`
+module.exports = require("./errors/WebpackError");
diff --git a/lib/WebpackIsIncludedPlugin.js b/lib/WebpackIsIncludedPlugin.js
new file mode 100644
index 00000000000..96f9ded51a8
--- /dev/null
+++ b/lib/WebpackIsIncludedPlugin.js
@@ -0,0 +1,95 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Ivan Kopeykin @vankop
+*/
+
+"use strict";
+
+const {
+	JAVASCRIPT_MODULE_TYPE_AUTO,
+	JAVASCRIPT_MODULE_TYPE_DYNAMIC,
+	JAVASCRIPT_MODULE_TYPE_ESM
+} = require("./ModuleTypeConstants");
+const WebpackIsIncludedDependency = require("./dependencies/WebpackIsIncludedDependency");
+const IgnoreErrorModuleFactory = require("./errors/IgnoreErrorModuleFactory");
+const {
+	toConstantDependency
+} = require("./javascript/JavascriptParserHelpers");
+
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */
+/** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */
+/** @typedef {import("./javascript/JavascriptParser").Range} Range */
+
+const PLUGIN_NAME = "WebpackIsIncludedPlugin";
+
+class WebpackIsIncludedPlugin {
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
+	apply(compiler) {
+		compiler.hooks.compilation.tap(
+			PLUGIN_NAME,
+			(compilation, { normalModuleFactory }) => {
+				compilation.dependencyFactories.set(
+					WebpackIsIncludedDependency,
+					new IgnoreErrorModuleFactory(normalModuleFactory)
+				);
+				compilation.dependencyTemplates.set(
+					WebpackIsIncludedDependency,
+					new WebpackIsIncludedDependency.Template()
+				);
+
+				/**
+				 * Handles the hook callback for this code path.
+				 * @param {JavascriptParser} parser the parser
+				 * @returns {void}
+				 */
+				const handler = (parser) => {
+					parser.hooks.call
+						.for("__webpack_is_included__")
+						.tap(PLUGIN_NAME, (expr) => {
+							if (
+								expr.type !== "CallExpression" ||
+								expr.arguments.length !== 1 ||
+								expr.arguments[0].type === "SpreadElement"
+							) {
+								return;
+							}
+
+							const request = parser.evaluateExpression(expr.arguments[0]);
+
+							if (!request.isString()) return;
+
+							const dep = new WebpackIsIncludedDependency(
+								/** @type {string} */ (request.string),
+								/** @type {Range} */ (expr.range)
+							);
+							dep.loc = parser.getLocation(expr);
+							parser.state.module.addDependency(dep);
+							return true;
+						});
+					parser.hooks.typeof
+						.for("__webpack_is_included__")
+						.tap(
+							PLUGIN_NAME,
+							toConstantDependency(parser, JSON.stringify("function"))
+						);
+				};
+				normalModuleFactory.hooks.parser
+					.for(JAVASCRIPT_MODULE_TYPE_AUTO)
+					.tap(PLUGIN_NAME, handler);
+				normalModuleFactory.hooks.parser
+					.for(JAVASCRIPT_MODULE_TYPE_DYNAMIC)
+					.tap(PLUGIN_NAME, handler);
+				normalModuleFactory.hooks.parser
+					.for(JAVASCRIPT_MODULE_TYPE_ESM)
+					.tap(PLUGIN_NAME, handler);
+			}
+		);
+	}
+}
+
+module.exports = WebpackIsIncludedPlugin;
diff --git a/lib/WebpackOptionsApply.js b/lib/WebpackOptionsApply.js
index d583787ac13..bc035c36973 100644
--- a/lib/WebpackOptionsApply.js
+++ b/lib/WebpackOptionsApply.js
@@ -2,374 +2,989 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
+
 "use strict";
 
-const OptionsApply = require("./OptionsApply");
+const APIPlugin = require("./APIPlugin");
 
-const JavascriptModulesPlugin = require("./JavascriptModulesPlugin");
-const JsonModulesPlugin = require("./JsonModulesPlugin");
-const WebAssemblyModulesPlugin = require("./wasm/WebAssemblyModulesPlugin");
+const CompatibilityPlugin = require("./CompatibilityPlugin");
 
-const LoaderTargetPlugin = require("./LoaderTargetPlugin");
-const FunctionModulePlugin = require("./FunctionModulePlugin");
-const EvalDevToolModulePlugin = require("./EvalDevToolModulePlugin");
-const SourceMapDevToolPlugin = require("./SourceMapDevToolPlugin");
-const EvalSourceMapDevToolPlugin = require("./EvalSourceMapDevToolPlugin");
+const ConstPlugin = require("./ConstPlugin");
 
 const EntryOptionPlugin = require("./EntryOptionPlugin");
-const RecordIdsPlugin = require("./RecordIdsPlugin");
 
-const APIPlugin = require("./APIPlugin");
-const ConstPlugin = require("./ConstPlugin");
-const RequireJsStuffPlugin = require("./RequireJsStuffPlugin");
+const ExportsInfoApiPlugin = require("./ExportsInfoApiPlugin");
+const FlagDependencyExportsPlugin = require("./FlagDependencyExportsPlugin");
+
+const JavascriptMetaInfoPlugin = require("./JavascriptMetaInfoPlugin");
+
 const NodeStuffPlugin = require("./NodeStuffPlugin");
-const CompatibilityPlugin = require("./CompatibilityPlugin");
+const OptionsApply = require("./OptionsApply");
+
+const RecordIdsPlugin = require("./RecordIdsPlugin");
+
+const RuntimePlugin = require("./RuntimePlugin");
 
 const TemplatedPathPlugin = require("./TemplatedPathPlugin");
-const WarnCaseSensitiveModulesPlugin = require("./WarnCaseSensitiveModulesPlugin");
+
 const UseStrictPlugin = require("./UseStrictPlugin");
 
-const LoaderPlugin = require("./dependencies/LoaderPlugin");
+const WarnCaseSensitiveModulesPlugin = require("./WarnCaseSensitiveModulesPlugin");
+
+const WebpackIsIncludedPlugin = require("./WebpackIsIncludedPlugin");
+
+const AssetModulesPlugin = require("./asset/AssetModulesPlugin");
+
+const InferAsyncModulesPlugin = require("./async-modules/InferAsyncModulesPlugin");
+
+const ResolverCachePlugin = require("./cache/ResolverCachePlugin");
+
 const CommonJsPlugin = require("./dependencies/CommonJsPlugin");
+
 const HarmonyModulesPlugin = require("./dependencies/HarmonyModulesPlugin");
-const SystemPlugin = require("./dependencies/SystemPlugin");
+
+const ImportMetaContextPlugin = require("./dependencies/ImportMetaContextPlugin");
+const ImportMetaPlugin = require("./dependencies/ImportMetaPlugin");
+
 const ImportPlugin = require("./dependencies/ImportPlugin");
-const AMDPlugin = require("./dependencies/AMDPlugin");
+const LoaderPlugin = require("./dependencies/LoaderPlugin");
+
 const RequireContextPlugin = require("./dependencies/RequireContextPlugin");
 const RequireEnsurePlugin = require("./dependencies/RequireEnsurePlugin");
 const RequireIncludePlugin = require("./dependencies/RequireIncludePlugin");
 
-const WarnNoModeSetPlugin = require("./WarnNoModeSetPlugin");
+const SystemPlugin = require("./dependencies/SystemPlugin");
 
-const EnsureChunkConditionsPlugin = require("./optimize/EnsureChunkConditionsPlugin");
-const RemoveParentModulesPlugin = require("./optimize/RemoveParentModulesPlugin");
-const RemoveEmptyChunksPlugin = require("./optimize/RemoveEmptyChunksPlugin");
-const MergeDuplicateChunksPlugin = require("./optimize/MergeDuplicateChunksPlugin");
-const FlagIncludedChunksPlugin = require("./optimize/FlagIncludedChunksPlugin");
-const OccurrenceOrderPlugin = require("./optimize/OccurrenceOrderPlugin");
-const SideEffectsFlagPlugin = require("./optimize/SideEffectsFlagPlugin");
-const FlagDependencyUsagePlugin = require("./FlagDependencyUsagePlugin");
-const FlagDependencyExportsPlugin = require("./FlagDependencyExportsPlugin");
-const ModuleConcatenationPlugin = require("./optimize/ModuleConcatenationPlugin");
-const SplitChunksPlugin = require("./optimize/SplitChunksPlugin");
-const RuntimeChunkPlugin = require("./optimize/RuntimeChunkPlugin");
-const NoEmitOnErrorsPlugin = require("./NoEmitOnErrorsPlugin");
-const NamedModulesPlugin = require("./NamedModulesPlugin");
-const NamedChunksPlugin = require("./NamedChunksPlugin");
-const DefinePlugin = require("./DefinePlugin");
-const SizeLimitsPlugin = require("./performance/SizeLimitsPlugin");
-const WasmFinalizeExportsPlugin = require("./wasm/WasmFinalizeExportsPlugin");
+const URLPlugin = require("./dependencies/URLPlugin");
+
+const WorkerAndWorkletPlugin = require("./dependencies/WorkerAndWorkletPlugin");
+
+const JavascriptModulesPlugin = require("./javascript/JavascriptModulesPlugin");
+
+const JsonModulesPlugin = require("./json/JsonModulesPlugin");
+
+const ChunkPrefetchPreloadPlugin = require("./prefetch/ChunkPrefetchPreloadPlugin");
+const ResourceHintPlugin = require("./prefetch/ResourceHintPlugin");
+
+const DataUriPlugin = require("./schemes/DataUriPlugin");
+const FileUriPlugin = require("./schemes/FileUriPlugin");
+
+const DefaultStatsFactoryPlugin = require("./stats/DefaultStatsFactoryPlugin");
+const DefaultStatsPresetPlugin = require("./stats/DefaultStatsPresetPlugin");
+const DefaultStatsPrinterPlugin = require("./stats/DefaultStatsPrinterPlugin");
+
+const { cleverMerge } = require("./util/cleverMerge");
+
+/** @typedef {import("./webpack").WebpackPluginFunction} WebpackPluginFunction */
+/** @typedef {import("./config/defaults").WebpackOptionsNormalizedWithDefaults} WebpackOptions */
+/** @typedef {import("./config/normalization").WebpackOptionsInterception} WebpackOptionsInterception */
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
+/** @typedef {import("./util/fs").IntermediateFileSystem} IntermediateFileSystem */
+
+const CLASS_NAME = "WebpackOptionsApply";
 
 class WebpackOptionsApply extends OptionsApply {
 	constructor() {
 		super();
 	}
 
-	process(options, compiler) {
-		let ExternalsPlugin;
+	/**
+	 * Returns options object.
+	 * @param {WebpackOptions} options options object
+	 * @param {Compiler} compiler compiler object
+	 * @param {WebpackOptionsInterception=} interception intercepted options
+	 * @returns {WebpackOptions} options object
+	 */
+	process(options, compiler, interception) {
 		compiler.outputPath = options.output.path;
-		compiler.recordsInputPath = options.recordsInputPath || options.recordsPath;
-		compiler.recordsOutputPath =
-			options.recordsOutputPath || options.recordsPath;
+		compiler.recordsInputPath = options.recordsInputPath || null;
+		compiler.recordsOutputPath = options.recordsOutputPath || null;
 		compiler.name = options.name;
-		compiler.dependencies = options.dependencies;
-		if (typeof options.target === "string") {
-			let JsonpTemplatePlugin;
-			let FetchCompileWasmTemplatePlugin;
-			let ReadFileCompileWasmTemplatePlugin;
-			let NodeSourcePlugin;
-			let NodeTargetPlugin;
-			let NodeTemplatePlugin;
-
-			switch (options.target) {
-				case "web":
-					JsonpTemplatePlugin = require("./web/JsonpTemplatePlugin");
-					FetchCompileWasmTemplatePlugin = require("./web/FetchCompileWasmTemplatePlugin");
-					NodeSourcePlugin = require("./node/NodeSourcePlugin");
-					new JsonpTemplatePlugin().apply(compiler);
-					new FetchCompileWasmTemplatePlugin({
-						mangleImports: options.optimization.mangleWasmImports
-					}).apply(compiler);
-					new FunctionModulePlugin().apply(compiler);
-					new NodeSourcePlugin(options.node).apply(compiler);
-					new LoaderTargetPlugin(options.target).apply(compiler);
-					break;
-				case "webworker": {
-					let WebWorkerTemplatePlugin = require("./webworker/WebWorkerTemplatePlugin");
-					FetchCompileWasmTemplatePlugin = require("./web/FetchCompileWasmTemplatePlugin");
-					NodeSourcePlugin = require("./node/NodeSourcePlugin");
-					new WebWorkerTemplatePlugin().apply(compiler);
-					new FetchCompileWasmTemplatePlugin({
-						mangleImports: options.optimization.mangleWasmImports
-					}).apply(compiler);
-					new FunctionModulePlugin().apply(compiler);
-					new NodeSourcePlugin(options.node).apply(compiler);
-					new LoaderTargetPlugin(options.target).apply(compiler);
-					break;
+
+		if (options.externals) {
+			// @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697
+			const ExternalsPlugin = require("./ExternalsPlugin");
+
+			new ExternalsPlugin(options.externalsType, options.externals).apply(
+				compiler
+			);
+		}
+
+		if (options.externalsPresets.node) {
+			const NodeTargetPlugin = require("./node/NodeTargetPlugin");
+
+			// Some older versions of Node.js don't support all built-in modules via import, only via `require`,
+			// but it seems like there shouldn't be a warning here since these versions are rarely used in real applications
+			new NodeTargetPlugin(
+				options.output.module ? "module-import" : "node-commonjs"
+			).apply(compiler);
+
+			// Handle external CSS `@import` and `url()`
+			if (options.experiments.css) {
+				// @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697
+				const ExternalsPlugin = require("./ExternalsPlugin");
+
+				new ExternalsPlugin(
+					"module",
+					({ request, dependencyType, contextInfo }, callback) => {
+						if (
+							/\.css(?:\?|$)/.test(contextInfo.issuer) &&
+							/^(?:\/\/|https?:\/\/|#)/.test(request)
+						) {
+							if (dependencyType === "url") {
+								return callback(null, `asset ${request}`);
+							} else if (
+								(dependencyType === "css-import" ||
+									dependencyType === "css-import-local-module" ||
+									dependencyType === "css-import-global-module") &&
+								options.experiments.css
+							) {
+								return callback(null, `css-import ${request}`);
+							}
+						}
+
+						callback();
+					}
+				).apply(compiler);
+			}
+		}
+		if (options.externalsPresets.nodeModules) {
+			// @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697
+			const ExternalsPlugin = require("./ExternalsPlugin");
+
+			const type = options.output.module ? "module-import" : "node-commonjs";
+			const preset = options.externalsPresets.nodeModules;
+			const allowlist = preset === true ? undefined : preset.allowlist;
+			/**
+			 * @param {string} request the request
+			 * @returns {boolean} true when the request should stay bundled
+			 */
+			const isAllowed = (request) =>
+				allowlist !== undefined &&
+				allowlist.some((entry) =>
+					entry instanceof RegExp
+						? entry.test(request)
+						: typeof entry === "function"
+							? entry(request)
+							: entry === request
+				);
+			// Relative/absolute requests are never installed packages, and `#` subpath
+			// imports only resolve against the importing package's own `imports` field.
+			const NEVER_EXTERNAL = /^(?:\.\.?(?:\/|$)|\/|[A-Za-z]:[\\/]|#)/;
+			const NODE_MODULES = /[\\/]node_modules[\\/]/i;
+			// Only JS-loadable resolutions are externalized; a package's CSS/asset
+			// imported from JS must stay bundled so webpack can process it.
+			const RUNTIME_LOADABLE = /\.(?:m|c)?js$|\.json$|\.node$/i;
+			/**
+			 * @param {string} request a request or a path below a `node_modules` directory
+			 * @returns {string} the package name it belongs to
+			 */
+			const getPackageName = (request) => {
+				const parts = request.split("/");
+				return request.startsWith("@") ? `${parts[0]}/${parts[1]}` : parts[0];
+			};
+			// `url()`/CSS `@import` references are handled by their own presets.
+			const NON_JS_DEPENDENCY_TYPES = new Set([
+				"url",
+				"css-import",
+				"css-import-local-module",
+				"css-import-global-module"
+			]);
+
+			new ExternalsPlugin(
+				// Keep `require()` semantics for CommonJS deps even with module output.
+				(dependency) =>
+					dependency.category === "commonjs" ? "node-commonjs" : type,
+				({ request, context, dependencyType, getResolve }, callback) => {
+					if (
+						NEVER_EXTERNAL.test(request) ||
+						NON_JS_DEPENDENCY_TYPES.has(dependencyType) ||
+						isAllowed(request)
+					) {
+						return callback();
+					}
+
+					const resolve =
+						/** @type {(context: string, request: string, callback: (err?: Error | null, result?: string | false) => void) => void} */
+						(getResolve());
+
+					// Externalize the package only when it resolves to a runtime-loadable
+					// file inside a node_modules directory.
+					resolve(context, request, (err, result) => {
+						if (
+							err ||
+							!result ||
+							!NODE_MODULES.test(result) ||
+							!RUNTIME_LOADABLE.test(result)
+						) {
+							return callback();
+						}
+						// The external keeps the original request, so requests that
+						// `resolve.alias` sends to another package must stay bundled.
+						const resolvedPath = result.split(NODE_MODULES).pop();
+						const resolvedPackageName = getPackageName(
+							/** @type {string} */ (resolvedPath).replace(/\\/g, "/")
+						);
+						callback(
+							null,
+							resolvedPackageName === getPackageName(request) ? true : undefined
+						);
+					});
 				}
-				case "node":
-				case "async-node":
-					NodeTemplatePlugin = require("./node/NodeTemplatePlugin");
-					ReadFileCompileWasmTemplatePlugin = require("./node/ReadFileCompileWasmTemplatePlugin");
-					NodeTargetPlugin = require("./node/NodeTargetPlugin");
-					new NodeTemplatePlugin({
-						asyncChunkLoading: options.target === "async-node"
-					}).apply(compiler);
-					new ReadFileCompileWasmTemplatePlugin({
-						mangleImports: options.optimization.mangleWasmImports
-					}).apply(compiler);
-					new FunctionModulePlugin().apply(compiler);
-					new NodeTargetPlugin().apply(compiler);
-					new LoaderTargetPlugin("node").apply(compiler);
-					break;
-				case "node-webkit":
-					JsonpTemplatePlugin = require("./web/JsonpTemplatePlugin");
-					NodeTargetPlugin = require("./node/NodeTargetPlugin");
-					ExternalsPlugin = require("./ExternalsPlugin");
-					new JsonpTemplatePlugin().apply(compiler);
-					new FunctionModulePlugin().apply(compiler);
-					new NodeTargetPlugin().apply(compiler);
-					new ExternalsPlugin("commonjs", "nw.gui").apply(compiler);
-					new LoaderTargetPlugin(options.target).apply(compiler);
+			).apply(compiler);
+		}
+		if (options.externalsPresets.deno) {
+			const DenoTargetPlugin = require("./deno/DenoTargetPlugin");
+
+			new DenoTargetPlugin().apply(compiler);
+		}
+		if (options.externalsPresets.bun) {
+			const BunTargetPlugin = require("./bun/BunTargetPlugin");
+
+			new BunTargetPlugin().apply(compiler);
+		}
+		if (options.externalsPresets.webAsync || options.externalsPresets.web) {
+			const type = options.externalsPresets.webAsync ? "import" : "module";
+
+			// @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697
+			const ExternalsPlugin = require("./ExternalsPlugin");
+
+			new ExternalsPlugin(type, ({ request, dependencyType }, callback) => {
+				if (/^(?:\/\/|https?:\/\/|#|std:|jsr:|npm:)/.test(request)) {
+					if (dependencyType === "url") {
+						return callback(null, `asset ${request}`);
+					} else if (
+						(dependencyType === "css-import" ||
+							dependencyType === "css-import-local-module" ||
+							dependencyType === "css-import-global-module") &&
+						options.experiments.css
+					) {
+						return callback(null, `css-import ${request}`);
+					} else if (/^(?:\/\/|https?:\/\/|std:|jsr:|npm:)/.test(request)) {
+						return callback(null, `${type} ${request}`);
+					}
+				}
+
+				callback();
+			}).apply(compiler);
+		}
+		if (options.externalsPresets.electron) {
+			// Use ESM imports only when the targeted electron version supports them
+			const type =
+				options.output.module && options.output.environment.module
+					? "module-import"
+					: "node-commonjs";
+
+			// Warn when ESM output is requested but the electron version lacks it (added in electron 28)
+			if (options.output.module && !options.output.environment.module) {
+				const WebpackError = require("./WebpackError");
+
+				compiler.hooks.thisCompilation.tap(CLASS_NAME, (compilation) => {
+					compilation.warnings.push(
+						new WebpackError(
+							"'output.module' is enabled, but the targeted electron version does not support ECMAScript modules (added in electron 28). Electron built-in modules are externalized as 'node-commonjs'."
+						)
+					);
+				});
+			}
+
+			if (options.externalsPresets.electronMain) {
+				// @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697
+				const ElectronTargetPlugin = require("./electron/ElectronTargetPlugin");
+
+				new ElectronTargetPlugin("main", type).apply(compiler);
+			}
+			if (options.externalsPresets.electronPreload) {
+				// @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697
+				const ElectronTargetPlugin = require("./electron/ElectronTargetPlugin");
+
+				new ElectronTargetPlugin("preload", type).apply(compiler);
+			}
+			if (options.externalsPresets.electronRenderer) {
+				// @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697
+				const ElectronTargetPlugin = require("./electron/ElectronTargetPlugin");
+
+				new ElectronTargetPlugin("renderer", type).apply(compiler);
+			}
+			if (
+				!options.externalsPresets.electronMain &&
+				!options.externalsPresets.electronPreload &&
+				!options.externalsPresets.electronRenderer
+			) {
+				// @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697
+				const ElectronTargetPlugin = require("./electron/ElectronTargetPlugin");
+
+				new ElectronTargetPlugin(undefined, type).apply(compiler);
+			}
+		}
+		if (options.externalsPresets.nwjs) {
+			// @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697
+			const ExternalsPlugin = require("./ExternalsPlugin");
+
+			new ExternalsPlugin("node-commonjs", "nw.gui").apply(compiler);
+		}
+
+		new ChunkPrefetchPreloadPlugin().apply(compiler);
+		new ResourceHintPlugin(options.output.resourceHints).apply(compiler);
+
+		if (typeof options.output.chunkFormat === "string") {
+			switch (options.output.chunkFormat) {
+				case "array-push": {
+					const ArrayPushCallbackChunkFormatPlugin = require("./javascript/ArrayPushCallbackChunkFormatPlugin");
+
+					new ArrayPushCallbackChunkFormatPlugin().apply(compiler);
 					break;
-				case "electron-main":
-					NodeTemplatePlugin = require("./node/NodeTemplatePlugin");
-					NodeTargetPlugin = require("./node/NodeTargetPlugin");
-					ExternalsPlugin = require("./ExternalsPlugin");
-					new NodeTemplatePlugin({
-						asyncChunkLoading: true
-					}).apply(compiler);
-					new FunctionModulePlugin().apply(compiler);
-					new NodeTargetPlugin().apply(compiler);
-					new ExternalsPlugin("commonjs", [
-						"app",
-						"auto-updater",
-						"browser-window",
-						"clipboard",
-						"content-tracing",
-						"crash-reporter",
-						"dialog",
-						"electron",
-						"global-shortcut",
-						"ipc",
-						"ipc-main",
-						"menu",
-						"menu-item",
-						"native-image",
-						"original-fs",
-						"power-monitor",
-						"power-save-blocker",
-						"protocol",
-						"screen",
-						"session",
-						"shell",
-						"tray",
-						"web-contents"
-					]).apply(compiler);
-					new LoaderTargetPlugin(options.target).apply(compiler);
+				}
+				case "commonjs": {
+					const CommonJsChunkFormatPlugin = require("./javascript/CommonJsChunkFormatPlugin");
+
+					new CommonJsChunkFormatPlugin().apply(compiler);
 					break;
-				case "electron-renderer":
-					JsonpTemplatePlugin = require("./web/JsonpTemplatePlugin");
-					NodeTargetPlugin = require("./node/NodeTargetPlugin");
-					ExternalsPlugin = require("./ExternalsPlugin");
-					new JsonpTemplatePlugin().apply(compiler);
-					new FunctionModulePlugin().apply(compiler);
-					new NodeTargetPlugin().apply(compiler);
-					new ExternalsPlugin("commonjs", [
-						"clipboard",
-						"crash-reporter",
-						"desktop-capturer",
-						"electron",
-						"ipc",
-						"ipc-renderer",
-						"native-image",
-						"original-fs",
-						"remote",
-						"screen",
-						"shell",
-						"web-frame"
-					]).apply(compiler);
-					new LoaderTargetPlugin(options.target).apply(compiler);
+				}
+				case "module": {
+					const ModuleChunkFormatPlugin = require("./esm/ModuleChunkFormatPlugin");
+
+					new ModuleChunkFormatPlugin().apply(compiler);
 					break;
+				}
 				default:
-					throw new Error("Unsupported target '" + options.target + "'.");
+					throw new Error(
+						`Unsupported chunk format '${options.output.chunkFormat}'.`
+					);
+			}
+		}
+
+		const enabledChunkLoadingTypes =
+			/** @type {NonNullable} */
+			(options.output.enabledChunkLoadingTypes);
+
+		if (enabledChunkLoadingTypes.length > 0) {
+			for (const type of enabledChunkLoadingTypes) {
+				const EnableChunkLoadingPlugin = require("./javascript/EnableChunkLoadingPlugin");
+
+				new EnableChunkLoadingPlugin(type).apply(compiler);
+			}
+		}
+
+		const enabledWasmLoadingTypes =
+			/** @type {NonNullable} */
+			(options.output.enabledWasmLoadingTypes);
+
+		if (enabledWasmLoadingTypes.length > 0) {
+			for (const type of enabledWasmLoadingTypes) {
+				const EnableWasmLoadingPlugin = require("./wasm/EnableWasmLoadingPlugin");
+
+				new EnableWasmLoadingPlugin(type).apply(compiler);
+			}
+		}
+
+		const enabledLibraryTypes =
+			/** @type {NonNullable} */
+			(options.output.enabledLibraryTypes);
+
+		if (enabledLibraryTypes.length > 0) {
+			let once = true;
+			for (const type of enabledLibraryTypes) {
+				const EnableLibraryPlugin = require("./library/EnableLibraryPlugin");
+
+				new EnableLibraryPlugin(type, {
+					// eslint-disable-next-line no-loop-func
+					additionalApply: () => {
+						if (!once) return;
+						once = false;
+						// We rely on `exportInfo` to generate the `export statement` in certain library bundles.
+						// Therefore, we ignore the disabling of `optimization.providedExport` and continue to apply `FlagDependencyExportsPlugin`.
+						if (
+							["module", "commonjs-static", "modern-module"].includes(type) &&
+							!options.optimization.providedExports
+						) {
+							new FlagDependencyExportsPlugin().apply(compiler);
+						}
+					}
+				}).apply(compiler);
 			}
-		} else if (options.target !== false) {
-			options.target(compiler);
-		} else {
-			throw new Error("Unsupported target '" + options.target + "'.");
-		}
-
-		if (options.output.library || options.output.libraryTarget !== "var") {
-			const LibraryTemplatePlugin = require("./LibraryTemplatePlugin");
-			new LibraryTemplatePlugin(
-				options.output.library,
-				options.output.libraryTarget,
-				options.output.umdNamedDefine,
-				options.output.auxiliaryComment || "",
-				options.output.libraryExport
+		}
+
+		if (options.output.pathinfo) {
+			const ModuleInfoHeaderPlugin = require("./ModuleInfoHeaderPlugin");
+
+			new ModuleInfoHeaderPlugin(options.output.pathinfo !== true).apply(
+				compiler
+			);
+		}
+
+		if (options.output.clean) {
+			const CleanPlugin = require("./CleanPlugin");
+
+			new CleanPlugin(
+				options.output.clean === true ? {} : options.output.clean
 			).apply(compiler);
 		}
-		if (options.externals) {
-			ExternalsPlugin = require("./ExternalsPlugin");
-			new ExternalsPlugin(
-				options.output.libraryTarget,
-				options.externals
+
+		if (options.dotenv) {
+			const DotenvPlugin = require("./DotenvPlugin");
+
+			new DotenvPlugin(
+				typeof options.dotenv === "boolean" ? {} : options.dotenv
 			).apply(compiler);
 		}
 
-		let noSources;
-		let legacy;
-		let modern;
-		let comment;
-		if (
-			options.devtool &&
-			(options.devtool.includes("sourcemap") ||
-				options.devtool.includes("source-map"))
-		) {
-			const hidden = options.devtool.includes("hidden");
-			const inline = options.devtool.includes("inline");
-			const evalWrapped = options.devtool.includes("eval");
-			const cheap = options.devtool.includes("cheap");
-			const moduleMaps = options.devtool.includes("module");
-			noSources = options.devtool.includes("nosources");
-			legacy = options.devtool.includes("@");
-			modern = options.devtool.includes("#");
-			comment =
-				legacy && modern
-					? "\n/*\n//@ source" +
-					  "MappingURL=[url]\n//# source" +
-					  "MappingURL=[url]\n*/"
-					: legacy
-						? "\n/*\n//@ source" + "MappingURL=[url]\n*/"
-						: modern
-							? "\n//# source" + "MappingURL=[url]"
-							: null;
-			const Plugin = evalWrapped
-				? EvalSourceMapDevToolPlugin
-				: SourceMapDevToolPlugin;
-			new Plugin({
-				filename: inline ? null : options.output.sourceMapFilename,
-				moduleFilenameTemplate: options.output.devtoolModuleFilenameTemplate,
-				fallbackModuleFilenameTemplate:
-					options.output.devtoolFallbackModuleFilenameTemplate,
-				append: hidden ? false : comment,
-				module: moduleMaps ? true : cheap ? false : true,
-				columns: cheap ? false : true,
-				lineToLine: options.output.devtoolLineToLine,
-				noSources: noSources,
-				namespace: options.output.devtoolNamespace
-			}).apply(compiler);
-		} else if (options.devtool && options.devtool.includes("eval")) {
-			legacy = options.devtool.includes("@");
-			modern = options.devtool.includes("#");
-			comment =
-				legacy && modern
-					? "\n//@ sourceURL=[url]\n//# sourceURL=[url]"
-					: legacy
-						? "\n//@ sourceURL=[url]"
-						: modern
-							? "\n//# sourceURL=[url]"
-							: null;
-			new EvalDevToolModulePlugin({
-				sourceUrlComment: comment,
-				moduleFilenameTemplate: options.output.devtoolModuleFilenameTemplate,
-				namespace: options.output.devtoolNamespace
-			}).apply(compiler);
+		let devtool =
+			interception === undefined ? options.devtool : interception.devtool;
+		devtool = Array.isArray(devtool)
+			? devtool
+			: typeof devtool === "string"
+				? [{ type: "all", use: devtool }]
+				: [];
+
+		for (const item of devtool) {
+			const { type, use } = item;
+
+			if (use) {
+				if (use.includes("source-map")) {
+					const hidden = use.includes("hidden");
+					const inline = use.includes("inline");
+					const evalWrapped = use.includes("eval");
+					const cheap = use.includes("cheap");
+					const moduleMaps = use.includes("module");
+					const noSources = use.includes("nosources");
+					const debugIds = use.includes("debugids");
+					const Plugin = evalWrapped
+						? require("./EvalSourceMapDevToolPlugin")
+						: require("./SourceMapDevToolPlugin");
+					const assetExt =
+						type === "javascript"
+							? /\.((c|m)?js)($|\?)/i
+							: type === "css"
+								? /\.(css)($|\?)/i
+								: /\.((c|m)?js|css)($|\?)/i;
+
+					new Plugin({
+						test: evalWrapped ? undefined : assetExt,
+						filename: inline ? null : options.output.sourceMapFilename,
+						moduleFilenameTemplate:
+							options.output.devtoolModuleFilenameTemplate,
+						fallbackModuleFilenameTemplate:
+							options.output.devtoolFallbackModuleFilenameTemplate,
+						append: hidden ? false : undefined,
+						module: moduleMaps ? true : !cheap,
+						columns: !cheap,
+						noSources,
+						namespace: options.output.devtoolNamespace,
+						debugIds
+					}).apply(compiler);
+				} else if (use.includes("eval")) {
+					const EvalDevToolModulePlugin = require("./EvalDevToolModulePlugin");
+
+					new EvalDevToolModulePlugin({
+						moduleFilenameTemplate:
+							options.output.devtoolModuleFilenameTemplate,
+						namespace: options.output.devtoolNamespace
+					}).apply(compiler);
+				}
+			}
 		}
 
 		new JavascriptModulesPlugin().apply(compiler);
+
 		new JsonModulesPlugin().apply(compiler);
-		new WebAssemblyModulesPlugin({
-			mangleImports: options.optimization.mangleWasmImports
+		new AssetModulesPlugin({
+			sideEffectFree: options.experiments.futureDefaults
 		}).apply(compiler);
 
+		if (!options.experiments.outputModule) {
+			if (options.output.module) {
+				throw new Error(
+					"'output.module: true' is only allowed when 'experiments.outputModule' is enabled"
+				);
+			}
+			if (options.output.enabledLibraryTypes.includes("module")) {
+				throw new Error(
+					"library type \"module\" is only allowed when 'experiments.outputModule' is enabled"
+				);
+			}
+			if (options.output.enabledLibraryTypes.includes("modern-module")) {
+				throw new Error(
+					"library type \"modern-module\" is only allowed when 'experiments.outputModule' is enabled"
+				);
+			}
+			if (
+				options.externalsType === "module" ||
+				options.externalsType === "module-import"
+			) {
+				throw new Error(
+					"'externalsType: \"module\"' is only allowed when 'experiments.outputModule' is enabled"
+				);
+			}
+		}
+
+		if (options.experiments.syncWebAssembly) {
+			const WebAssemblyModulesPlugin = require("./wasm-sync/WebAssemblyModulesPlugin");
+
+			new WebAssemblyModulesPlugin({
+				mangleImports: options.optimization.mangleWasmImports
+			}).apply(compiler);
+		}
+
+		if (options.experiments.asyncWebAssembly) {
+			const AsyncWebAssemblyModulesPlugin = require("./wasm-async/AsyncWebAssemblyModulesPlugin");
+
+			new AsyncWebAssemblyModulesPlugin({
+				mangleImports: options.optimization.mangleWasmImports
+			}).apply(compiler);
+		}
+
+		if (options.experiments.css) {
+			const CssModulesPlugin = require("./css/CssModulesPlugin");
+
+			new CssModulesPlugin().apply(compiler);
+		}
+
+		if (options.experiments.html) {
+			const HtmlModulesPlugin = require("./html/HtmlModulesPlugin");
+
+			new HtmlModulesPlugin().apply(compiler);
+		}
+
+		if (options.experiments.typescript) {
+			const TypeScriptPlugin = require("./typescript/TypeScriptPlugin");
+
+			new TypeScriptPlugin().apply(compiler);
+		}
+
+		if (options.experiments.lazyCompilation) {
+			const LazyCompilationPlugin = require("./hmr/LazyCompilationPlugin");
+
+			const lazyOptions =
+				typeof options.experiments.lazyCompilation === "object"
+					? options.experiments.lazyCompilation
+					: {};
+			const isUniversalTarget =
+				options.output.module &&
+				compiler.platform.node === null &&
+				compiler.platform.web === null;
+
+			if (isUniversalTarget) {
+				const emitter = require.resolve("../hot/emitter-event-target.js");
+
+				const NormalModuleReplacementPlugin = require("./NormalModuleReplacementPlugin");
+
+				// Override emitter that using `EventEmitter` to `EventTarget`
+				// TODO webpack 6 - migrate to `EventTarget` by default
+				new NormalModuleReplacementPlugin(/emitter(\.js)?$/, (result) => {
+					if (
+						/webpack[/\\]hot|webpack-dev-server[/\\]client|webpack-hot-middleware[/\\]client/.test(
+							result.context
+						)
+					) {
+						result.request = emitter;
+					}
+
+					return result;
+				}).apply(compiler);
+			}
+
+			const backend = require.resolve(
+				isUniversalTarget
+					? "../hot/lazy-compilation-universal.js"
+					: `../hot/lazy-compilation-${
+							options.externalsPresets.node ? "node" : "web"
+						}.js`
+			);
+
+			new LazyCompilationPlugin({
+				backend:
+					typeof lazyOptions.backend === "function"
+						? lazyOptions.backend
+						: require("./hmr/lazyCompilationBackend")({
+								...lazyOptions.backend,
+								client:
+									(lazyOptions.backend && lazyOptions.backend.client) || backend
+							}),
+				entries: !lazyOptions || lazyOptions.entries !== false,
+				imports: !lazyOptions || lazyOptions.imports !== false,
+				test: (lazyOptions && lazyOptions.test) || undefined
+			}).apply(compiler);
+		}
+
+		if (options.experiments.buildHttp) {
+			const HttpUriPlugin = require("./schemes/HttpUriPlugin");
+
+			const httpOptions = options.experiments.buildHttp;
+			new HttpUriPlugin(httpOptions).apply(compiler);
+		}
+
 		new EntryOptionPlugin().apply(compiler);
 		compiler.hooks.entryOption.call(options.context, options.entry);
 
+		new RuntimePlugin().apply(compiler);
+
+		new InferAsyncModulesPlugin().apply(compiler);
+
+		new DataUriPlugin().apply(compiler);
+		new FileUriPlugin().apply(compiler);
+
 		new CompatibilityPlugin().apply(compiler);
-		new HarmonyModulesPlugin(options.module).apply(compiler);
-		new AMDPlugin(options.module, options.amd || {}).apply(compiler);
-		new CommonJsPlugin(options.module).apply(compiler);
+		new HarmonyModulesPlugin({
+			deferImport: options.experiments.deferImport
+		}).apply(compiler);
+		if (options.amd !== false) {
+			const AMDPlugin = require("./dependencies/AMDPlugin");
+			const RequireJsStuffPlugin = require("./dependencies/RequireJsStuffPlugin");
+
+			new AMDPlugin(options.amd || {}).apply(compiler);
+			new RequireJsStuffPlugin().apply(compiler);
+		}
+		new CommonJsPlugin().apply(compiler);
 		new LoaderPlugin().apply(compiler);
-		new NodeStuffPlugin(options.node).apply(compiler);
-		new RequireJsStuffPlugin().apply(compiler);
+		new NodeStuffPlugin({
+			global: options.node ? options.node.global : false,
+			__dirname: options.node ? options.node.__dirname : false,
+			__filename: options.node ? options.node.__filename : false
+		}).apply(compiler);
 		new APIPlugin().apply(compiler);
+		new ExportsInfoApiPlugin().apply(compiler);
+		new WebpackIsIncludedPlugin().apply(compiler);
 		new ConstPlugin().apply(compiler);
 		new UseStrictPlugin().apply(compiler);
 		new RequireIncludePlugin().apply(compiler);
 		new RequireEnsurePlugin().apply(compiler);
-		new RequireContextPlugin(
-			options.resolve.modules,
-			options.resolve.extensions,
-			options.resolve.mainFiles
+		new RequireContextPlugin().apply(compiler);
+		new ImportPlugin().apply(compiler);
+		new ImportMetaContextPlugin().apply(compiler);
+		new SystemPlugin().apply(compiler);
+		new ImportMetaPlugin().apply(compiler);
+		new URLPlugin().apply(compiler);
+		new WorkerAndWorkletPlugin(
+			options.output.workerChunkLoading,
+			options.output.workerWasmLoading,
+			options.output.module,
+			options.output.workerPublicPath,
+			options.experiments.futureDefaults
 		).apply(compiler);
-		new ImportPlugin(options.module).apply(compiler);
-		new SystemPlugin(options.module).apply(compiler);
+
+		new DefaultStatsFactoryPlugin().apply(compiler);
+		new DefaultStatsPresetPlugin().apply(compiler);
+		new DefaultStatsPrinterPlugin().apply(compiler);
+
+		new JavascriptMetaInfoPlugin().apply(compiler);
 
 		if (typeof options.mode !== "string") {
+			const WarnNoModeSetPlugin = require("./WarnNoModeSetPlugin");
+
 			new WarnNoModeSetPlugin().apply(compiler);
 		}
 
+		const EnsureChunkConditionsPlugin = require("./optimize/EnsureChunkConditionsPlugin");
+
 		new EnsureChunkConditionsPlugin().apply(compiler);
 		if (options.optimization.removeAvailableModules) {
+			const RemoveParentModulesPlugin = require("./optimize/RemoveParentModulesPlugin");
+
 			new RemoveParentModulesPlugin().apply(compiler);
 		}
 		if (options.optimization.removeEmptyChunks) {
+			const RemoveEmptyChunksPlugin = require("./optimize/RemoveEmptyChunksPlugin");
+
 			new RemoveEmptyChunksPlugin().apply(compiler);
 		}
 		if (options.optimization.mergeDuplicateChunks) {
+			const MergeDuplicateChunksPlugin = require("./optimize/MergeDuplicateChunksPlugin");
+
 			new MergeDuplicateChunksPlugin().apply(compiler);
 		}
 		if (options.optimization.flagIncludedChunks) {
+			const FlagIncludedChunksPlugin = require("./optimize/FlagIncludedChunksPlugin");
+
 			new FlagIncludedChunksPlugin().apply(compiler);
 		}
-		if (options.optimization.occurrenceOrder) {
-			new OccurrenceOrderPlugin(true).apply(compiler);
-		}
 		if (options.optimization.sideEffects) {
-			new SideEffectsFlagPlugin().apply(compiler);
+			const SideEffectsFlagPlugin = require("./optimize/SideEffectsFlagPlugin");
+
+			new SideEffectsFlagPlugin(
+				options.optimization.sideEffects === true
+			).apply(compiler);
 		}
 		if (options.optimization.providedExports) {
 			new FlagDependencyExportsPlugin().apply(compiler);
 		}
 		if (options.optimization.usedExports) {
-			new FlagDependencyUsagePlugin().apply(compiler);
+			const FlagDependencyUsagePlugin = require("./FlagDependencyUsagePlugin");
+
+			new FlagDependencyUsagePlugin(
+				options.optimization.usedExports === "global",
+				// Keep an escaping module's exports mangleable when mangling is on.
+				Boolean(options.optimization.mangleExports)
+			).apply(compiler);
+		}
+		if (options.optimization.innerGraph) {
+			const InnerGraphPlugin = require("./optimize/InnerGraphPlugin");
+
+			new InnerGraphPlugin().apply(compiler);
+		}
+
+		const circularHints =
+			options.performance && options.performance.circularDependencies
+				? options.performance.hints
+				: false;
+
+		if (options.mode === "production" || circularHints) {
+			const CircularModulesPlugin = require("./CircularModulesPlugin");
+
+			new CircularModulesPlugin({ hints: circularHints }).apply(compiler);
+		}
+
+		const ConstExportsPlugin = require("./optimize/ConstExportsPlugin");
+
+		new ConstExportsPlugin({
+			inlineExports: options.optimization.inlineExports
+		}).apply(compiler);
+
+		if (options.optimization.mangleExports) {
+			const MangleExportsPlugin = require("./optimize/MangleExportsPlugin");
+
+			new MangleExportsPlugin(
+				options.optimization.mangleExports !== "size"
+			).apply(compiler);
 		}
 		if (options.optimization.concatenateModules) {
+			const ModuleConcatenationPlugin = require("./optimize/ModuleConcatenationPlugin");
+
 			new ModuleConcatenationPlugin().apply(compiler);
 		}
 		if (options.optimization.splitChunks) {
+			const SplitChunksPlugin = require("./optimize/SplitChunksPlugin");
+
 			new SplitChunksPlugin(options.optimization.splitChunks).apply(compiler);
 		}
 		if (options.optimization.runtimeChunk) {
+			const RuntimeChunkPlugin = require("./optimize/RuntimeChunkPlugin");
+
 			new RuntimeChunkPlugin(options.optimization.runtimeChunk).apply(compiler);
 		}
-		if (options.optimization.noEmitOnErrors) {
+		if (!options.optimization.emitOnErrors) {
+			const NoEmitOnErrorsPlugin = require("./NoEmitOnErrorsPlugin");
+
 			new NoEmitOnErrorsPlugin().apply(compiler);
 		}
+		if (options.optimization.realContentHash) {
+			const RealContentHashPlugin = require("./optimize/RealContentHashPlugin");
+
+			new RealContentHashPlugin({
+				hashFunction:
+					/** @type {NonNullable} */
+					(options.output.hashFunction),
+				hashDigest:
+					/** @type {NonNullable} */
+					(options.output.hashDigest)
+			}).apply(compiler);
+		}
 		if (options.optimization.checkWasmTypes) {
+			const WasmFinalizeExportsPlugin = require("./wasm-sync/WasmFinalizeExportsPlugin");
+
 			new WasmFinalizeExportsPlugin().apply(compiler);
 		}
-		if (options.optimization.namedModules) {
-			new NamedModulesPlugin().apply(compiler);
+		const moduleIds = options.optimization.moduleIds;
+		if (moduleIds) {
+			switch (moduleIds) {
+				case "natural": {
+					const NaturalModuleIdsPlugin = require("./ids/NaturalModuleIdsPlugin");
+
+					new NaturalModuleIdsPlugin().apply(compiler);
+					break;
+				}
+				case "named": {
+					const NamedModuleIdsPlugin = require("./ids/NamedModuleIdsPlugin");
+
+					new NamedModuleIdsPlugin().apply(compiler);
+					break;
+				}
+				case "hashed": {
+					const WarnDeprecatedOptionPlugin = require("./WarnDeprecatedOptionPlugin");
+					const HashedModuleIdsPlugin = require("./ids/HashedModuleIdsPlugin");
+
+					new WarnDeprecatedOptionPlugin(
+						"optimization.moduleIds",
+						"hashed",
+						"deterministic"
+					).apply(compiler);
+					new HashedModuleIdsPlugin({
+						hashFunction: options.output.hashFunction
+					}).apply(compiler);
+					break;
+				}
+				case "deterministic": {
+					const DeterministicModuleIdsPlugin = require("./ids/DeterministicModuleIdsPlugin");
+
+					new DeterministicModuleIdsPlugin().apply(compiler);
+					break;
+				}
+				case "size": {
+					const OccurrenceModuleIdsPlugin = require("./ids/OccurrenceModuleIdsPlugin");
+
+					new OccurrenceModuleIdsPlugin({
+						prioritiseInitial: true
+					}).apply(compiler);
+					break;
+				}
+				default:
+					throw new Error(
+						`webpack bug: moduleIds: ${moduleIds} is not implemented`
+					);
+			}
 		}
-		if (options.optimization.namedChunks) {
-			new NamedChunksPlugin().apply(compiler);
+		const chunkIds = options.optimization.chunkIds;
+		if (chunkIds) {
+			switch (chunkIds) {
+				case "natural": {
+					const NaturalChunkIdsPlugin = require("./ids/NaturalChunkIdsPlugin");
+
+					new NaturalChunkIdsPlugin().apply(compiler);
+					break;
+				}
+				case "named": {
+					const NamedChunkIdsPlugin = require("./ids/NamedChunkIdsPlugin");
+
+					new NamedChunkIdsPlugin().apply(compiler);
+					break;
+				}
+				case "deterministic": {
+					const DeterministicChunkIdsPlugin = require("./ids/DeterministicChunkIdsPlugin");
+
+					new DeterministicChunkIdsPlugin().apply(compiler);
+					break;
+				}
+				case "size": {
+					// @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697
+					const OccurrenceChunkIdsPlugin = require("./ids/OccurrenceChunkIdsPlugin");
+
+					new OccurrenceChunkIdsPlugin({
+						prioritiseInitial: true
+					}).apply(compiler);
+					break;
+				}
+				case "total-size": {
+					// @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697
+					const OccurrenceChunkIdsPlugin = require("./ids/OccurrenceChunkIdsPlugin");
+
+					new OccurrenceChunkIdsPlugin({
+						prioritiseInitial: false
+					}).apply(compiler);
+					break;
+				}
+				default:
+					throw new Error(
+						`webpack bug: chunkIds: ${chunkIds} is not implemented`
+					);
+			}
 		}
 		if (options.optimization.nodeEnv) {
+			const DefinePlugin = require("./DefinePlugin");
+
+			const defValue = JSON.stringify(options.optimization.nodeEnv);
+			const development = options.mode === "development";
+			// SSR = server-only build (node, not browser); a universal (node+browser) target is null here, so not SSR.
+			const ssr =
+				compiler.platform.node === true && compiler.platform.browser !== true;
+			const { publicPath } = options.output;
+			// Vite-compatible base URL; fall back to "/" for runtime ("auto") or empty publicPath.
+			const baseUrl =
+				typeof publicPath === "string" &&
+				publicPath !== "auto" &&
+				publicPath !== ""
+					? publicPath
+					: "/";
+
 			new DefinePlugin({
-				"process.env.NODE_ENV": JSON.stringify(options.optimization.nodeEnv)
+				"process.env.NODE_ENV": defValue,
+				"import.meta.env.NODE_ENV": defValue,
+				"import.meta.env.MODE": defValue,
+				"import.meta.env.DEV": JSON.stringify(development),
+				"import.meta.env.PROD": JSON.stringify(!development),
+				"import.meta.env.SSR": JSON.stringify(ssr),
+				"import.meta.env.BASE_URL": JSON.stringify(baseUrl)
 			}).apply(compiler);
 		}
 		if (options.optimization.minimize) {
 			for (const minimizer of options.optimization.minimizer) {
-				minimizer.apply(compiler);
+				if (typeof minimizer === "function") {
+					/** @type {WebpackPluginFunction} */
+					(minimizer).call(compiler, compiler);
+				} else if (minimizer !== "..." && minimizer) {
+					minimizer.apply(compiler);
+				}
 			}
 		}
 
 		if (options.performance) {
+			const SizeLimitsPlugin = require("./performance/SizeLimitsPlugin");
+
 			new SizeLimitsPlugin(options.performance).apply(compiler);
+
+			if (options.performance.duplicatePackages) {
+				const DuplicatePackagesPlugin = require("./performance/DuplicatePackagesPlugin");
+
+				new DuplicatePackagesPlugin(options.performance).apply(compiler);
+			}
+
+			if (options.performance.cacheEffectiveness) {
+				const CacheEffectivenessPlugin = require("./performance/CacheEffectivenessPlugin");
+
+				new CacheEffectivenessPlugin(options.performance).apply(compiler);
+			}
+
+			if (options.performance.duplicateModules) {
+				const DuplicateModulesPlugin = require("./performance/DuplicateModulesPlugin");
+
+				new DuplicateModulesPlugin(options.performance).apply(compiler);
+			}
+
+			if (options.performance.redundantDynamicImports) {
+				const RedundantDynamicImportsPlugin = require("./performance/RedundantDynamicImportsPlugin");
+
+				new RedundantDynamicImportsPlugin(options.performance).apply(compiler);
+			}
+
+			if (options.performance.dynamicExports) {
+				const DynamicExportsPlugin = require("./performance/DynamicExportsPlugin");
+
+				new DynamicExportsPlugin(options.performance).apply(compiler);
+			}
+
+			if (options.performance.missingSideEffects) {
+				const MissingSideEffectsPlugin = require("./performance/MissingSideEffectsPlugin");
+
+				new MissingSideEffectsPlugin(options.performance).apply(compiler);
+			}
+
+			if (options.performance.scopeHoistingBailouts) {
+				const ScopeHoistingBailoutsPlugin = require("./performance/ScopeHoistingBailoutsPlugin");
+
+				new ScopeHoistingBailoutsPlugin(options.performance).apply(compiler);
+			}
+
+			if (options.performance.unusedReexports) {
+				const UnusedReexportsPlugin = require("./performance/UnusedReexportsPlugin");
+
+				new UnusedReexportsPlugin(options.performance).apply(compiler);
+			}
+
+			// Deliberately not gated on `hints`, like `unusedRules` below: a rule
+			// that only matches on one OS is a configuration mistake, not a size.
+			if (options.performance.osDependentRules) {
+				const OsDependentRuleWarningPlugin = require("./performance/OsDependentRuleWarningPlugin");
+
+				new OsDependentRuleWarningPlugin(options.performance).apply(compiler);
+			}
+
+			// Deliberately not gated on `hints`: a dead rule is a configuration
+			// mistake worth reporting even where size hints are off.
+			if (options.performance.unusedRules) {
+				const UnusedRuleWarningPlugin = require("./performance/UnusedRuleWarningPlugin");
+
+				new UnusedRuleWarningPlugin(options.performance).apply(compiler);
+			}
 		}
 
 		new TemplatedPathPlugin().apply(compiler);
@@ -380,11 +995,127 @@ class WebpackOptionsApply extends OptionsApply {
 
 		new WarnCaseSensitiveModulesPlugin().apply(compiler);
 
-		if (options.cache) {
-			const CachePlugin = require("./CachePlugin");
-			new CachePlugin(
-				typeof options.cache === "object" ? options.cache : null
-			).apply(compiler);
+		const AddManagedPathsPlugin = require("./cache/AddManagedPathsPlugin");
+
+		new AddManagedPathsPlugin(
+			/** @type {NonNullable} */
+			(options.snapshot.managedPaths),
+			/** @type {NonNullable} */
+			(options.snapshot.immutablePaths),
+			/** @type {NonNullable} */
+			(options.snapshot.unmanagedPaths)
+		).apply(compiler);
+
+		if (options.cache && typeof options.cache === "object") {
+			const cacheOptions = options.cache;
+			switch (cacheOptions.type) {
+				case "memory": {
+					if (Number.isFinite(cacheOptions.maxGenerations)) {
+						// @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697
+						const MemoryWithGcCachePlugin = require("./cache/MemoryWithGcCachePlugin");
+
+						new MemoryWithGcCachePlugin({
+							maxGenerations:
+								/** @type {number} */
+								(cacheOptions.maxGenerations)
+						}).apply(compiler);
+					} else {
+						// @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697
+						const MemoryCachePlugin = require("./cache/MemoryCachePlugin");
+
+						new MemoryCachePlugin().apply(compiler);
+					}
+					if (cacheOptions.cacheUnaffected) {
+						if (!options.experiments.cacheUnaffected) {
+							throw new Error(
+								"'cache.cacheUnaffected: true' is only allowed when 'experiments.cacheUnaffected' is enabled"
+							);
+						}
+						compiler.moduleMemCaches = new Map();
+					}
+					break;
+				}
+				case "filesystem": {
+					const AddBuildDependenciesPlugin = require("./cache/AddBuildDependenciesPlugin");
+
+					for (const key in cacheOptions.buildDependencies) {
+						const list = cacheOptions.buildDependencies[key];
+						new AddBuildDependenciesPlugin(list).apply(compiler);
+					}
+					if (!Number.isFinite(cacheOptions.maxMemoryGenerations)) {
+						// @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697
+						const MemoryCachePlugin = require("./cache/MemoryCachePlugin");
+
+						new MemoryCachePlugin().apply(compiler);
+					} else if (cacheOptions.maxMemoryGenerations !== 0) {
+						// @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697
+						const MemoryWithGcCachePlugin = require("./cache/MemoryWithGcCachePlugin");
+
+						new MemoryWithGcCachePlugin({
+							maxGenerations:
+								/** @type {number} */
+								(cacheOptions.maxMemoryGenerations)
+						}).apply(compiler);
+					}
+					if (cacheOptions.memoryCacheUnaffected) {
+						if (!options.experiments.cacheUnaffected) {
+							throw new Error(
+								"'cache.memoryCacheUnaffected: true' is only allowed when 'experiments.cacheUnaffected' is enabled"
+							);
+						}
+						compiler.moduleMemCaches = new Map();
+					}
+					switch (cacheOptions.store) {
+						case "pack": {
+							const IdleFileCachePlugin = require("./cache/IdleFileCachePlugin");
+							const PackFileCacheStrategy = require("./cache/PackFileCacheStrategy");
+
+							new IdleFileCachePlugin(
+								new PackFileCacheStrategy({
+									compiler,
+									fs:
+										/** @type {IntermediateFileSystem} */
+										(compiler.intermediateFileSystem),
+									context: options.context,
+									cacheLocation:
+										/** @type {string} */
+										(cacheOptions.cacheLocation),
+									version: /** @type {string} */ (cacheOptions.version),
+									logger: compiler.getInfrastructureLogger(
+										"webpack.cache.PackFileCacheStrategy"
+									),
+									snapshot: options.snapshot,
+									maxAge: /** @type {number} */ (cacheOptions.maxAge),
+									profile: cacheOptions.profile,
+									allowCollectingMemory: cacheOptions.allowCollectingMemory,
+									compression: cacheOptions.compression,
+									readonly: cacheOptions.readonly
+								}),
+								/** @type {number} */
+								(cacheOptions.idleTimeout),
+								/** @type {number} */
+								(cacheOptions.idleTimeoutForInitialStore),
+								/** @type {number} */
+								(cacheOptions.idleTimeoutAfterLargeChanges)
+							).apply(compiler);
+							break;
+						}
+						default:
+							throw new Error("Unhandled value for cache.store");
+					}
+					break;
+				}
+				default:
+					// @ts-expect-error Property 'type' does not exist on type 'never'. ts(2339)
+					throw new Error(`Unknown cache type ${cacheOptions.type}`);
+			}
+		}
+		new ResolverCachePlugin().apply(compiler);
+
+		if (options.ignoreWarnings && options.ignoreWarnings.length > 0) {
+			const IgnoreWarningsPlugin = require("./IgnoreWarningsPlugin");
+
+			new IgnoreWarningsPlugin(options.ignoreWarnings).apply(compiler);
 		}
 
 		compiler.hooks.afterPlugins.call(compiler);
@@ -393,37 +1124,31 @@ class WebpackOptionsApply extends OptionsApply {
 		}
 		compiler.resolverFactory.hooks.resolveOptions
 			.for("normal")
-			.tap("WebpackOptionsApply", resolveOptions => {
-				return Object.assign(
-					{
-						fileSystem: compiler.inputFileSystem
-					},
-					options.resolve,
-					resolveOptions
-				);
+			.tap(CLASS_NAME, (resolveOptions) => {
+				resolveOptions = cleverMerge(options.resolve, resolveOptions);
+				resolveOptions.fileSystem =
+					/** @type {InputFileSystem} */
+					(compiler.inputFileSystem);
+				return resolveOptions;
 			});
 		compiler.resolverFactory.hooks.resolveOptions
 			.for("context")
-			.tap("WebpackOptionsApply", resolveOptions => {
-				return Object.assign(
-					{
-						fileSystem: compiler.inputFileSystem,
-						resolveToContext: true
-					},
-					options.resolve,
-					resolveOptions
-				);
+			.tap(CLASS_NAME, (resolveOptions) => {
+				resolveOptions = cleverMerge(options.resolve, resolveOptions);
+				resolveOptions.fileSystem =
+					/** @type {InputFileSystem} */
+					(compiler.inputFileSystem);
+				resolveOptions.resolveToContext = true;
+				return resolveOptions;
 			});
 		compiler.resolverFactory.hooks.resolveOptions
 			.for("loader")
-			.tap("WebpackOptionsApply", resolveOptions => {
-				return Object.assign(
-					{
-						fileSystem: compiler.inputFileSystem
-					},
-					options.resolveLoader,
-					resolveOptions
-				);
+			.tap(CLASS_NAME, (resolveOptions) => {
+				resolveOptions = cleverMerge(options.resolveLoader, resolveOptions);
+				resolveOptions.fileSystem =
+					/** @type {InputFileSystem} */
+					(compiler.inputFileSystem);
+				return resolveOptions;
 			});
 		compiler.hooks.afterResolvers.call(compiler);
 		return options;
diff --git a/lib/WebpackOptionsDefaulter.js b/lib/WebpackOptionsDefaulter.js
index e15d58d7905..d05a217e737 100644
--- a/lib/WebpackOptionsDefaulter.js
+++ b/lib/WebpackOptionsDefaulter.js
@@ -2,348 +2,25 @@
 	MIT License http://www.opensource.org/licenses/mit-license.php
 	Author Tobias Koppers @sokra
 */
-"use strict";
-
-const path = require("path");
-
-const OptionsDefaulter = require("./OptionsDefaulter");
-const Template = require("./Template");
-
-const isProductionLikeMode = options => {
-	return options.mode === "production" || !options.mode;
-};
-
-const isWebLikeTarget = options => {
-	return options.target === "web" || options.target === "webworker";
-};
-
-class WebpackOptionsDefaulter extends OptionsDefaulter {
-	constructor() {
-		super();
-
-		this.set("entry", "./src");
-
-		this.set(
-			"devtool",
-			"make",
-			options => (options.mode === "development" ? "eval" : false)
-		);
-		this.set("cache", "make", options => options.mode === "development");
 
-		this.set("context", process.cwd());
-		this.set("target", "web");
-
-		this.set("module", "call", value => Object.assign({}, value));
-		this.set("module.unknownContextRequest", ".");
-		this.set("module.unknownContextRegExp", false);
-		this.set("module.unknownContextRecursive", true);
-		this.set("module.unknownContextCritical", true);
-		this.set("module.exprContextRequest", ".");
-		this.set("module.exprContextRegExp", false);
-		this.set("module.exprContextRecursive", true);
-		this.set("module.exprContextCritical", true);
-		this.set("module.wrappedContextRegExp", /.*/);
-		this.set("module.wrappedContextRecursive", true);
-		this.set("module.wrappedContextCritical", false);
-		this.set("module.strictExportPresence", false);
-		this.set("module.strictThisContextOnImports", false);
-		this.set("module.unsafeCache", "make", options => !!options.cache);
-		this.set("module.rules", []);
-		this.set("module.defaultRules", "make", options => [
-			{
-				type: "javascript/auto",
-				resolve: {}
-			},
-			{
-				test: /\.mjs$/i,
-				type: "javascript/esm",
-				resolve: {
-					mainFields:
-						options.target === "web" ||
-						options.target === "webworker" ||
-						options.target === "electron-renderer"
-							? ["browser", "main"]
-							: ["main"]
-				}
-			},
-			{
-				test: /\.json$/i,
-				type: "json"
-			},
-			{
-				test: /\.wasm$/i,
-				type: "webassembly/experimental"
-			}
-		]);
-
-		this.set("output", "call", (value, options) => {
-			if (typeof value === "string") {
-				return {
-					filename: value
-				};
-			} else if (typeof value !== "object") {
-				return {};
-			} else {
-				return Object.assign({}, value);
-			}
-		});
-
-		this.set("output.filename", "[name].js");
-		this.set("output.chunkFilename", "make", options => {
-			const filename = options.output.filename;
-			if (typeof filename !== "function") {
-				const hasName = filename.includes("[name]");
-				const hasId = filename.includes("[id]");
-				const hasChunkHash = filename.includes("[chunkhash]");
-				// Anything changing depending on chunk is fine
-				if (hasChunkHash || hasName || hasId) return filename;
-				// Elsewise prefix "[id]." in front of the basename to make it changing
-				return filename.replace(/(^|\/)([^/]*(?:\?|$))/, "$1[id].$2");
-			}
-			return "[id].js";
-		});
-		this.set("output.webassemblyModuleFilename", "[modulehash].module.wasm");
-		this.set("output.library", "");
-		this.set("output.hotUpdateFunction", "make", options => {
-			return Template.toIdentifier(
-				"webpackHotUpdate" + Template.toIdentifier(options.output.library)
-			);
-		});
-		this.set("output.jsonpFunction", "make", options => {
-			return Template.toIdentifier(
-				"webpackJsonp" + Template.toIdentifier(options.output.library)
-			);
-		});
-		this.set("output.chunkCallbackName", "make", options => {
-			return Template.toIdentifier(
-				"webpackChunk" + Template.toIdentifier(options.output.library)
-			);
-		});
-		this.set("output.globalObject", "make", options => {
-			switch (options.target) {
-				case "web":
-				case "electron-renderer":
-				case "node-webkit":
-					return "window";
-				case "webworker":
-					return "self";
-				case "node":
-				case "async-node":
-				case "electron-main":
-					return "global";
-				default:
-					return "self";
-			}
-		});
-		this.set("output.devtoolNamespace", "make", options => {
-			if (Array.isArray(options.output.library)) {
-				return options.output.library.join(".");
-			} else if (typeof options.output.library === "object") {
-				return options.output.library.root || "";
-			}
-			return options.output.library || "";
-		});
-		this.set("output.libraryTarget", "var");
-		this.set("output.path", path.join(process.cwd(), "dist"));
-		this.set(
-			"output.pathinfo",
-			"make",
-			options => options.mode === "development"
-		);
-		this.set("output.sourceMapFilename", "[file].map[query]");
-		this.set("output.hotUpdateChunkFilename", "[id].[hash].hot-update.js");
-		this.set("output.hotUpdateMainFilename", "[hash].hot-update.json");
-		this.set("output.crossOriginLoading", false);
-		this.set("output.jsonpScriptType", false);
-		this.set("output.chunkLoadTimeout", 120000);
-		this.set("output.hashFunction", "md4");
-		this.set("output.hashDigest", "hex");
-		this.set("output.hashDigestLength", 20);
-		this.set("output.devtoolLineToLine", false);
-		this.set("output.strictModuleExceptionHandling", false);
-
-		this.set("node", "call", value => {
-			if (typeof value === "boolean") {
-				return value;
-			} else {
-				return Object.assign({}, value);
-			}
-		});
-		this.set("node.console", false);
-		this.set("node.process", true);
-		this.set("node.global", true);
-		this.set("node.Buffer", true);
-		this.set("node.setImmediate", true);
-		this.set("node.__filename", "mock");
-		this.set("node.__dirname", "mock");
-
-		this.set("performance", "call", (value, options) => {
-			if (value === false) return false;
-			if (
-				value === undefined &&
-				(!isProductionLikeMode(options) || !isWebLikeTarget(options))
-			)
-				return false;
-			return Object.assign({}, value);
-		});
-		this.set("performance.maxAssetSize", 250000);
-		this.set("performance.maxEntrypointSize", 250000);
-		this.set(
-			"performance.hints",
-			"make",
-			options => (isProductionLikeMode(options) ? "warning" : false)
-		);
-
-		this.set("optimization", "call", value => Object.assign({}, value));
-		this.set("optimization.removeAvailableModules", true);
-		this.set("optimization.removeEmptyChunks", true);
-		this.set("optimization.mergeDuplicateChunks", true);
-		this.set("optimization.flagIncludedChunks", "make", options =>
-			isProductionLikeMode(options)
-		);
-		this.set("optimization.occurrenceOrder", "make", options =>
-			isProductionLikeMode(options)
-		);
-		this.set("optimization.sideEffects", "make", options =>
-			isProductionLikeMode(options)
-		);
-		this.set("optimization.providedExports", true);
-		this.set("optimization.usedExports", "make", options =>
-			isProductionLikeMode(options)
-		);
-		this.set("optimization.concatenateModules", "make", options =>
-			isProductionLikeMode(options)
-		);
-		this.set("optimization.splitChunks", {});
-		this.set("optimization.splitChunks.chunks", "async");
-		this.set("optimization.splitChunks.minSize", "make", options => {
-			return isProductionLikeMode(options) ? 30000 : 10000;
-		});
-		this.set("optimization.splitChunks.minChunks", 1);
-		this.set("optimization.splitChunks.maxAsyncRequests", "make", options => {
-			return isProductionLikeMode(options) ? 5 : Infinity;
-		});
-		this.set("optimization.splitChunks.automaticNameDelimiter", "~");
-		this.set("optimization.splitChunks.maxInitialRequests", "make", options => {
-			return isProductionLikeMode(options) ? 3 : Infinity;
-		});
-		this.set("optimization.splitChunks.name", true);
-		this.set("optimization.splitChunks.cacheGroups", {});
-		this.set("optimization.splitChunks.cacheGroups.default", {
-			reuseExistingChunk: true,
-			minChunks: 2,
-			priority: -20
-		});
-		this.set("optimization.splitChunks.cacheGroups.vendors", {
-			test: /[\\/]node_modules[\\/]/,
-			priority: -10
-		});
-		this.set("optimization.runtimeChunk", "call", value => {
-			if (value === "single") {
-				return {
-					name: "runtime"
-				};
-			}
-			if (value === true || value === "multiple") {
-				return {
-					name: entrypoint => `runtime~${entrypoint.name}`
-				};
-			}
-			return value;
-		});
-		this.set("optimization.noEmitOnErrors", "make", options =>
-			isProductionLikeMode(options)
-		);
-		this.set("optimization.checkWasmTypes", "make", options =>
-			isProductionLikeMode(options)
-		);
-		this.set("optimization.mangleWasmImports", false);
-		this.set(
-			"optimization.namedModules",
-			"make",
-			options => options.mode === "development"
-		);
-		this.set(
-			"optimization.namedChunks",
-			"make",
-			options => options.mode === "development"
-		);
-		this.set(
-			"optimization.portableRecords",
-			"make",
-			options =>
-				!!(
-					options.recordsInputPath ||
-					options.recordsOutputPath ||
-					options.recordsPath
-				)
-		);
-		this.set("optimization.minimize", "make", options =>
-			isProductionLikeMode(options)
-		);
-		this.set("optimization.minimizer", "make", options => [
-			{
-				apply: compiler => {
-					// Lazy load the uglifyjs plugin
-					const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
-					const SourceMapDevToolPlugin = require("./SourceMapDevToolPlugin");
-					new UglifyJsPlugin({
-						cache: true,
-						parallel: true,
-						sourceMap:
-							(options.devtool && /source-?map/.test(options.devtool)) ||
-							(options.plugins &&
-								options.plugins.some(p => p instanceof SourceMapDevToolPlugin))
-					}).apply(compiler);
-				}
-			}
-		]);
-		this.set("optimization.nodeEnv", "make", options => {
-			// TODO: In webpack 5, it should return `false` when mode is `none`
-			return options.mode || "production";
-		});
-
-		this.set("resolve", "call", value => Object.assign({}, value));
-		this.set("resolve.unsafeCache", true);
-		this.set("resolve.modules", ["node_modules"]);
-		this.set("resolve.extensions", [".wasm", ".mjs", ".js", ".json"]);
-		this.set("resolve.mainFiles", ["index"]);
-		this.set("resolve.aliasFields", "make", options => {
-			if (options.target === "web" || options.target === "webworker") {
-				return ["browser"];
-			} else {
-				return [];
-			}
-		});
-		this.set("resolve.mainFields", "make", options => {
-			if (
-				options.target === "web" ||
-				options.target === "webworker" ||
-				options.target === "electron-renderer"
-			) {
-				return ["browser", "module", "main"];
-			} else {
-				return ["module", "main"];
-			}
-		});
-		this.set("resolve.cacheWithContext", "make", options => {
-			return (
-				Array.isArray(options.resolve.plugins) &&
-				options.resolve.plugins.length > 0
-			);
-		});
+"use strict";
 
-		this.set("resolveLoader", "call", value => Object.assign({}, value));
-		this.set("resolveLoader.unsafeCache", true);
-		this.set("resolveLoader.mainFields", ["loader", "main"]);
-		this.set("resolveLoader.extensions", [".js", ".json"]);
-		this.set("resolveLoader.mainFiles", ["index"]);
-		this.set("resolveLoader.cacheWithContext", "make", options => {
-			return (
-				Array.isArray(options.resolveLoader.plugins) &&
-				options.resolveLoader.plugins.length > 0
-			);
-		});
+const { applyWebpackOptionsDefaults } = require("./config/defaults");
+const { getNormalizedWebpackOptions } = require("./config/normalization");
+
+/** @typedef {import("./config/normalization").WebpackOptions} WebpackOptions */
+/** @typedef {import("./config/normalization").WebpackOptionsNormalized} WebpackOptionsNormalized */
+
+class WebpackOptionsDefaulter {
+	/**
+	 * Returns normalized webpack options.
+	 * @param {WebpackOptions} options webpack options
+	 * @returns {WebpackOptionsNormalized} normalized webpack options
+	 */
+	process(options) {
+		const normalizedOptions = getNormalizedWebpackOptions(options);
+		applyWebpackOptionsDefaults(normalizedOptions);
+		return normalizedOptions;
 	}
 }
 
diff --git a/lib/WebpackOptionsValidationError.js b/lib/WebpackOptionsValidationError.js
deleted file mode 100644
index 3fdbd0df6ba..00000000000
--- a/lib/WebpackOptionsValidationError.js
+++ /dev/null
@@ -1,345 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Gajus Kuizinas @gajus
-*/
-"use strict";
-
-const WebpackError = require("./WebpackError");
-const webpackOptionsSchema = require("../schemas/WebpackOptions.json");
-
-const getSchemaPart = (path, parents, additionalPath) => {
-	parents = parents || 0;
-	path = path.split("/");
-	path = path.slice(0, path.length - parents);
-	if (additionalPath) {
-		additionalPath = additionalPath.split("/");
-		path = path.concat(additionalPath);
-	}
-	let schemaPart = webpackOptionsSchema;
-	for (let i = 1; i < path.length; i++) {
-		const inner = schemaPart[path[i]];
-		if (inner) schemaPart = inner;
-	}
-	return schemaPart;
-};
-
-const getSchemaPartText = (schemaPart, additionalPath) => {
-	if (additionalPath) {
-		for (let i = 0; i < additionalPath.length; i++) {
-			const inner = schemaPart[additionalPath[i]];
-			if (inner) schemaPart = inner;
-		}
-	}
-	while (schemaPart.$ref) {
-		schemaPart = getSchemaPart(schemaPart.$ref);
-	}
-	let schemaText = WebpackOptionsValidationError.formatSchema(schemaPart);
-	if (schemaPart.description) {
-		schemaText += `\n-> ${schemaPart.description}`;
-	}
-	return schemaText;
-};
-
-const getSchemaPartDescription = schemaPart => {
-	while (schemaPart.$ref) {
-		schemaPart = getSchemaPart(schemaPart.$ref);
-	}
-	if (schemaPart.description) {
-		return `\n-> ${schemaPart.description}`;
-	}
-	return "";
-};
-
-const filterChildren = children => {
-	return children.filter(
-		err =>
-			err.keyword !== "anyOf" &&
-			err.keyword !== "allOf" &&
-			err.keyword !== "oneOf"
-	);
-};
-
-const indent = (str, prefix, firstLine) => {
-	if (firstLine) {
-		return prefix + str.replace(/\n(?!$)/g, "\n" + prefix);
-	} else {
-		return str.replace(/\n(?!$)/g, `\n${prefix}`);
-	}
-};
-
-class WebpackOptionsValidationError extends WebpackError {
-	constructor(validationErrors) {
-		super(
-			"Invalid configuration object. " +
-				"Webpack has been initialised using a configuration object that does not match the API schema.\n" +
-				validationErrors
-					.map(
-						err =>
-							" - " +
-							indent(
-								WebpackOptionsValidationError.formatValidationError(err),
-								"   ",
-								false
-							)
-					)
-					.join("\n")
-		);
-
-		this.name = "WebpackOptionsValidationError";
-		this.validationErrors = validationErrors;
-
-		Error.captureStackTrace(this, this.constructor);
-	}
-
-	static formatSchema(schema, prevSchemas) {
-		prevSchemas = prevSchemas || [];
-
-		const formatInnerSchema = (innerSchema, addSelf) => {
-			if (!addSelf) {
-				return WebpackOptionsValidationError.formatSchema(
-					innerSchema,
-					prevSchemas
-				);
-			}
-			if (prevSchemas.includes(innerSchema)) {
-				return "(recursive)";
-			}
-			return WebpackOptionsValidationError.formatSchema(
-				innerSchema,
-				prevSchemas.concat(schema)
-			);
-		};
-
-		if (schema.type === "string") {
-			if (schema.minLength === 1) {
-				return "non-empty string";
-			}
-			if (schema.minLength > 1) {
-				return `string (min length ${schema.minLength})`;
-			}
-			return "string";
-		}
-		if (schema.type === "boolean") {
-			return "boolean";
-		}
-		if (schema.type === "number") {
-			return "number";
-		}
-		if (schema.type === "object") {
-			if (schema.properties) {
-				const required = schema.required || [];
-				return `object { ${Object.keys(schema.properties)
-					.map(property => {
-						if (!required.includes(property)) return property + "?";
-						return property;
-					})
-					.concat(schema.additionalProperties ? ["…"] : [])
-					.join(", ")} }`;
-			}
-			if (schema.additionalProperties) {
-				return `object { : ${formatInnerSchema(
-					schema.additionalProperties
-				)} }`;
-			}
-			return "object";
-		}
-		if (schema.type === "array") {
-			return `[${formatInnerSchema(schema.items)}]`;
-		}
-
-		switch (schema.instanceof) {
-			case "Function":
-				return "function";
-			case "RegExp":
-				return "RegExp";
-		}
-
-		if (schema.$ref) {
-			return formatInnerSchema(getSchemaPart(schema.$ref), true);
-		}
-		if (schema.allOf) {
-			return schema.allOf.map(formatInnerSchema).join(" & ");
-		}
-		if (schema.oneOf) {
-			return schema.oneOf.map(formatInnerSchema).join(" | ");
-		}
-		if (schema.anyOf) {
-			return schema.anyOf.map(formatInnerSchema).join(" | ");
-		}
-		if (schema.enum) {
-			return schema.enum.map(item => JSON.stringify(item)).join(" | ");
-		}
-		return JSON.stringify(schema, null, 2);
-	}
-
-	static formatValidationError(err) {
-		const dataPath = `configuration${err.dataPath}`;
-		if (err.keyword === "additionalProperties") {
-			const baseMessage = `${dataPath} has an unknown property '${
-				err.params.additionalProperty
-			}'. These properties are valid:\n${getSchemaPartText(err.parentSchema)}`;
-			if (!err.dataPath) {
-				switch (err.params.additionalProperty) {
-					case "debug":
-						return (
-							`${baseMessage}\n` +
-							"The 'debug' property was removed in webpack 2.0.0.\n" +
-							"Loaders should be updated to allow passing this option via loader options in module.rules.\n" +
-							"Until loaders are updated one can use the LoaderOptionsPlugin to switch loaders into debug mode:\n" +
-							"plugins: [\n" +
-							"  new webpack.LoaderOptionsPlugin({\n" +
-							"    debug: true\n" +
-							"  })\n" +
-							"]"
-						);
-				}
-				return (
-					`${baseMessage}\n` +
-					"For typos: please correct them.\n" +
-					"For loader options: webpack >= v2.0.0 no longer allows custom properties in configuration.\n" +
-					"  Loaders should be updated to allow passing options via loader options in module.rules.\n" +
-					"  Until loaders are updated one can use the LoaderOptionsPlugin to pass these options to the loader:\n" +
-					"  plugins: [\n" +
-					"    new webpack.LoaderOptionsPlugin({\n" +
-					"      // test: /\\.xxx$/, // may apply this only for some modules\n" +
-					"      options: {\n" +
-					`        ${err.params.additionalProperty}: …\n` +
-					"      }\n" +
-					"    })\n" +
-					"  ]"
-				);
-			}
-			return baseMessage;
-		} else if (err.keyword === "oneOf" || err.keyword === "anyOf") {
-			if (err.children && err.children.length > 0) {
-				if (err.schema.length === 1) {
-					const lastChild = err.children[err.children.length - 1];
-					const remainingChildren = err.children.slice(
-						0,
-						err.children.length - 1
-					);
-					return WebpackOptionsValidationError.formatValidationError(
-						Object.assign({}, lastChild, {
-							children: remainingChildren,
-							parentSchema: Object.assign(
-								{},
-								err.parentSchema,
-								lastChild.parentSchema
-							)
-						})
-					);
-				}
-				return (
-					`${dataPath} should be one of these:\n${getSchemaPartText(
-						err.parentSchema
-					)}\n` +
-					`Details:\n${filterChildren(err.children)
-						.map(
-							err =>
-								" * " +
-								indent(
-									WebpackOptionsValidationError.formatValidationError(err),
-									"   ",
-									false
-								)
-						)
-						.join("\n")}`
-				);
-			}
-			return `${dataPath} should be one of these:\n${getSchemaPartText(
-				err.parentSchema
-			)}`;
-		} else if (err.keyword === "enum") {
-			if (
-				err.parentSchema &&
-				err.parentSchema.enum &&
-				err.parentSchema.enum.length === 1
-			) {
-				return `${dataPath} should be ${getSchemaPartText(err.parentSchema)}`;
-			}
-			return `${dataPath} should be one of these:\n${getSchemaPartText(
-				err.parentSchema
-			)}`;
-		} else if (err.keyword === "allOf") {
-			return `${dataPath} should be:\n${getSchemaPartText(err.parentSchema)}`;
-		} else if (err.keyword === "type") {
-			switch (err.params.type) {
-				case "object":
-					return `${dataPath} should be an object.${getSchemaPartDescription(
-						err.parentSchema
-					)}`;
-				case "string":
-					return `${dataPath} should be a string.${getSchemaPartDescription(
-						err.parentSchema
-					)}`;
-				case "boolean":
-					return `${dataPath} should be a boolean.${getSchemaPartDescription(
-						err.parentSchema
-					)}`;
-				case "number":
-					return `${dataPath} should be a number.${getSchemaPartDescription(
-						err.parentSchema
-					)}`;
-				case "array":
-					return `${dataPath} should be an array:\n${getSchemaPartText(
-						err.parentSchema
-					)}`;
-			}
-			return `${dataPath} should be ${err.params.type}:\n${getSchemaPartText(
-				err.parentSchema
-			)}`;
-		} else if (err.keyword === "instanceof") {
-			return `${dataPath} should be an instance of ${getSchemaPartText(
-				err.parentSchema
-			)}`;
-		} else if (err.keyword === "required") {
-			const missingProperty = err.params.missingProperty.replace(/^\./, "");
-			return `${dataPath} misses the property '${missingProperty}'.\n${getSchemaPartText(
-				err.parentSchema,
-				["properties", missingProperty]
-			)}`;
-		} else if (err.keyword === "minimum") {
-			return `${dataPath} ${err.message}.${getSchemaPartDescription(
-				err.parentSchema
-			)}`;
-		} else if (err.keyword === "uniqueItems") {
-			return `${dataPath} should not contain the item '${
-				err.data[err.params.i]
-			}' twice.${getSchemaPartDescription(err.parentSchema)}`;
-		} else if (
-			err.keyword === "minLength" ||
-			err.keyword === "minItems" ||
-			err.keyword === "minProperties"
-		) {
-			if (err.params.limit === 1) {
-				return `${dataPath} should not be empty.${getSchemaPartDescription(
-					err.parentSchema
-				)}`;
-			} else {
-				return `${dataPath} ${err.message}${getSchemaPartDescription(
-					err.parentSchema
-				)}`;
-			}
-		} else if (err.keyword === "absolutePath") {
-			const baseMessage = `${dataPath}: ${
-				err.message
-			}${getSchemaPartDescription(err.parentSchema)}`;
-			if (dataPath === "configuration.output.filename") {
-				return (
-					`${baseMessage}\n` +
-					"Please use output.path to specify absolute path and output.filename for the file name."
-				);
-			}
-			return baseMessage;
-		} else {
-			// eslint-disable-line no-fallthrough
-			return `${dataPath} ${err.message} (${JSON.stringify(
-				err,
-				null,
-				2
-			)}).\n${getSchemaPartText(err.parentSchema)}`;
-		}
-	}
-}
-
-module.exports = WebpackOptionsValidationError;
diff --git a/lib/asset/AssetBytesGenerator.js b/lib/asset/AssetBytesGenerator.js
new file mode 100644
index 00000000000..3e6b7990ce7
--- /dev/null
+++ b/lib/asset/AssetBytesGenerator.js
@@ -0,0 +1,190 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Alexander Akait @alexander-akait
+*/
+
+"use strict";
+
+const { RawSource } = require("webpack-sources");
+const ConcatenationScope = require("../ConcatenationScope");
+const Generator = require("../Generator");
+const {
+	ASSET_URL_TYPE,
+	ASSET_URL_TYPES,
+	CSS_TYPE,
+	HTML_TYPE,
+	JAVASCRIPT_AND_ASSET_URL_TYPES,
+	JAVASCRIPT_TYPE,
+	JAVASCRIPT_TYPES,
+	NO_TYPES
+} = require("../ModuleSourceTypeConstants");
+const RuntimeGlobals = require("../RuntimeGlobals");
+
+/** @typedef {import("webpack-sources").Source} Source */
+/** @typedef {import("../Generator").GenerateContext} GenerateContext */
+/** @typedef {import("../Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */
+/** @typedef {import("../Module").SourceType} SourceType */
+/** @typedef {import("../Module").SourceTypes} SourceTypes */
+/** @typedef {import("../ModuleGraph")} ModuleGraph */
+/** @typedef {import("../NormalModule")} NormalModule */
+
+class AssetBytesGenerator extends Generator {
+	/**
+	 * Creates an instance of AssetBytesGenerator.
+	 * @param {ModuleGraph} moduleGraph the module graph
+	 */
+	constructor(moduleGraph) {
+		super();
+
+		/** @type {ModuleGraph} */
+		this._moduleGraph = moduleGraph;
+	}
+
+	/**
+	 * Generates generated code for this runtime module.
+	 * @param {NormalModule} module module for which the code should be generated
+	 * @param {GenerateContext} generateContext context for generate
+	 * @returns {Source | null} generated code
+	 */
+	generate(
+		module,
+		{ type, concatenationScope, getData, runtimeTemplate, runtimeRequirements }
+	) {
+		const originalSource = module.originalSource();
+		const data = getData ? getData() : undefined;
+
+		switch (type) {
+			case JAVASCRIPT_TYPE: {
+				if (!originalSource) {
+					return new RawSource("");
+				}
+
+				const encodedSource = originalSource.buffer().toString("base64");
+
+				runtimeRequirements.add(RuntimeGlobals.requireScope);
+				runtimeRequirements.add(RuntimeGlobals.toBinary);
+
+				/** @type {string} */
+				let sourceContent;
+				if (concatenationScope) {
+					concatenationScope.registerNamespaceExport(
+						ConcatenationScope.NAMESPACE_OBJECT_EXPORT
+					);
+					sourceContent = `${runtimeTemplate.renderConst()} ${
+						ConcatenationScope.NAMESPACE_OBJECT_EXPORT
+					} = ${RuntimeGlobals.toBinary}(${JSON.stringify(encodedSource)});`;
+				} else {
+					runtimeRequirements.add(RuntimeGlobals.module);
+					sourceContent = `${module.moduleArgument}.exports = ${RuntimeGlobals.toBinary}(${JSON.stringify(
+						encodedSource
+					)});`;
+				}
+				return new RawSource(sourceContent);
+			}
+			case ASSET_URL_TYPE: {
+				if (!originalSource) {
+					return null;
+				}
+
+				const encodedSource = originalSource.buffer().toString("base64");
+
+				if (data) {
+					data.set("url", {
+						[type]: `data:application/octet-stream;base64,${encodedSource}`
+					});
+				}
+				return null;
+			}
+			default:
+				return null;
+		}
+	}
+
+	/**
+	 * Generates fallback output for the provided error condition.
+	 * @param {Error} error the error
+	 * @param {NormalModule} module module for which the code should be generated
+	 * @param {GenerateContext} generateContext context for generate
+	 * @returns {Source | null} generated code
+	 */
+	generateError(error, module, generateContext) {
+		switch (generateContext.type) {
+			case JAVASCRIPT_TYPE: {
+				return new RawSource(
+					`throw new Error(${JSON.stringify(error.message)});`
+				);
+			}
+			default:
+				return null;
+		}
+	}
+
+	/**
+	 * Returns the reason this module cannot be concatenated, when one exists.
+	 * @param {NormalModule} module module for which the bailout reason should be determined
+	 * @param {ConcatenationBailoutReasonContext} context context
+	 * @returns {string | undefined} reason why this module can't be concatenated, undefined when it can be concatenated
+	 */
+	getConcatenationBailoutReason(module, context) {
+		return undefined;
+	}
+
+	/**
+	 * Returns the source types available for this module.
+	 * @param {NormalModule} module fresh module
+	 * @returns {SourceTypes} available types (do not mutate)
+	 */
+	getTypes(module) {
+		/** @type {Set} */
+		const sourceTypes = new Set();
+		const connections = this._moduleGraph.getIncomingConnections(module);
+
+		for (const connection of connections) {
+			if (!connection.originModule) {
+				continue;
+			}
+
+			sourceTypes.add(connection.originModule.type.split("/")[0]);
+		}
+
+		if (sourceTypes.size > 0) {
+			if (
+				sourceTypes.has(JAVASCRIPT_TYPE) &&
+				(sourceTypes.has(CSS_TYPE) || sourceTypes.has(HTML_TYPE))
+			) {
+				return JAVASCRIPT_AND_ASSET_URL_TYPES;
+			} else if (sourceTypes.has(CSS_TYPE) || sourceTypes.has(HTML_TYPE)) {
+				return ASSET_URL_TYPES;
+			}
+			return JAVASCRIPT_TYPES;
+		}
+
+		return NO_TYPES;
+	}
+
+	/**
+	 * @returns {boolean} whether getTypes() depends on the module's incoming connections
+	 */
+	getTypesDependOnIncomingConnections() {
+		return true;
+	}
+
+	/**
+	 * Returns the estimated size for the requested source type.
+	 * @param {NormalModule} module the module
+	 * @param {SourceType=} type source type
+	 * @returns {number} estimate size of the module
+	 */
+	getSize(module, type) {
+		const originalSource = module.originalSource();
+
+		if (!originalSource) {
+			return 0;
+		}
+
+		// Example: m.exports="abcd"
+		return originalSource.size() + 12;
+	}
+}
+
+module.exports = AssetBytesGenerator;
diff --git a/lib/asset/AssetBytesParser.js b/lib/asset/AssetBytesParser.js
new file mode 100644
index 00000000000..0e4eea4d0ed
--- /dev/null
+++ b/lib/asset/AssetBytesParser.js
@@ -0,0 +1,38 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Alexander Akait @alexander-akait
+*/
+
+"use strict";
+
+const Parser = require("../Parser");
+
+/** @typedef {import("../Module").BuildInfo} BuildInfo */
+/** @typedef {import("../Module").BuildMeta} BuildMeta */
+/** @typedef {import("../Parser").ParserState} ParserState */
+/** @typedef {import("../Parser").PreparsedAst} PreparsedAst */
+
+class AssetBytesParser extends Parser {
+	/**
+	 * Parses the provided source and updates the parser state.
+	 * @param {string | Buffer | PreparsedAst} source the source to parse
+	 * @param {ParserState} state the parser state
+	 * @returns {ParserState} the parser state
+	 */
+	parse(source, state) {
+		if (typeof source === "object" && !Buffer.isBuffer(source)) {
+			throw new Error("AssetBytesParser doesn't accept preparsed AST");
+		}
+		const { module } = state;
+		/** @type {BuildInfo} */
+		(module.buildInfo).strict = true;
+		/** @type {BuildMeta} */
+		(module.buildMeta).exportsType = "default";
+		/** @type {BuildMeta} */
+		(state.module.buildMeta).defaultObject = false;
+
+		return state;
+	}
+}
+
+module.exports = AssetBytesParser;
diff --git a/lib/asset/AssetGenerator.js b/lib/asset/AssetGenerator.js
new file mode 100644
index 00000000000..bfb91ac021f
--- /dev/null
+++ b/lib/asset/AssetGenerator.js
@@ -0,0 +1,997 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Sergey Melyukov @smelukov
+*/
+
+"use strict";
+
+const path = require("path");
+const { RawSource } = require("webpack-sources");
+const ConcatenationScope = require("../ConcatenationScope");
+const Generator = require("../Generator");
+const {
+	ASSET_AND_ASSET_URL_TYPES,
+	ASSET_AND_JAVASCRIPT_AND_ASSET_URL_TYPES,
+	ASSET_AND_JAVASCRIPT_TYPES,
+	ASSET_TYPES,
+	ASSET_URL_TYPE,
+	ASSET_URL_TYPES,
+	JAVASCRIPT_AND_ASSET_URL_TYPES,
+	JAVASCRIPT_TYPE,
+	JAVASCRIPT_TYPES,
+	NO_TYPES
+} = require("../ModuleSourceTypeConstants");
+const { ASSET_MODULE_TYPE } = require("../ModuleTypeConstants");
+const RuntimeGlobals = require("../RuntimeGlobals");
+const { toTemplateSourceFileName } = require("../TemplatedPathPlugin");
+const URLDependency = require("../dependencies/URLDependency");
+const { getScheme } = require("../util/URLAbsoluteSpecifier");
+const createHash = require("../util/createHash");
+const { makePathsRelative } = require("../util/identifier");
+const memoize = require("../util/memoize");
+const nonNumericOnlyHash = require("../util/nonNumericOnlyHash");
+const {
+	PUBLIC_PATH_AUTO,
+	PUBLIC_PATH_FULL_HASH
+} = require("../util/publicPathPlaceholder");
+const { updateHashFromSource } = require("../util/source");
+
+const getMimeTypes = memoize(() => require("../util/mimeTypes"));
+
+/** @typedef {import("webpack-sources").Source} Source */
+/** @typedef {import("../../declarations/WebpackOptions").AssetGeneratorDataUrlOptions} AssetGeneratorDataUrlOptions */
+/** @typedef {import("../../declarations/WebpackOptions").AssetGeneratorOptions} AssetGeneratorOptions */
+/** @typedef {import("../../declarations/WebpackOptions").AssetModuleFilename} AssetModuleFilename */
+/** @typedef {import("../../declarations/WebpackOptions").AssetModuleOutputPath} AssetModuleOutputPath */
+/** @typedef {import("../../declarations/WebpackOptions").AssetResourceGeneratorOptions} AssetResourceGeneratorOptions */
+/** @typedef {import("../../declarations/WebpackOptions").RawPublicPath} RawPublicPath */
+/** @typedef {import("../ChunkGraph")} ChunkGraph */
+/** @typedef {import("../Compilation")} Compilation */
+/** @typedef {import("../Compilation").AssetInfo} AssetInfo */
+/** @typedef {import("../Generator").GenerateContext} GenerateContext */
+/** @typedef {import("../Generator").UpdateHashContext} UpdateHashContext */
+/** @typedef {import("../Module")} Module */
+/** @typedef {import("../Module").NameForCondition} NameForCondition */
+/** @typedef {import("./AssetModule").AssetModuleBuildInfo} AssetModuleBuildInfo */
+/** @typedef {import("../Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */
+/** @typedef {import("../Module").SourceType} SourceType */
+/** @typedef {import("../Module").SourceTypes} SourceTypes */
+/** @typedef {import("../ModuleGraph")} ModuleGraph */
+/** @typedef {import("../NormalModule")} NormalModule */
+/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */
+/** @typedef {import("../util/Hash")} Hash */
+/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
+
+/** @typedef {(source: string | Buffer, context: { filename: string, module: Module }) => string} DataUrlFunction */
+
+/**
+ * Merges maybe arrays.
+ * @template T
+ * @template U
+ * @param {null | string | T[] | Set | undefined} a a
+ * @param {null | string | U[] | Set | undefined} b b
+ * @returns {T[] & U[]} array
+ */
+const mergeMaybeArrays = (a, b) => {
+	/** @type {Set | Set>} */
+	const set = new Set();
+	if (Array.isArray(a)) for (const item of a) set.add(item);
+	else set.add(a);
+	if (Array.isArray(b)) for (const item of b) set.add(item);
+	else set.add(b);
+	return /** @type {T[] & U[]} */ ([.../** @type {Set} */ (set)]);
+};
+
+/**
+ * Merges the provided values into a single result.
+ * @param {AssetInfo} a a
+ * @param {AssetInfo} b b
+ * @returns {AssetInfo} object
+ */
+const mergeAssetInfo = (a, b) => {
+	/** @type {AssetInfo} */
+	const result = { ...a, ...b };
+	for (const key of Object.keys(a)) {
+		if (key in b) {
+			if (a[key] === b[key]) continue;
+			switch (key) {
+				case "fullhash":
+				case "chunkhash":
+				case "modulehash":
+				case "contenthash":
+					result[key] = mergeMaybeArrays(a[key], b[key]);
+					break;
+				case "immutable":
+				case "development":
+				case "hotModuleReplacement":
+				case "javascriptModule":
+					result[key] = a[key] || b[key];
+					break;
+				case "related":
+					result[key] = mergeRelatedInfo(
+						/** @type {NonNullable} */
+						(a[key]),
+						/** @type {NonNullable} */
+						(b[key])
+					);
+					break;
+				default:
+					throw new Error(`Can't handle conflicting asset info for ${key}`);
+			}
+		}
+	}
+	return result;
+};
+
+/**
+ * Merges related info.
+ * @param {NonNullable} a a
+ * @param {NonNullable} b b
+ * @returns {NonNullable} object
+ */
+const mergeRelatedInfo = (a, b) => {
+	const result = { ...a, ...b };
+	for (const key of Object.keys(a)) {
+		if (key in b) {
+			if (a[key] === b[key]) continue;
+			result[key] = mergeMaybeArrays(a[key], b[key]);
+		}
+	}
+	return result;
+};
+
+/**
+ * Encodes the provided encoding.
+ * @param {"base64" | false} encoding encoding
+ * @param {Source} source source
+ * @returns {string} encoded data
+ */
+const encodeDataUri = (encoding, source) => {
+	/** @type {string | undefined} */
+	let encodedContent;
+
+	switch (encoding) {
+		case "base64": {
+			encodedContent = source.buffer().toString("base64");
+			break;
+		}
+		case false: {
+			const content = source.source();
+
+			encodedContent =
+				typeof content === "string" ? content : content.toString("utf8");
+
+			encodedContent = encodeURIComponent(encodedContent).replace(
+				/[!'()*]/g,
+				(character) =>
+					`%${/** @type {number} */ (character.codePointAt(0)).toString(16)}`
+			);
+			break;
+		}
+		default:
+			throw new Error(`Unsupported encoding '${encoding}'`);
+	}
+
+	return encodedContent;
+};
+
+/**
+ * Decodes data uri content.
+ * @param {"base64" | false} encoding encoding
+ * @param {string} content content
+ * @returns {Buffer} decoded content
+ */
+const decodeDataUriContent = (encoding, content) => {
+	const isBase64 = encoding === "base64";
+
+	if (isBase64) {
+		return Buffer.from(content, "base64");
+	}
+
+	// If we can't decode return the original body
+	try {
+		return Buffer.from(decodeURIComponent(content), "utf8");
+	} catch (_) {
+		return Buffer.from(content, "utf8");
+	}
+};
+
+const DEFAULT_ENCODING = "base64";
+
+class AssetGenerator extends Generator {
+	/**
+	 * Creates an instance of AssetGenerator.
+	 * @param {ModuleGraph} moduleGraph the module graph
+	 * @param {AssetGeneratorOptions["dataUrl"]=} dataUrlOptions the options for the data url
+	 * @param {AssetModuleFilename=} filename override for output.assetModuleFilename
+	 * @param {RawPublicPath=} publicPath override for output.assetModulePublicPath
+	 * @param {AssetModuleOutputPath=} outputPath the output path for the emitted file which is not included in the runtime import
+	 * @param {boolean=} emit generate output asset
+	 * @param {Compilation=} compilation the compilation (`getTypes` has no generate context, so the analyzable-output decision reads `output.module`/`output.publicPath` from here)
+	 */
+	constructor(
+		moduleGraph,
+		dataUrlOptions,
+		filename,
+		publicPath,
+		outputPath,
+		emit,
+		compilation
+	) {
+		super();
+		/** @type {AssetGeneratorOptions["dataUrl"] | undefined} */
+		this.dataUrlOptions = dataUrlOptions;
+		/** @type {AssetModuleFilename | undefined} */
+		this.filename = filename;
+		/** @type {RawPublicPath | undefined} */
+		this.publicPath = publicPath;
+		/** @type {AssetModuleOutputPath | undefined} */
+		this.outputPath = outputPath;
+		/** @type {boolean | undefined} */
+		this.emit = emit;
+		/** @type {Compilation | undefined} */
+		this._compilation = compilation;
+		/** @type {ModuleGraph} */
+		this._moduleGraph = moduleGraph;
+	}
+
+	/**
+	 * Gets source file name.
+	 * @param {NormalModule} module module
+	 * @param {RuntimeTemplate} runtimeTemplate runtime template
+	 * @returns {string} source file name
+	 */
+	static getSourceFileName(module, runtimeTemplate) {
+		return makePathsRelative(
+			runtimeTemplate.compilation.compiler.context,
+			/** @type {string} */
+			(module.getResource()),
+			runtimeTemplate.compilation.compiler.root
+		).replace(/^\.\//, "");
+	}
+
+	/**
+	 * Gets the source file name as seen by a filename template. With
+	 * `experiments.futureDefaults` (webpack 6 default) `[path]` and `[file]` of a
+	 * module outside of `context` behave like `[containedpath]`/`[containedfile]`,
+	 * so they can't emit the asset outside of `output.path`.
+	 * @param {NormalModule} module module
+	 * @param {RuntimeTemplate} runtimeTemplate runtime template
+	 * @returns {string} source file name for the filename template
+	 */
+	static getTemplateSourceFileName(module, runtimeTemplate) {
+		return toTemplateSourceFileName(
+			AssetGenerator.getSourceFileName(module, runtimeTemplate),
+			runtimeTemplate.compilation
+		);
+	}
+
+	/**
+	 * Gets full content hash.
+	 * @param {NormalModule} module module
+	 * @param {RuntimeTemplate} runtimeTemplate runtime template
+	 * @returns {[string, string]} return full hash and non-numeric full hash
+	 */
+	static getFullContentHash(module, runtimeTemplate) {
+		const hash = createHash(runtimeTemplate.outputOptions.hashFunction);
+
+		if (runtimeTemplate.outputOptions.hashSalt) {
+			hash.update(runtimeTemplate.outputOptions.hashSalt);
+		}
+
+		const source = module.originalSource();
+
+		if (source) {
+			updateHashFromSource(hash, source);
+		}
+
+		if (module.error) {
+			hash.update(module.error.toString());
+		}
+
+		const fullContentHash = hash.digest(
+			runtimeTemplate.outputOptions.hashDigest
+		);
+
+		const contentHash = nonNumericOnlyHash(
+			fullContentHash,
+			runtimeTemplate.outputOptions.hashDigestLength
+		);
+
+		return [fullContentHash, contentHash];
+	}
+
+	/**
+	 * Gets filename with info.
+	 * @param {NormalModule} module module for which the code should be generated
+	 * @param {Pick} generatorOptions generator options
+	 * @param {{ runtime: RuntimeSpec, runtimeTemplate: RuntimeTemplate, chunkGraph: ChunkGraph }} generateContext context for generate
+	 * @param {string} contentHash the content hash
+	 * @param {string=} fullContentHash untruncated content hash, so `[contenthash:]` re-encodes from full entropy
+	 * @returns {{ filename: string, originalFilename: string, assetInfo: AssetInfo }} info
+	 */
+	static getFilenameWithInfo(
+		module,
+		generatorOptions,
+		{ runtime, runtimeTemplate, chunkGraph },
+		contentHash,
+		fullContentHash
+	) {
+		const assetModuleFilename =
+			generatorOptions.filename ||
+			runtimeTemplate.outputOptions.assetModuleFilename;
+
+		// Both templates decide where the asset is written, so they see the
+		// contained path.
+		const templateSourceFilename = AssetGenerator.getTemplateSourceFileName(
+			module,
+			runtimeTemplate
+		);
+		let { path: filename, info: assetInfo } =
+			runtimeTemplate.compilation.getAssetPathWithInfo(assetModuleFilename, {
+				module,
+				runtime,
+				filename: templateSourceFilename,
+				chunkGraph,
+				contentHash,
+				contentHashFull: fullContentHash
+			});
+
+		const originalFilename = filename;
+
+		if (generatorOptions.outputPath) {
+			const { path: outputPath, info } =
+				runtimeTemplate.compilation.getAssetPathWithInfo(
+					generatorOptions.outputPath,
+					{
+						module,
+						runtime,
+						filename: templateSourceFilename,
+						chunkGraph,
+						contentHash,
+						contentHashFull: fullContentHash
+					}
+				);
+			filename = path.posix.join(outputPath, filename);
+			assetInfo = mergeAssetInfo(assetInfo, info);
+		}
+
+		return { originalFilename, filename, assetInfo };
+	}
+
+	/**
+	 * Gets asset path with info.
+	 * @param {NormalModule} module module for which the code should be generated
+	 * @param {Pick} generatorOptions generator options
+	 * @param {GenerateContext} generateContext context for generate
+	 * @param {string} filename the filename
+	 * @param {AssetInfo} assetInfo the asset info
+	 * @param {string} contentHash the content hash
+	 * @param {string=} fullContentHash untruncated content hash, so `[contenthash:]` re-encodes from full entropy
+	 * @returns {{ assetPath: string, assetInfo: AssetInfo }} asset path and info
+	 */
+	static getAssetPathWithInfo(
+		module,
+		generatorOptions,
+		{ runtime, runtimeTemplate, type, chunkGraph, runtimeRequirements },
+		filename,
+		assetInfo,
+		contentHash,
+		fullContentHash
+	) {
+		// A public path is a url prefix, it doesn't decide where the asset is
+		// written, so `[path]` stays the real path of the source file here.
+		const sourceFilename = AssetGenerator.getSourceFileName(
+			module,
+			runtimeTemplate
+		);
+
+		/** @type {undefined | string} */
+		let assetPath;
+
+		if (generatorOptions.publicPath !== undefined && type === JAVASCRIPT_TYPE) {
+			const { path, info } = runtimeTemplate.compilation.getAssetPathWithInfo(
+				generatorOptions.publicPath,
+				{
+					module,
+					runtime,
+					filename: sourceFilename,
+					chunkGraph,
+					contentHash,
+					contentHashFull: fullContentHash
+				}
+			);
+			assetInfo = mergeAssetInfo(assetInfo, info);
+			assetPath = JSON.stringify(path + filename);
+		} else if (
+			generatorOptions.publicPath !== undefined &&
+			type === ASSET_URL_TYPE
+		) {
+			const { path, info } = runtimeTemplate.compilation.getAssetPathWithInfo(
+				generatorOptions.publicPath,
+				{
+					module,
+					runtime,
+					filename: sourceFilename,
+					chunkGraph,
+					contentHash,
+					contentHashFull: fullContentHash
+				}
+			);
+			assetInfo = mergeAssetInfo(assetInfo, info);
+			assetPath = path + filename;
+		} else if (type === JAVASCRIPT_TYPE) {
+			// `auto` reads `.p` as this asset's own directory url, which the absolute
+			// `href` spells too; a complete url of its own is already the whole string.
+			const constant = runtimeTemplate.constantPublicPath();
+			const whole =
+				constant !== undefined &&
+				(constant.startsWith("//") || getScheme(constant) !== undefined);
+			const analyzable =
+				(runtimeTemplate.outputOptions.publicPath === "auto" || whole) &&
+				runtimeTemplate.supportsAnalyzable("url", chunkGraph, module)
+					? runtimeTemplate.getAnalyzableAssetUrl(
+							module,
+							chunkGraph,
+							filename,
+							runtime
+						)
+					: null;
+			if (analyzable !== null) {
+				assetPath = whole
+					? analyzable
+					: `${runtimeTemplate.importMetaUrl(analyzable)}.href`;
+			} else {
+				// add __webpack_require__.p
+				runtimeRequirements.add(RuntimeGlobals.publicPath);
+				assetPath = runtimeTemplate.concatenation(
+					{ expr: RuntimeGlobals.publicPath },
+					filename
+				);
+			}
+		} else if (type === ASSET_URL_TYPE) {
+			const compilation = runtimeTemplate.compilation;
+			const path =
+				compilation.outputOptions.publicPath === "auto"
+					? PUBLIC_PATH_AUTO
+					: compilation.getAssetPath(compilation.outputOptions.publicPath, {
+							hash: compilation.hash || `${PUBLIC_PATH_FULL_HASH}0__`,
+							hashWithLength: (length) =>
+								compilation.hash
+									? compilation.hash.slice(0, length)
+									: `${PUBLIC_PATH_FULL_HASH}${length}__`
+						});
+
+			assetPath = path + filename;
+		}
+
+		return {
+			assetPath: /** @type {string} */ (assetPath),
+			assetInfo: { sourceFilename, ...assetInfo }
+		};
+	}
+
+	/**
+	 * Returns the reason this module cannot be concatenated, when one exists.
+	 * @param {NormalModule} module module for which the bailout reason should be determined
+	 * @param {ConcatenationBailoutReasonContext} context context
+	 * @returns {string | undefined} reason why this module can't be concatenated, undefined when it can be concatenated
+	 */
+	getConcatenationBailoutReason(module, context) {
+		return undefined;
+	}
+
+	/**
+	 * Returns mime type.
+	 * @param {NormalModule} module module
+	 * @returns {string} mime type
+	 */
+	getMimeType(module) {
+		if (typeof this.dataUrlOptions === "function") {
+			throw new Error(
+				"This method must not be called when dataUrlOptions is a function"
+			);
+		}
+
+		/** @type {string | undefined} */
+		let mimeType =
+			/** @type {AssetGeneratorDataUrlOptions} */
+			(this.dataUrlOptions).mimetype;
+		if (mimeType === undefined) {
+			const ext = path.extname(
+				/** @type {NameForCondition} */
+				(module.nameForCondition())
+			);
+			if (
+				module.resourceResolveData &&
+				module.resourceResolveData.mimetype !== undefined
+			) {
+				mimeType =
+					module.resourceResolveData.mimetype +
+					module.resourceResolveData.parameters;
+			} else if (ext) {
+				mimeType = getMimeTypes().lookup(ext);
+
+				if (typeof mimeType !== "string") {
+					throw new Error(
+						"DataUrl can't be generated automatically, " +
+							`because there is no mimetype for "${ext}" in mimetype database. ` +
+							'Either pass a mimetype via "generator.mimetype" or ' +
+							'use type: "asset/resource" to create a resource file instead of a DataUrl'
+					);
+				}
+			}
+		}
+
+		if (typeof mimeType !== "string") {
+			throw new Error(
+				"DataUrl can't be generated automatically. " +
+					'Either pass a mimetype via "generator.mimetype" or ' +
+					'use type: "asset/resource" to create a resource file instead of a DataUrl'
+			);
+		}
+
+		return /** @type {string} */ (mimeType);
+	}
+
+	/**
+	 * Generates data uri.
+	 * @param {NormalModule} module module for which the code should be generated
+	 * @returns {string} DataURI
+	 */
+	generateDataUri(module) {
+		const source = /** @type {Source} */ (module.originalSource());
+
+		/** @type {string} */
+		let encodedSource;
+
+		if (typeof this.dataUrlOptions === "function") {
+			encodedSource = this.dataUrlOptions.call(null, source.source(), {
+				filename: /** @type {string} */ (module.getResource()),
+				module
+			});
+		} else {
+			let encoding =
+				/** @type {AssetGeneratorDataUrlOptions} */
+				(this.dataUrlOptions).encoding;
+			if (
+				encoding === undefined &&
+				module.resourceResolveData &&
+				module.resourceResolveData.encoding !== undefined
+			) {
+				encoding = module.resourceResolveData.encoding;
+			}
+			if (encoding === undefined) {
+				encoding = DEFAULT_ENCODING;
+			}
+			const mimeType = this.getMimeType(module);
+
+			/** @type {string} */
+			let encodedContent;
+
+			if (
+				module.resourceResolveData &&
+				module.resourceResolveData.encoding === encoding &&
+				decodeDataUriContent(
+					module.resourceResolveData.encoding,
+					/** @type {string} */ (module.resourceResolveData.encodedContent)
+				).equals(source.buffer())
+			) {
+				encodedContent =
+					/** @type {string} */
+					(module.resourceResolveData.encodedContent);
+			} else {
+				encodedContent = encodeDataUri(
+					/** @type {"base64" | false} */ (encoding),
+					source
+				);
+			}
+
+			encodedSource = `data:${mimeType}${
+				encoding ? `;${encoding}` : ""
+			},${encodedContent}`;
+		}
+
+		return encodedSource;
+	}
+
+	/**
+	 * Generates generated code for this runtime module.
+	 * @param {NormalModule} module module for which the code should be generated
+	 * @param {GenerateContext} generateContext context for generate
+	 * @returns {Source | null} generated code
+	 */
+	generate(module, generateContext) {
+		const {
+			type,
+			getData,
+			runtimeTemplate,
+			runtimeRequirements,
+			concatenationScope
+		} = generateContext;
+
+		/** @type {string} */
+		let content;
+
+		const needContent = type === JAVASCRIPT_TYPE || type === ASSET_URL_TYPE;
+		const data = getData ? getData() : undefined;
+
+		if (
+			/** @type {AssetModuleBuildInfo} */
+			(module.buildInfo).dataUrl &&
+			needContent
+		) {
+			const encodedSource = this.generateDataUri(module);
+			content =
+				type === JAVASCRIPT_TYPE
+					? JSON.stringify(encodedSource)
+					: encodedSource;
+
+			if (data) {
+				data.set("url", { ...data.get("url"), [type]: content });
+			}
+		} else {
+			const [fullContentHash, contentHash] = AssetGenerator.getFullContentHash(
+				module,
+				runtimeTemplate
+			);
+
+			if (data) {
+				data.set("fullContentHash", fullContentHash);
+				data.set("contentHash", contentHash);
+			}
+
+			/** @type {AssetModuleBuildInfo} */
+			(module.buildInfo).fullContentHash = fullContentHash;
+
+			const { originalFilename, filename, assetInfo } =
+				AssetGenerator.getFilenameWithInfo(
+					module,
+					{ filename: this.filename, outputPath: this.outputPath },
+					generateContext,
+					contentHash,
+					fullContentHash
+				);
+
+			if (data) {
+				data.set("filename", filename);
+			}
+
+			let { assetPath, assetInfo: newAssetInfo } =
+				AssetGenerator.getAssetPathWithInfo(
+					module,
+					{ publicPath: this.publicPath },
+					generateContext,
+					originalFilename,
+					assetInfo,
+					contentHash,
+					fullContentHash
+				);
+
+			if (data && (type === JAVASCRIPT_TYPE || type === ASSET_URL_TYPE)) {
+				data.set("url", { ...data.get("url"), [type]: assetPath });
+			}
+
+			if (data) {
+				const oldAssetInfo = data.get("assetInfo");
+
+				if (oldAssetInfo) {
+					newAssetInfo = mergeAssetInfo(oldAssetInfo, newAssetInfo);
+				}
+			}
+
+			if (data) {
+				data.set("assetInfo", newAssetInfo);
+			}
+
+			// Due to code generation caching module.buildInfo.XXX can't used to store such information
+			// It need to be stored in the code generation results instead, where it's cached too
+			// TODO webpack 6 For back-compat reasons we also store in on module.buildInfo
+			/** @type {AssetModuleBuildInfo} */
+			(module.buildInfo).filename = filename;
+
+			/** @type {AssetModuleBuildInfo} */
+			(module.buildInfo).assetInfo = newAssetInfo;
+
+			content = assetPath;
+		}
+
+		if (type === JAVASCRIPT_TYPE) {
+			if (concatenationScope) {
+				concatenationScope.registerNamespaceExport(
+					ConcatenationScope.NAMESPACE_OBJECT_EXPORT
+				);
+
+				return new RawSource(
+					`${runtimeTemplate.renderConst()} ${
+						ConcatenationScope.NAMESPACE_OBJECT_EXPORT
+					} = ${content};`
+				);
+			}
+
+			runtimeRequirements.add(RuntimeGlobals.module);
+
+			return new RawSource(`${module.moduleArgument}.exports = ${content};`);
+		} else if (type === ASSET_URL_TYPE) {
+			return null;
+		}
+
+		return /** @type {Source} */ (module.originalSource());
+	}
+
+	/**
+	 * Generates fallback output for the provided error condition.
+	 * @param {Error} error the error
+	 * @param {NormalModule} module module for which the code should be generated
+	 * @param {GenerateContext} generateContext context for generate
+	 * @returns {Source | null} generated code
+	 */
+	generateError(error, module, generateContext) {
+		switch (generateContext.type) {
+			case "asset": {
+				return new RawSource(error.message);
+			}
+			case JAVASCRIPT_TYPE: {
+				return new RawSource(
+					`throw new Error(${JSON.stringify(error.message)});`
+				);
+			}
+			default:
+				return null;
+		}
+	}
+
+	/**
+	 * Returns whether the effective publicPath yields a chunk-independent absolute
+	 * url (scheme-relative, protocol-absolute, or root-absolute) with no hash tokens.
+	 * Only then is a `new URL` literal correct for every chunk the asset is shared by,
+	 * so the `module.exports` wrapper can be dropped in favor of the `asset-url` type.
+	 * @returns {boolean} true when the publicPath is a token-free absolute string
+	 */
+	_hasAbsolutePublicPath() {
+		const publicPath =
+			this.publicPath !== undefined
+				? this.publicPath
+				: this._compilation && this._compilation.outputOptions.publicPath;
+		return (
+			typeof publicPath === "string" &&
+			!publicPath.includes("[") &&
+			(publicPath.startsWith("/") ||
+				/^[a-z][a-z\d+\-.]*:\/\//i.test(publicPath))
+		);
+	}
+
+	/**
+	 * Returns the source types available for this module.
+	 * @param {NormalModule} module fresh module
+	 * @returns {SourceTypes} available types (do not mutate)
+	 */
+	getTypes(module) {
+		const connections = this._moduleGraph.getIncomingConnections(module);
+
+		// One that emits nothing has no name to build a literal from, so expose its url.
+		const emits =
+			!(
+				module.buildInfo &&
+				/** @type {AssetModuleBuildInfo} */ (module.buildInfo).dataUrl
+			) && this.emit !== false;
+
+		// Only analyzable ESM output can drop a bare `new URL()` to an asset-url; without
+		// it every JS consumer needs the wrapper, so skip the per-connection dependency
+		// probe entirely and keep the pre-existing cheap path. Must match the condition
+		// `URLDependency` uses to emit the literal, or the wrapper is dropped while the
+		// call site still requires the module — except where the fallback spells the name
+		// itself (`.p + `), which needs no wrapper to read it out of.
+		// Build-time execution instead emulates the wrapper (`AssetModulesPlugin`).
+		const isModule = Boolean(
+			this._compilation &&
+			this._compilation.runtimeTemplate.supportsAnalyzable(
+				emits ? "url-inline" : "url"
+			)
+		);
+
+		// Collapse the incoming origin types into flags instead of a Set of prefixes:
+		// on assets shared by many modules this loop runs millions of times, so avoid
+		// the per-connection `split("/")` allocation and stop early when the result is fixed.
+		let hasOrigin = false;
+		// JS origin that needs the `module.exports = …` wrapper (not a `new URL` ref).
+		let hasOtherJs = false;
+		// JS origin via `new URL(…, import.meta.url)` — droppable to `asset-url` in ESM.
+		let hasUrlJs = false;
+		// A manifest embeds a bare URL string (like css/html), so its icons must
+		// resolve to `ASSET_URL_TYPE` rather than the JS runtime form.
+		let hasUrl = false;
+		for (const connection of connections) {
+			const originModule = connection.originModule;
+			if (!originModule) {
+				continue;
+			}
+			hasOrigin = true;
+			// The dependency says how the asset is read, so a reference added later answers
+			// for itself; concatenation re-points the origin module, so it cannot.
+			const dependency = connection.dependency;
+			if (dependency && dependency.referencedSourceType === ASSET_URL_TYPE) {
+				hasUrl = true;
+			} else if (
+				// Only a bare `new URL()` becomes the analyzable literal; a relative ref
+				// keeps the runtime form and still needs the wrapper. Prefetch/preload
+				// refs don't: their `` is emitted at chunk startup, which resolves
+				// the href from the asset filename when there is no wrapper.
+				isModule &&
+				dependency instanceof URLDependency &&
+				!dependency.relative
+			) {
+				hasUrlJs = true;
+			} else {
+				hasOtherJs = true;
+			}
+			// Once a non-URL JS consumer and a url consumer coexist the result is fixed.
+			if (hasOtherJs && hasUrl) break;
+		}
+
+		// Every javascript consumer names the file itself, so nothing reads the wrapper.
+		// Only an absolute public path settles one url for all of them to share.
+		const jsWrapperUnused = isModule && hasUrlJs && !hasOtherJs;
+		const jsAsAssetUrl =
+			jsWrapperUnused && (!emits || this._hasAbsolutePublicPath());
+		// JS wrapper is needed for non-URL JS consumers, or URL consumers still reading it.
+		const wantJs = hasOtherJs || (hasUrlJs && !jsWrapperUnused);
+		const wantUrl = hasUrl || jsAsAssetUrl;
+
+		if (!emits) {
+			if (hasOrigin) {
+				if (wantJs && wantUrl) {
+					return JAVASCRIPT_AND_ASSET_URL_TYPES;
+				} else if (wantUrl) {
+					return ASSET_URL_TYPES;
+				}
+				return JAVASCRIPT_TYPES;
+			}
+
+			return NO_TYPES;
+		}
+
+		if (hasOrigin) {
+			if (wantJs && wantUrl) {
+				return ASSET_AND_JAVASCRIPT_AND_ASSET_URL_TYPES;
+			} else if (wantUrl) {
+				return ASSET_AND_ASSET_URL_TYPES;
+			} else if (jsWrapperUnused) {
+				// Every javascript consumer names the file itself, so nothing reads it.
+				return ASSET_TYPES;
+			}
+			return ASSET_AND_JAVASCRIPT_TYPES;
+		}
+
+		return ASSET_TYPES;
+	}
+
+	/**
+	 * @returns {boolean} whether getTypes() depends on the module's incoming connections
+	 */
+	getTypesDependOnIncomingConnections() {
+		return true;
+	}
+
+	/**
+	 * Returns the estimated size for the requested source type.
+	 * @param {NormalModule} module the module
+	 * @param {SourceType=} type source type
+	 * @returns {number} estimate size of the module
+	 */
+	getSize(module, type) {
+		switch (type) {
+			case ASSET_MODULE_TYPE: {
+				const originalSource = module.originalSource();
+
+				if (!originalSource) {
+					return 0;
+				}
+
+				return originalSource.size();
+			}
+			default:
+				if (
+					module.buildInfo &&
+					/** @type {AssetModuleBuildInfo} */ (module.buildInfo).dataUrl
+				) {
+					const originalSource = module.originalSource();
+
+					if (!originalSource) {
+						return 0;
+					}
+
+					// roughly for data url
+					// Example: m.exports="data:image/png;base64,ag82/f+2=="
+					// 4/3 = base64 encoding
+					// 34 = ~ data url header + footer + rounding
+					return originalSource.size() * 1.34 + 36;
+				}
+				// it's only estimated so this number is probably fine
+				// Example: m.exports=r.p+"0123456789012345678901.ext"
+				return 42;
+		}
+	}
+
+	/**
+	 * Updates the hash with the data contributed by this instance.
+	 * @param {Hash} hash hash that will be modified
+	 * @param {UpdateHashContext} updateHashContext context for updating hash
+	 */
+	updateHash(hash, updateHashContext) {
+		const { module } = updateHashContext;
+
+		if (
+			/** @type {AssetModuleBuildInfo} */
+			(module.buildInfo).dataUrl
+		) {
+			hash.update("data-url");
+			// this.dataUrlOptions as function should be pure and only depend on input source and filename
+			// therefore it doesn't need to be hashed
+			if (typeof this.dataUrlOptions === "function") {
+				const ident = /** @type {{ ident?: string }} */ (this.dataUrlOptions)
+					.ident;
+				if (ident) hash.update(ident);
+			} else {
+				const dataUrlOptions =
+					/** @type {AssetGeneratorDataUrlOptions} */
+					(this.dataUrlOptions);
+				if (
+					dataUrlOptions.encoding &&
+					dataUrlOptions.encoding !== DEFAULT_ENCODING
+				) {
+					hash.update(dataUrlOptions.encoding);
+				}
+				if (dataUrlOptions.mimetype) hash.update(dataUrlOptions.mimetype);
+				// computed mimetype depends only on module filename which is already part of the hash
+			}
+		} else {
+			hash.update("resource");
+
+			const { module, chunkGraph, runtime } = updateHashContext;
+			const runtimeTemplate =
+				/** @type {NonNullable} */
+				(updateHashContext.runtimeTemplate);
+
+			const pathData = {
+				module,
+				runtime,
+				filename: AssetGenerator.getSourceFileName(module, runtimeTemplate),
+				chunkGraph,
+				contentHash: runtimeTemplate.contentHashReplacement
+			};
+
+			if (typeof this.publicPath === "function") {
+				hash.update("path");
+				const assetInfo = {};
+				hash.update(this.publicPath(pathData, assetInfo));
+				hash.update(JSON.stringify(assetInfo));
+			} else if (this.publicPath) {
+				hash.update("path");
+				hash.update(this.publicPath);
+			} else {
+				hash.update("no-path");
+			}
+
+			const assetModuleFilename =
+				this.filename || runtimeTemplate.outputOptions.assetModuleFilename;
+			// Hash what generation emits: the filename template sees the contained
+			// path, unlike the public path above.
+			const { path: filename, info } =
+				runtimeTemplate.compilation.getAssetPathWithInfo(assetModuleFilename, {
+					module,
+					runtime,
+					filename: AssetGenerator.getTemplateSourceFileName(
+						module,
+						runtimeTemplate
+					),
+					chunkGraph,
+					contentHash: runtimeTemplate.contentHashReplacement
+				});
+			hash.update(filename);
+			hash.update(JSON.stringify(info));
+		}
+	}
+}
+
+module.exports = AssetGenerator;
diff --git a/lib/asset/AssetModule.js b/lib/asset/AssetModule.js
new file mode 100644
index 00000000000..84aa1eeb3ba
--- /dev/null
+++ b/lib/asset/AssetModule.js
@@ -0,0 +1,47 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+*/
+
+"use strict";
+
+const NormalModule = require("../NormalModule");
+const makeSerializable = require("../util/makeSerializable");
+
+/** @typedef {import("../Compilation").AssetInfo} AssetInfo */
+/** @typedef {import("../NormalModule").NormalModuleBuildInfo} NormalModuleBuildInfo */
+/** @typedef {import("../NormalModule").NormalModuleCreateData} NormalModuleCreateData */
+
+/**
+ * Defines the build info properties specific to asset modules.
+ * @typedef {object} KnownAssetModuleBuildInfo
+ * @property {boolean=} dataUrl whether the asset is inlined as a data url
+ * @property {string=} filename
+ * @property {AssetInfo=} assetInfo
+ * @property {string=} fullContentHash
+ */
+
+/** @typedef {NormalModuleBuildInfo & KnownAssetModuleBuildInfo} AssetModuleBuildInfo */
+
+/**
+ * Module class for all `asset/*` modules. Asset-specific properties should live here instead of `NormalModule`.
+ */
+class AssetModule extends NormalModule {
+	/**
+	 * @param {NormalModuleCreateData} options options object
+	 * @param {boolean=} sideEffectFree whether asset modules are side-effect-free (`AssetModulesPluginOptions`)
+	 */
+	constructor(options, sideEffectFree) {
+		super(options);
+
+		// Redeclared with the asset specific shape
+		/** @type {AssetModuleBuildInfo | undefined} */
+		this.buildInfo = undefined;
+		if (sideEffectFree) {
+			this.factoryMeta = { sideEffectFree: true };
+		}
+	}
+}
+
+makeSerializable(AssetModule, "webpack/lib/asset/AssetModule");
+
+module.exports = AssetModule;
diff --git a/lib/asset/AssetModulesPlugin.js b/lib/asset/AssetModulesPlugin.js
new file mode 100644
index 00000000000..8484275f861
--- /dev/null
+++ b/lib/asset/AssetModulesPlugin.js
@@ -0,0 +1,534 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Yuta Hiroto @hiroppy
+*/
+
+"use strict";
+
+const { RawSource } = require("webpack-sources");
+const HotUpdateChunk = require("../HotUpdateChunk");
+const {
+	ASSET_URL_TYPE,
+	JAVASCRIPT_TYPE
+} = require("../ModuleSourceTypeConstants");
+const {
+	ASSET_MODULE_TYPE,
+	ASSET_MODULE_TYPE_BYTES,
+	ASSET_MODULE_TYPE_INLINE,
+	ASSET_MODULE_TYPE_RESOURCE,
+	ASSET_MODULE_TYPE_SOURCE,
+	ASSET_MODULE_TYPE_WEBMANIFEST
+} = require("../ModuleTypeConstants");
+const { compareModulesByFullName } = require("../util/comparators");
+const createHash = require("../util/createHash");
+const { getUndoPath } = require("../util/identifier");
+const lazyModule = require("../util/lazyModule");
+const preloadModuleType = require("../util/preloadModuleType");
+const { PUBLIC_PATH_AUTO } = require("../util/publicPathPlaceholder");
+
+/** @typedef {import("webpack-sources").Source} Source */
+/** @typedef {import("schema-utils").Schema} Schema */
+/** @typedef {import("../../declarations/WebpackOptions").AssetGeneratorDataUrl} AssetGeneratorDataUrl */
+/** @typedef {import("../../declarations/WebpackOptions").AssetModuleOutputPath} AssetModuleOutputPath */
+/** @typedef {import("../../declarations/WebpackOptions").RawPublicPath} RawPublicPath */
+/** @typedef {import("../../declarations/WebpackOptions").AssetModuleFilename} AssetModuleFilename */
+/** @typedef {import("../Compilation").AssetInfo} AssetInfo */
+/** @typedef {import("../Compilation").ExecuteModuleObject} ExecuteModuleObject */
+/** @typedef {import("../Compilation").WebpackRequire} WebpackRequire */
+/** @typedef {import("../Compiler")} Compiler */
+/** @typedef {import("./AssetModule").AssetModuleBuildInfo} AssetModuleBuildInfo */
+/** @typedef {import("../Module").CodeGenerationResult} CodeGenerationResult */
+/** @typedef {import("../NormalModule")} NormalModule */
+
+/**
+ * Returns definition.
+ * @param {string} name name of definitions
+ * @returns {Schema} definition
+ */
+const getSchema = (name) => {
+	const { definitions } =
+		/** @type {EXPECTED_ANY} */
+		(require("../../schemas/WebpackOptions.json"));
+
+	return {
+		definitions,
+		oneOf: [{ $ref: `#/definitions/${name}` }]
+	};
+};
+
+const generatorValidationOptions = {
+	name: "Asset Modules Plugin",
+	baseDataPath: "generator"
+};
+
+const getAssetGenerator = lazyModule(() => require("./AssetGenerator"));
+const getAssetParser = lazyModule(() => require("./AssetParser"));
+const getAssetSourceParser = lazyModule(() => require("./AssetSourceParser"));
+const getAssetBytesParser = lazyModule(() => require("./AssetBytesParser"));
+const getAssetSourceGenerator = lazyModule(() =>
+	require("./AssetSourceGenerator")
+);
+const getAssetBytesGenerator = lazyModule(() =>
+	require("./AssetBytesGenerator")
+);
+const getAssetModule = lazyModule(() => require("./AssetModule"));
+const getWebManifestParser = lazyModule(() => require("./WebManifestParser"));
+const getWebManifestGenerator = lazyModule(() =>
+	require("./WebManifestGenerator")
+);
+
+const type = ASSET_MODULE_TYPE;
+// Source type produced by `WebManifestGenerator` (its emitted, rewritten JSON).
+const WEBMANIFEST_SOURCE_TYPE = "webmanifest";
+const PLUGIN_NAME = "AssetModulesPlugin";
+
+/**
+ * Represents the asset modules plugin runtime component.
+ * @typedef {object} AssetModulesPluginOptions
+ * @property {boolean=} sideEffectFree
+ */
+
+class AssetModulesPlugin {
+	/**
+	 * Creates an instance of AssetModulesPlugin.
+	 * @param {AssetModulesPluginOptions} options options
+	 */
+	constructor(options) {
+		/** @type {AssetModulesPluginOptions} */
+		this.options = options;
+	}
+
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
+	apply(compiler) {
+		compiler.hooks.compilation.tap(
+			PLUGIN_NAME,
+			(compilation, { normalModuleFactory }) => {
+				preloadModuleType(normalModuleFactory, PLUGIN_NAME, [
+					[
+						ASSET_MODULE_TYPE,
+						[getAssetModule, getAssetParser, getAssetGenerator]
+					],
+					[
+						ASSET_MODULE_TYPE_INLINE,
+						[getAssetModule, getAssetParser, getAssetGenerator]
+					],
+					[
+						ASSET_MODULE_TYPE_RESOURCE,
+						[getAssetModule, getAssetParser, getAssetGenerator]
+					],
+					[
+						ASSET_MODULE_TYPE_SOURCE,
+						[getAssetModule, getAssetSourceParser, getAssetSourceGenerator]
+					],
+					[
+						ASSET_MODULE_TYPE_BYTES,
+						[getAssetModule, getAssetBytesParser, getAssetBytesGenerator]
+					],
+					[
+						ASSET_MODULE_TYPE_WEBMANIFEST,
+						[getWebManifestParser, getWebManifestGenerator]
+					]
+				]);
+
+				for (const type of [
+					ASSET_MODULE_TYPE,
+					ASSET_MODULE_TYPE_BYTES,
+					ASSET_MODULE_TYPE_INLINE,
+					ASSET_MODULE_TYPE_RESOURCE,
+					ASSET_MODULE_TYPE_SOURCE
+				]) {
+					normalModuleFactory.hooks.createModuleClass
+						.for(type)
+						.tap(PLUGIN_NAME, (createData, _resolveData) => {
+							const AssetModule = getAssetModule.loaded();
+
+							return new AssetModule(createData, this.options.sideEffectFree);
+						});
+				}
+
+				normalModuleFactory.hooks.createParser
+					.for(ASSET_MODULE_TYPE)
+					.tap(PLUGIN_NAME, (parserOptions) => {
+						compiler.validate(
+							() => getSchema("AssetParserOptions"),
+							parserOptions,
+							{
+								name: "Asset Modules Plugin",
+								baseDataPath: "parser"
+							},
+							(options) =>
+								require("../../schemas/plugins/asset/AssetParserOptions.check")(
+									options
+								)
+						);
+
+						let dataUrlCondition = parserOptions.dataUrlCondition;
+						if (!dataUrlCondition || typeof dataUrlCondition === "object") {
+							dataUrlCondition = {
+								maxSize: 8096,
+								...dataUrlCondition
+							};
+						}
+
+						const AssetParser = getAssetParser.loaded();
+
+						return new AssetParser(dataUrlCondition);
+					});
+				normalModuleFactory.hooks.createParser
+					.for(ASSET_MODULE_TYPE_INLINE)
+					.tap(PLUGIN_NAME, (_parserOptions) => {
+						const AssetParser = getAssetParser.loaded();
+
+						return new AssetParser(true);
+					});
+				normalModuleFactory.hooks.createParser
+					.for(ASSET_MODULE_TYPE_RESOURCE)
+					.tap(PLUGIN_NAME, (_parserOptions) => {
+						const AssetParser = getAssetParser.loaded();
+
+						return new AssetParser(false);
+					});
+				normalModuleFactory.hooks.createParser
+					.for(ASSET_MODULE_TYPE_SOURCE)
+					.tap(PLUGIN_NAME, (_parserOptions) => {
+						const AssetSourceParser = getAssetSourceParser.loaded();
+
+						return new AssetSourceParser();
+					});
+				normalModuleFactory.hooks.createParser
+					.for(ASSET_MODULE_TYPE_BYTES)
+					.tap(PLUGIN_NAME, (_parserOptions) => {
+						const AssetBytesParser = getAssetBytesParser.loaded();
+
+						return new AssetBytesParser();
+					});
+
+				for (const type of [
+					ASSET_MODULE_TYPE,
+					ASSET_MODULE_TYPE_INLINE,
+					ASSET_MODULE_TYPE_RESOURCE
+				]) {
+					normalModuleFactory.hooks.createGenerator
+						.for(type)
+						.tap(PLUGIN_NAME, (generatorOptions) => {
+							switch (type) {
+								case ASSET_MODULE_TYPE: {
+									compiler.validate(
+										() => getSchema("AssetGeneratorOptions"),
+										generatorOptions,
+										generatorValidationOptions,
+										(options) =>
+											require("../../schemas/plugins/asset/AssetGeneratorOptions.check")(
+												options
+											)
+									);
+									break;
+								}
+								case ASSET_MODULE_TYPE_RESOURCE: {
+									compiler.validate(
+										() => getSchema("AssetResourceGeneratorOptions"),
+										generatorOptions,
+										generatorValidationOptions,
+										(options) =>
+											require("../../schemas/plugins/asset/AssetResourceGeneratorOptions.check")(
+												options
+											)
+									);
+									break;
+								}
+								case ASSET_MODULE_TYPE_INLINE: {
+									compiler.validate(
+										() => getSchema("AssetInlineGeneratorOptions"),
+										generatorOptions,
+										generatorValidationOptions,
+										(options) =>
+											require("../../schemas/plugins/asset/AssetInlineGeneratorOptions.check")(
+												options
+											)
+									);
+									break;
+								}
+							}
+
+							/** @type {undefined | AssetGeneratorDataUrl} */
+							let dataUrl;
+							if (type !== ASSET_MODULE_TYPE_RESOURCE) {
+								dataUrl = generatorOptions.dataUrl;
+								if (!dataUrl || typeof dataUrl === "object") {
+									dataUrl = {
+										encoding: undefined,
+										mimetype: undefined,
+										...dataUrl
+									};
+								}
+							}
+
+							/** @type {undefined | AssetModuleFilename} */
+							let filename;
+							/** @type {undefined | RawPublicPath} */
+							let publicPath;
+							/** @type {undefined | AssetModuleOutputPath} */
+							let outputPath;
+							if (type !== ASSET_MODULE_TYPE_INLINE) {
+								filename = generatorOptions.filename;
+								publicPath = generatorOptions.publicPath;
+								outputPath = generatorOptions.outputPath;
+							}
+
+							const AssetGenerator = getAssetGenerator.loaded();
+
+							return new AssetGenerator(
+								compilation.moduleGraph,
+								dataUrl,
+								filename,
+								publicPath,
+								outputPath,
+								generatorOptions.emit !== false,
+								compilation
+							);
+						});
+				}
+				normalModuleFactory.hooks.createGenerator
+					.for(ASSET_MODULE_TYPE_SOURCE)
+					.tap(PLUGIN_NAME, () => {
+						const AssetSourceGenerator = getAssetSourceGenerator.loaded();
+
+						return new AssetSourceGenerator(compilation.moduleGraph);
+					});
+
+				normalModuleFactory.hooks.createGenerator
+					.for(ASSET_MODULE_TYPE_BYTES)
+					.tap(PLUGIN_NAME, () => {
+						const AssetBytesGenerator = getAssetBytesGenerator.loaded();
+
+						return new AssetBytesGenerator(compilation.moduleGraph);
+					});
+
+				compilation.hooks.renderManifest.tap(PLUGIN_NAME, (result, options) => {
+					const { chunkGraph } = compilation;
+					const { chunk, codeGenerationResults, runtimeTemplate } = options;
+
+					const modules = chunkGraph.getOrderedChunkModulesIterableBySourceType(
+						chunk,
+						ASSET_MODULE_TYPE,
+						compareModulesByFullName(compilation.compiler)
+					);
+					if (modules) {
+						for (const module of modules) {
+							try {
+								const codeGenResult = codeGenerationResults.get(
+									module,
+									chunk.runtime
+								);
+								const buildInfo = /** @type {AssetModuleBuildInfo} */ (
+									module.buildInfo
+								);
+								const data =
+									/** @type {NonNullable} */
+									(codeGenResult.data);
+								const errored = module.getNumberOfErrors() > 0;
+
+								/** @type {string} */
+								let entryFilename;
+								/** @type {AssetInfo} */
+								let entryInfo;
+								/** @type {string} */
+								let entryHash;
+
+								if (errored) {
+									const erroredModule = /** @type {NormalModule} */ (module);
+									const AssetGenerator = getAssetGenerator.loaded();
+									const [fullContentHash, contentHash] =
+										AssetGenerator.getFullContentHash(
+											erroredModule,
+											runtimeTemplate
+										);
+									const { filename, assetInfo } =
+										AssetGenerator.getFilenameWithInfo(
+											erroredModule,
+											{
+												filename:
+													erroredModule.generatorOptions &&
+													erroredModule.generatorOptions.filename,
+												outputPath:
+													erroredModule.generatorOptions &&
+													erroredModule.generatorOptions.outputPath
+											},
+											{
+												runtime: chunk.runtime,
+												runtimeTemplate,
+												chunkGraph
+											},
+											contentHash,
+											fullContentHash
+										);
+									entryFilename = filename;
+									entryInfo = assetInfo;
+									entryHash = fullContentHash;
+								} else {
+									entryFilename =
+										/** @type {string} */
+										(buildInfo.filename || data.get("filename"));
+									entryInfo =
+										/** @type {AssetInfo} */
+										(buildInfo.assetInfo || data.get("assetInfo"));
+									entryHash =
+										/** @type {string} */
+										(buildInfo.fullContentHash || data.get("fullContentHash"));
+								}
+
+								result.push({
+									render: () =>
+										/** @type {Source} */ (codeGenResult.sources.get(type)),
+									filename: entryFilename,
+									info: entryInfo,
+									auxiliary: true,
+									identifier: `assetModule${chunkGraph.getModuleId(module)}`,
+									hash: entryHash
+								});
+							} catch (err) {
+								/** @type {Error} */ (err).message +=
+									`\nduring rendering of asset ${module.identifier()}`;
+								throw err;
+							}
+						}
+					}
+
+					return result;
+				});
+
+				// A Web App Manifest (`asset/webmanifest`, gated on `experiments.html`)
+				// parses/emits like an asset, but its icon URLs are rewritten in place
+				// and the emitted file needs its own per-file public-path resolution.
+				if (compilation.options.experiments.html) {
+					normalModuleFactory.hooks.createParser
+						.for(ASSET_MODULE_TYPE_WEBMANIFEST)
+						.tap(PLUGIN_NAME, () => {
+							const WebManifestParser = getWebManifestParser.loaded();
+
+							return new WebManifestParser();
+						});
+					normalModuleFactory.hooks.createGenerator
+						.for(ASSET_MODULE_TYPE_WEBMANIFEST)
+						.tap(PLUGIN_NAME, () => {
+							const WebManifestGenerator = getWebManifestGenerator.loaded();
+
+							return new WebManifestGenerator(compilation.moduleGraph);
+						});
+
+					compilation.hooks.renderManifest.tap(
+						PLUGIN_NAME,
+						(result, options) => {
+							const { chunk, codeGenerationResults } = options;
+							if (chunk instanceof HotUpdateChunk) return result;
+							const { chunkGraph, outputOptions } = compilation;
+							const modules =
+								chunkGraph.getOrderedChunkModulesIterableBySourceType(
+									chunk,
+									WEBMANIFEST_SOURCE_TYPE,
+									compareModulesByFullName(compilation.compiler)
+								);
+							if (!modules) return result;
+							for (const module of modules) {
+								const codeGenResult = codeGenerationResults.get(
+									module,
+									chunk.runtime
+								);
+								const source = codeGenResult.sources.get(
+									WEBMANIFEST_SOURCE_TYPE
+								);
+								if (!source) continue;
+								const filename = /** @type {string} */ (
+									codeGenResult.data && codeGenResult.data.get("filename")
+								);
+								if (!filename) continue;
+								// Icon URLs resolve relative to the manifest's own location.
+								const undoPath = getUndoPath(
+									filename,
+									/** @type {string} */ (outputOptions.path),
+									false
+								);
+								const content = source
+									.source()
+									.toString()
+									.split(PUBLIC_PATH_AUTO)
+									.join(undoPath);
+								const hash = createHash(outputOptions.hashFunction || "md4");
+								hash.update(content);
+								const contentHash = /** @type {string} */ (
+									hash.digest(outputOptions.hashDigest || "hex")
+								).slice(0, outputOptions.hashDigestLength || 20);
+								result.push({
+									render: () => new RawSource(content),
+									filename,
+									info: { immutable: true },
+									auxiliary: true,
+									identifier: `webManifestModule${chunkGraph.getModuleId(
+										module
+									)}|${filename}`,
+									hash: contentHash
+								});
+							}
+							return result;
+						}
+					);
+				}
+
+				compilation.hooks.prepareModuleExecution.tap(
+					PLUGIN_NAME,
+					(options, context) => {
+						const { codeGenerationResult } = options;
+						const source = codeGenerationResult.sources.get(ASSET_MODULE_TYPE);
+						if (source === undefined) return;
+						const data =
+							/** @type {NonNullable} */
+							(codeGenerationResult.data);
+						context.assets.set(
+							/** @type {string} */
+							(data.get("filename")),
+							{
+								source,
+								info: data.get("assetInfo")
+							}
+						);
+					}
+				);
+
+				compilation.hooks.executeModule.tap(PLUGIN_NAME, (options, context) => {
+					const { codeGenerationResult } = options;
+					// A wrapper-less asset (`asset-url` only) would yield `{}` when
+					// required at build time — emulate the wrapper's exports instead.
+					if (!options.module.type.startsWith(ASSET_MODULE_TYPE)) return;
+					if (codeGenerationResult.sources.has(JAVASCRIPT_TYPE)) return;
+					const data = codeGenerationResult.data;
+					if (!data) return;
+					const moduleObject =
+						/** @type {ExecuteModuleObject} */
+						(options.moduleObject);
+					// `__webpack_require__.p` honors the `importModule` publicPath
+					// override (`URLDependency` requests it for wrapper-less assets).
+					const publicPath =
+						/** @type {WebpackRequire} */
+						(context.__webpack_require__).p;
+					const filename = data.get("filename");
+					if (typeof filename === "string" && typeof publicPath === "string") {
+						moduleObject.exports = publicPath + filename;
+						return;
+					}
+					// data: uris (and assets without a runtime publicPath) resolve
+					// chunk-independently — use the baked `asset-url`.
+					const url = data.get("url");
+					if (url && typeof url[ASSET_URL_TYPE] === "string") {
+						moduleObject.exports = url[ASSET_URL_TYPE];
+					}
+				});
+			}
+		);
+	}
+}
+
+module.exports = AssetModulesPlugin;
diff --git a/lib/asset/AssetParser.js b/lib/asset/AssetParser.js
new file mode 100644
index 00000000000..91fb428606d
--- /dev/null
+++ b/lib/asset/AssetParser.js
@@ -0,0 +1,75 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Yuta Hiroto @hiroppy
+*/
+
+"use strict";
+
+const Parser = require("../Parser");
+
+/** @typedef {import("../../declarations/WebpackOptions").AssetParserDataUrlOptions} AssetParserDataUrlOptions */
+/** @typedef {import("../../declarations/WebpackOptions").AssetParserOptions} AssetParserOptions */
+/** @typedef {import("../Module")} Module */
+/** @typedef {import("./AssetModule").AssetModuleBuildInfo} AssetModuleBuildInfo */
+/** @typedef {import("../Module").BuildMeta} BuildMeta */
+/** @typedef {import("../Parser").ParserState} ParserState */
+/** @typedef {import("../Parser").PreparsedAst} PreparsedAst */
+
+/** @typedef {((source: string | Buffer, context: { filename: string, module: Module }) => boolean)} AssetParserDataUrlFunction */
+
+class AssetParser extends Parser {
+	/**
+	 * Creates an instance of AssetParser.
+	 * @param {AssetParserOptions["dataUrlCondition"] | boolean} dataUrlCondition condition for inlining as DataUrl
+	 */
+	constructor(dataUrlCondition) {
+		super();
+		/** @type {AssetParserOptions["dataUrlCondition"] | boolean} */
+		this.dataUrlCondition = dataUrlCondition;
+	}
+
+	/**
+	 * Parses the provided source and updates the parser state.
+	 * @param {string | Buffer | PreparsedAst} source the source to parse
+	 * @param {ParserState} state the parser state
+	 * @returns {ParserState} the parser state
+	 */
+	parse(source, state) {
+		if (typeof source === "object" && !Buffer.isBuffer(source)) {
+			throw new Error("AssetParser doesn't accept preparsed AST");
+		}
+
+		const buildInfo =
+			/** @type {AssetModuleBuildInfo} */
+			(state.module.buildInfo);
+		buildInfo.strict = true;
+		const buildMeta =
+			/** @type {BuildMeta} */
+			(state.module.buildMeta);
+		buildMeta.exportsType = "default";
+		buildMeta.defaultObject = false;
+
+		if (typeof this.dataUrlCondition === "function") {
+			buildInfo.dataUrl = this.dataUrlCondition(source, {
+				filename: /** @type {string} */ (state.module.getResource()),
+				module: state.module
+			});
+		} else if (typeof this.dataUrlCondition === "boolean") {
+			buildInfo.dataUrl = this.dataUrlCondition;
+		} else if (
+			this.dataUrlCondition &&
+			typeof this.dataUrlCondition === "object"
+		) {
+			buildInfo.dataUrl =
+				Buffer.byteLength(source) <=
+				/** @type {NonNullable} */
+				(this.dataUrlCondition.maxSize);
+		} else {
+			throw new Error("Unexpected dataUrlCondition type");
+		}
+
+		return state;
+	}
+}
+
+module.exports = AssetParser;
diff --git a/lib/asset/AssetSourceGenerator.js b/lib/asset/AssetSourceGenerator.js
new file mode 100644
index 00000000000..34fe5bc18c3
--- /dev/null
+++ b/lib/asset/AssetSourceGenerator.js
@@ -0,0 +1,189 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Sergey Melyukov @smelukov
+*/
+
+"use strict";
+
+const { RawSource } = require("webpack-sources");
+const ConcatenationScope = require("../ConcatenationScope");
+const Generator = require("../Generator");
+const {
+	ASSET_URL_TYPE,
+	ASSET_URL_TYPES,
+	CSS_TYPE,
+	HTML_TYPE,
+	JAVASCRIPT_AND_ASSET_URL_TYPES,
+	JAVASCRIPT_TYPE,
+	JAVASCRIPT_TYPES,
+	NO_TYPES
+} = require("../ModuleSourceTypeConstants");
+const RuntimeGlobals = require("../RuntimeGlobals");
+
+/** @typedef {import("webpack-sources").Source} Source */
+/** @typedef {import("../Generator").GenerateContext} GenerateContext */
+/** @typedef {import("../Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */
+/** @typedef {import("../Module").SourceType} SourceType */
+/** @typedef {import("../Module").SourceTypes} SourceTypes */
+/** @typedef {import("../ModuleGraph")} ModuleGraph */
+/** @typedef {import("../NormalModule")} NormalModule */
+
+class AssetSourceGenerator extends Generator {
+	/**
+	 * Creates an instance of AssetSourceGenerator.
+	 * @param {ModuleGraph} moduleGraph the module graph
+	 */
+	constructor(moduleGraph) {
+		super();
+
+		/** @type {ModuleGraph} */
+		this._moduleGraph = moduleGraph;
+	}
+
+	/**
+	 * Generates generated code for this runtime module.
+	 * @param {NormalModule} module module for which the code should be generated
+	 * @param {GenerateContext} generateContext context for generate
+	 * @returns {Source | null} generated code
+	 */
+	generate(
+		module,
+		{ type, concatenationScope, getData, runtimeTemplate, runtimeRequirements }
+	) {
+		const originalSource = module.originalSource();
+		const data = getData ? getData() : undefined;
+
+		switch (type) {
+			case JAVASCRIPT_TYPE: {
+				if (!originalSource) {
+					return new RawSource("");
+				}
+
+				const content = originalSource.source();
+				const encodedSource =
+					typeof content === "string" ? content : content.toString("utf8");
+
+				/** @type {string} */
+				let sourceContent;
+				if (concatenationScope) {
+					concatenationScope.registerNamespaceExport(
+						ConcatenationScope.NAMESPACE_OBJECT_EXPORT
+					);
+					sourceContent = `${runtimeTemplate.renderConst()} ${
+						ConcatenationScope.NAMESPACE_OBJECT_EXPORT
+					} = ${JSON.stringify(encodedSource)};`;
+				} else {
+					runtimeRequirements.add(RuntimeGlobals.module);
+					sourceContent = `${module.moduleArgument}.exports = ${JSON.stringify(
+						encodedSource
+					)};`;
+				}
+				return new RawSource(sourceContent);
+			}
+			case ASSET_URL_TYPE: {
+				if (!originalSource) {
+					return null;
+				}
+
+				const content = originalSource.source();
+				const encodedSource =
+					typeof content === "string" ? content : content.toString("utf8");
+
+				if (data) {
+					data.set("url", { [type]: encodedSource });
+				}
+				return null;
+			}
+			default:
+				return null;
+		}
+	}
+
+	/**
+	 * Generates fallback output for the provided error condition.
+	 * @param {Error} error the error
+	 * @param {NormalModule} module module for which the code should be generated
+	 * @param {GenerateContext} generateContext context for generate
+	 * @returns {Source | null} generated code
+	 */
+	generateError(error, module, generateContext) {
+		switch (generateContext.type) {
+			case JAVASCRIPT_TYPE: {
+				return new RawSource(
+					`throw new Error(${JSON.stringify(error.message)});`
+				);
+			}
+			default:
+				return null;
+		}
+	}
+
+	/**
+	 * Returns the reason this module cannot be concatenated, when one exists.
+	 * @param {NormalModule} module module for which the bailout reason should be determined
+	 * @param {ConcatenationBailoutReasonContext} context context
+	 * @returns {string | undefined} reason why this module can't be concatenated, undefined when it can be concatenated
+	 */
+	getConcatenationBailoutReason(module, context) {
+		return undefined;
+	}
+
+	/**
+	 * Returns the source types available for this module.
+	 * @param {NormalModule} module fresh module
+	 * @returns {SourceTypes} available types (do not mutate)
+	 */
+	getTypes(module) {
+		/** @type {Set} */
+		const sourceTypes = new Set();
+		const connections = this._moduleGraph.getIncomingConnections(module);
+
+		for (const connection of connections) {
+			if (!connection.originModule) {
+				continue;
+			}
+
+			sourceTypes.add(connection.originModule.type.split("/")[0]);
+		}
+
+		if (sourceTypes.size > 0) {
+			if (
+				sourceTypes.has(JAVASCRIPT_TYPE) &&
+				(sourceTypes.has(CSS_TYPE) || sourceTypes.has(HTML_TYPE))
+			) {
+				return JAVASCRIPT_AND_ASSET_URL_TYPES;
+			} else if (sourceTypes.has(CSS_TYPE) || sourceTypes.has(HTML_TYPE)) {
+				return ASSET_URL_TYPES;
+			}
+			return JAVASCRIPT_TYPES;
+		}
+
+		return NO_TYPES;
+	}
+
+	/**
+	 * @returns {boolean} whether getTypes() depends on the module's incoming connections
+	 */
+	getTypesDependOnIncomingConnections() {
+		return true;
+	}
+
+	/**
+	 * Returns the estimated size for the requested source type.
+	 * @param {NormalModule} module the module
+	 * @param {SourceType=} type source type
+	 * @returns {number} estimate size of the module
+	 */
+	getSize(module, type) {
+		const originalSource = module.originalSource();
+
+		if (!originalSource) {
+			return 0;
+		}
+
+		// Example: m.exports="abcd"
+		return originalSource.size() + 12;
+	}
+}
+
+module.exports = AssetSourceGenerator;
diff --git a/lib/asset/AssetSourceParser.js b/lib/asset/AssetSourceParser.js
new file mode 100644
index 00000000000..4e3bd4c1af2
--- /dev/null
+++ b/lib/asset/AssetSourceParser.js
@@ -0,0 +1,38 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Yuta Hiroto @hiroppy
+*/
+
+"use strict";
+
+const Parser = require("../Parser");
+
+/** @typedef {import("../Module").BuildInfo} BuildInfo */
+/** @typedef {import("../Module").BuildMeta} BuildMeta */
+/** @typedef {import("../Parser").ParserState} ParserState */
+/** @typedef {import("../Parser").PreparsedAst} PreparsedAst */
+
+class AssetSourceParser extends Parser {
+	/**
+	 * Parses the provided source and updates the parser state.
+	 * @param {string | Buffer | PreparsedAst} source the source to parse
+	 * @param {ParserState} state the parser state
+	 * @returns {ParserState} the parser state
+	 */
+	parse(source, state) {
+		if (typeof source === "object" && !Buffer.isBuffer(source)) {
+			throw new Error("AssetSourceParser doesn't accept preparsed AST");
+		}
+		const { module } = state;
+		/** @type {BuildInfo} */
+		(module.buildInfo).strict = true;
+		/** @type {BuildMeta} */
+		(module.buildMeta).exportsType = "default";
+		/** @type {BuildMeta} */
+		(state.module.buildMeta).defaultObject = false;
+
+		return state;
+	}
+}
+
+module.exports = AssetSourceParser;
diff --git a/lib/asset/RawDataUrlModule.js b/lib/asset/RawDataUrlModule.js
new file mode 100644
index 00000000000..f349bbbb0a3
--- /dev/null
+++ b/lib/asset/RawDataUrlModule.js
@@ -0,0 +1,189 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const { RawSource } = require("webpack-sources");
+const Module = require("../Module");
+const {
+	JAVASCRIPT_TYPE,
+	JAVASCRIPT_TYPES
+} = require("../ModuleSourceTypeConstants");
+const { ASSET_MODULE_TYPE_RAW_DATA_URL } = require("../ModuleTypeConstants");
+const RuntimeGlobals = require("../RuntimeGlobals");
+const makeSerializable = require("../util/makeSerializable");
+
+/** @typedef {import("../config/defaults").WebpackOptionsNormalizedWithDefaults} WebpackOptions */
+/** @typedef {import("../Compilation")} Compilation */
+/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */
+/** @typedef {import("../Module").BuildCallback} BuildCallback */
+/** @typedef {import("../Module").RuntimeRequirements} RuntimeRequirements */
+/** @typedef {import("../Module").CodeGenerationContext} CodeGenerationContext */
+/** @typedef {import("../Module").CodeGenerationResult} CodeGenerationResult */
+/** @typedef {import("../Module").CodeGenerationResultData} CodeGenerationResultData */
+/** @typedef {import("../Module").NeedBuildCallback} NeedBuildCallback */
+/** @typedef {import("../Module").NeedBuildContext} NeedBuildContext */
+/** @typedef {import("../Module").Sources} Sources */
+/** @typedef {import("../Module").SourceTypes} SourceTypes */
+/** @typedef {import("../RequestShortener")} RequestShortener */
+/** @typedef {import("../ResolverFactory").ResolverWithOptions} ResolverWithOptions */
+/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext<[Buffer | undefined, string, string]>} ObjectDeserializerContext */
+/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext<[Buffer | undefined, string, string]>} ObjectSerializerContext */
+/** @typedef {import("../util/Hash")} Hash */
+/** @typedef {import("../util/fs").InputFileSystem} InputFileSystem */
+
+class RawDataUrlModule extends Module {
+	/**
+	 * Creates an instance of RawDataUrlModule.
+	 * @param {string} url raw url
+	 * @param {string} identifier unique identifier
+	 * @param {string=} readableIdentifier readable identifier
+	 */
+	constructor(url, identifier, readableIdentifier) {
+		super(ASSET_MODULE_TYPE_RAW_DATA_URL, null);
+		/** @type {string} */
+		this.url = url;
+		/** @type {Buffer | undefined} */
+		this.urlBuffer = url ? Buffer.from(url) : undefined;
+		/** @type {string} */
+		this.identifierStr = identifier;
+		/** @type {string} */
+		this.readableIdentifierStr = readableIdentifier || this.identifierStr;
+	}
+
+	/**
+	 * Returns the source types this module can generate.
+	 * @returns {SourceTypes} types available (do not mutate)
+	 */
+	getSourceTypes() {
+		return JAVASCRIPT_TYPES;
+	}
+
+	/**
+	 * Returns the unique identifier used to reference this module.
+	 * @returns {string} a unique identifier of the module
+	 */
+	identifier() {
+		return this.identifierStr;
+	}
+
+	/**
+	 * Returns the estimated size for the requested source type.
+	 * @param {string=} type the source type for which the size should be estimated
+	 * @returns {number} the estimated size of the module (must be non-zero)
+	 */
+	size(type) {
+		if (this.url === undefined) {
+			this.url = /** @type {Buffer} */ (this.urlBuffer).toString();
+		}
+		return Math.max(1, this.url.length);
+	}
+
+	/**
+	 * Returns a human-readable identifier for this module.
+	 * @param {RequestShortener} requestShortener the request shortener
+	 * @returns {string} a user readable identifier of the module
+	 */
+	readableIdentifier(requestShortener) {
+		return /** @type {string} */ (
+			requestShortener.shorten(this.readableIdentifierStr)
+		);
+	}
+
+	/**
+	 * Checks whether the module needs to be rebuilt for the current build state.
+	 * @param {NeedBuildContext} context context info
+	 * @param {NeedBuildCallback} callback callback function, returns true, if the module needs a rebuild
+	 * @returns {void}
+	 */
+	needBuild(context, callback) {
+		return callback(null, !this.buildMeta);
+	}
+
+	/**
+	 * Builds the module using the provided compilation context.
+	 * @param {WebpackOptions} options webpack options
+	 * @param {Compilation} compilation the compilation
+	 * @param {ResolverWithOptions} resolver the resolver
+	 * @param {InputFileSystem} fs the file system
+	 * @param {BuildCallback} callback callback function
+	 * @returns {void}
+	 */
+	build(options, compilation, resolver, fs, callback) {
+		this.buildMeta = {};
+		this.buildInfo = {
+			cacheable: true
+		};
+		callback();
+	}
+
+	/**
+	 * Generates code and runtime requirements for this module.
+	 * @param {CodeGenerationContext} context context for code generation
+	 * @returns {CodeGenerationResult} result
+	 */
+	codeGeneration(context) {
+		if (this.url === undefined) {
+			this.url = /** @type {Buffer} */ (this.urlBuffer).toString();
+		}
+		/** @type {Sources} */
+		const sources = new Map();
+		sources.set(
+			JAVASCRIPT_TYPE,
+			new RawSource(`module.exports = ${JSON.stringify(this.url)};`)
+		);
+		/** @type {CodeGenerationResultData} */
+		const data = new Map();
+		data.set("url", {
+			javascript: this.url
+		});
+		/** @type {RuntimeRequirements} */
+		const runtimeRequirements = new Set();
+		runtimeRequirements.add(RuntimeGlobals.module);
+		return { sources, runtimeRequirements, data };
+	}
+
+	/**
+	 * Updates the hash with the data contributed by this instance.
+	 * @param {Hash} hash the hash used to track dependencies
+	 * @param {UpdateHashContext} context context
+	 * @returns {void}
+	 */
+	updateHash(hash, context) {
+		hash.update(/** @type {Buffer} */ (this.urlBuffer));
+		super.updateHash(hash, context);
+	}
+
+	/**
+	 * Serializes this instance into the provided serializer context.
+	 * @param {ObjectSerializerContext} context context
+	 */
+	serialize(context) {
+		context
+			.write(this.urlBuffer)
+			.write(this.identifierStr)
+			.write(this.readableIdentifierStr);
+
+		super.serialize(context);
+	}
+
+	/**
+	 * Restores this instance from the provided deserializer context.
+	 * @param {ObjectDeserializerContext} context context
+	 */
+	deserialize(context) {
+		this.urlBuffer = context.read();
+		const c1 = context.rest;
+		this.identifierStr = c1.read();
+		const c2 = c1.rest;
+		this.readableIdentifierStr = c2.read();
+
+		super.deserialize(c2.rest);
+	}
+}
+
+makeSerializable(RawDataUrlModule, "webpack/lib/asset/RawDataUrlModule");
+
+module.exports = RawDataUrlModule;
diff --git a/lib/asset/WebManifestGenerator.js b/lib/asset/WebManifestGenerator.js
new file mode 100644
index 00000000000..9c15312187b
--- /dev/null
+++ b/lib/asset/WebManifestGenerator.js
@@ -0,0 +1,164 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Alexander Akait @alexander-akait
+*/
+
+"use strict";
+
+const { RawSource, ReplaceSource } = require("webpack-sources");
+const { ASSET_URL_TYPE } = require("../ModuleSourceTypeConstants");
+const createHash = require("../util/createHash");
+const nonNumericOnlyHash = require("../util/nonNumericOnlyHash");
+const { PUBLIC_PATH_AUTO } = require("../util/publicPathPlaceholder");
+const AssetGenerator = require("./AssetGenerator");
+
+/** @typedef {import("webpack-sources").Source} Source */
+/** @typedef {import("../CodeGenerationResults")} CodeGenerationResults */
+/** @typedef {import("../Compilation").DependencyConstructor} DependencyConstructor */
+/** @typedef {import("../Dependency")} Dependency */
+/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
+/** @typedef {import("../Generator").GenerateContext} GenerateContext */
+/** @typedef {import("../Module").SourceType} SourceType */
+/** @typedef {import("../Module").SourceTypes} SourceTypes */
+/** @typedef {import("../ModuleGraph")} ModuleGraph */
+/** @typedef {import("../NormalModule")} NormalModule */
+
+const WEBMANIFEST_TYPE = "webmanifest";
+const WEBMANIFEST_TYPES = new Set([WEBMANIFEST_TYPE]);
+const WEBMANIFEST_AND_ASSET_URL_TYPES = new Set([
+	WEBMANIFEST_TYPE,
+	ASSET_URL_TYPE
+]);
+
+/**
+ * A Web App Manifest is a "typed asset": a variant of `AssetGenerator` (reusing
+ * its filename/hash and `ASSET_URL_TYPE` machinery) whose emitted content is the
+ * raw JSON with each icon `src` rewritten to its bundled asset URL. The manifest
+ * itself is emitted — with its own URL exposed to the referencing
+ * `` — by `AssetModulesPlugin`'s `renderManifest`, which
+ * resolves the per-file public path (like the CSS/HTML plugins).
+ */
+class WebManifestGenerator extends AssetGenerator {
+	/**
+	 * @param {ModuleGraph} moduleGraph the module graph
+	 */
+	constructor(moduleGraph) {
+		super(moduleGraph, undefined, undefined, undefined, undefined, true);
+	}
+
+	/**
+	 * Returns the source types available for this module.
+	 * @param {NormalModule} module fresh module
+	 * @returns {SourceTypes} available types (do not mutate)
+	 */
+	getTypes(module) {
+		// Expose the manifest's own URL only when it is referenced (from HTML).
+		const connections = this._moduleGraph.getIncomingConnections(module);
+		const referenced = !connections[Symbol.iterator]().next().done;
+		return referenced ? WEBMANIFEST_AND_ASSET_URL_TYPES : WEBMANIFEST_TYPES;
+	}
+
+	/**
+	 * Returns the estimated size for the requested source type.
+	 * @param {NormalModule} module the module
+	 * @param {SourceType=} type source type
+	 * @returns {number} estimate size of the module
+	 */
+	getSize(module, type) {
+		const originalSource = module.originalSource();
+		return originalSource ? originalSource.size() : 0;
+	}
+
+	/**
+	 * Rewrites each icon `src` in the raw manifest to its emitted asset URL.
+	 * @param {NormalModule} module the manifest module
+	 * @param {GenerateContext} generateContext context for generate
+	 * @returns {string} the rewritten manifest content
+	 */
+	_rewrite(module, generateContext) {
+		const originalSource = /** @type {Source} */ (module.originalSource());
+		const source = new ReplaceSource(originalSource);
+		// `HtmlSourceDependency.Template` only reads `moduleGraph`/`runtimeTemplate`/
+		// `codeGenerationResults`; the rest is filled to satisfy the context shape.
+		/** @type {DependencyTemplateContext} */
+		const templateContext = {
+			runtimeTemplate: generateContext.runtimeTemplate,
+			dependencyTemplates: generateContext.dependencyTemplates,
+			moduleGraph: generateContext.moduleGraph,
+			chunkGraph: generateContext.chunkGraph,
+			module,
+			runtime: generateContext.runtime,
+			runtimeRequirements: generateContext.runtimeRequirements,
+			concatenationScope: generateContext.concatenationScope,
+			codeGenerationResults:
+				/** @type {CodeGenerationResults} */
+				(generateContext.codeGenerationResults),
+			initFragments: [],
+			chunkInitFragments: []
+		};
+		for (const dependency of module.dependencies) {
+			const constructor = /** @type {DependencyConstructor} */ (
+				dependency.constructor
+			);
+			const template = templateContext.dependencyTemplates.get(constructor);
+			if (!template) {
+				throw new Error(
+					`No template for dependency: ${dependency.constructor.name}`
+				);
+			}
+			template.apply(dependency, source, templateContext);
+		}
+		return /** @type {string} */ (source.source());
+	}
+
+	/**
+	 * Generates generated code for this runtime module.
+	 * @param {NormalModule} module module for which the code should be generated
+	 * @param {GenerateContext} generateContext context for generate
+	 * @returns {Source | null} generated code
+	 */
+	generate(module, generateContext) {
+		if (!module.originalSource()) return new RawSource("");
+
+		const { type, getData } = generateContext;
+		const content = this._rewrite(module, generateContext);
+
+		// Hash the *rewritten* content (so the filename tracks the icons) and reuse
+		// the asset filename machinery, so a plain (icon-less) manifest emits like a
+		// plain asset (`output.assetModuleFilename`, `[name]`, `[ext]`).
+		const outputOptions = generateContext.runtimeTemplate.outputOptions;
+		const hash = createHash(outputOptions.hashFunction);
+		if (outputOptions.hashSalt) hash.update(outputOptions.hashSalt);
+		hash.update(content);
+		const fullContentHash = /** @type {string} */ (
+			hash.digest(outputOptions.hashDigest)
+		);
+		const contentHash = nonNumericOnlyHash(
+			fullContentHash,
+			/** @type {number} */ (outputOptions.hashDigestLength)
+		);
+		const { filename, assetInfo } = AssetGenerator.getFilenameWithInfo(
+			module,
+			{ filename: undefined, outputPath: undefined },
+			generateContext,
+			contentHash,
+			fullContentHash
+		);
+
+		const data = getData ? getData() : undefined;
+		if (data) {
+			data.set("filename", filename);
+			data.set("assetInfo", assetInfo);
+			if (type === ASSET_URL_TYPE) {
+				data.set("url", {
+					...data.get("url"),
+					[ASSET_URL_TYPE]: PUBLIC_PATH_AUTO + filename
+				});
+			}
+		}
+
+		return type === ASSET_URL_TYPE ? null : new RawSource(content);
+	}
+}
+
+module.exports = WebManifestGenerator;
diff --git a/lib/asset/WebManifestParser.js b/lib/asset/WebManifestParser.js
new file mode 100644
index 00000000000..b750d6d0087
--- /dev/null
+++ b/lib/asset/WebManifestParser.js
@@ -0,0 +1,148 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Alexander Akait @alexander-akait
+*/
+
+"use strict";
+
+const { parse } = require("acorn");
+const Parser = require("../Parser");
+const HtmlSourceDependency = require("../dependencies/HtmlSourceDependency");
+
+/** @typedef {import("estree").Expression} Expression */
+/** @typedef {import("estree").ObjectExpression} ObjectExpression */
+/** @typedef {import("estree").Program} Program */
+/** @typedef {import("estree").Property} Property */
+/** @typedef {import("../Module").BuildInfo} BuildInfo */
+/** @typedef {import("../Parser").ParserState} ParserState */
+/** @typedef {import("../Parser").PreparsedAst} PreparsedAst */
+
+// acorn decorates every node with numeric source offsets (absent from the estree types).
+/** @typedef {{ start: number, end: number }} Positioned */
+
+// A URL carrying its own scheme (`https:`, `data:`, …) is external; it is only
+// bundled when `experiments.buildHttp` is enabled (like an absolute URL in HTML).
+const ABSOLUTE_URL_SCHEME_REGEXP = /^[a-zA-Z][a-zA-Z\d+\-.]*:/;
+// Manifest members whose array items carry an asset `src` (`icons[].src`,
+// `screenshots[].src`, and `shortcuts[].icons[].src` — the last nests under `icons`).
+const ICON_CONTAINER_KEYS = new Set(["icons", "screenshots"]);
+const BOM_BYTE_LENGTH = 3;
+
+/**
+ * Parses a Web App Manifest (`.webmanifest`) so its icon/screenshot `src`
+ * URLs are bundled as assets. Reuses acorn (JSON is a valid JS expression
+ * literal) to recover the exact source offsets, then emits an
+ * `HtmlSourceDependency` per URL so the generator can rewrite it in place.
+ */
+class WebManifestParser extends Parser {
+	/**
+	 * Parses the provided source and updates the parser state.
+	 * @param {string | Buffer | PreparsedAst} source the source to parse
+	 * @param {ParserState} state the parser state
+	 * @returns {ParserState} the parser state
+	 */
+	parse(source, state) {
+		// The generator replaces over the raw buffer, so the emitted ranges are
+		// byte offsets rather than the UTF-16 ones acorn reports.
+		const binary = Buffer.isBuffer(source);
+		if (binary) {
+			source = /** @type {Buffer} */ (source).toString("utf8");
+		} else if (typeof source === "object") {
+			throw new Error("webpackAst is unexpected for the WebManifestParser");
+		}
+		let leadingBytes = 0;
+		if (source[0] === "") {
+			source = source.slice(1);
+			leadingBytes = binary ? BOM_BYTE_LENGTH : 1;
+		}
+		const text = /** @type {string} */ (source);
+		const asciiOnly = !binary || Buffer.byteLength(text) === text.length;
+		/**
+		 * @param {number} offset offset in UTF-16 code units into `text`
+		 * @returns {number} matching offset into the module source
+		 */
+		const toSourceOffset = (offset) =>
+			(asciiOnly ? offset : Buffer.byteLength(text.slice(0, offset))) +
+			leadingBytes;
+
+		const module = state.module;
+		// Without `buildHttp` there is no handler to fetch remote icons, so an
+		// absolute URL is left as-is rather than rewritten to an ignored asset.
+		const buildHttp = Boolean(state.options.experiments.buildHttp);
+
+		/** @type {Program | undefined} */
+		let ast;
+		try {
+			// Wrap in parens so the top-level object is an expression, not a block.
+			ast = /** @type {Program} */ (
+				/** @type {unknown} */ (parse(`(${source})`, { ecmaVersion: "latest" }))
+			);
+		} catch (_err) {
+			// Not valid JSON — leave the file untouched (still emitted as-is).
+			ast = undefined;
+		}
+
+		const statement = ast && ast.body[0];
+		if (
+			statement &&
+			statement.type === "ExpressionStatement" &&
+			statement.expression.type === "ObjectExpression"
+		) {
+			/**
+			 * @param {ObjectExpression} obj object expression node
+			 * @param {string | undefined} parentKey key of the array/object this node sits in
+			 */
+			const walk = (obj, parentKey) => {
+				for (const prop of obj.properties) {
+					if (prop.type !== "Property") continue;
+					const key =
+						prop.key.type === "Literal" && typeof prop.key.value === "string"
+							? prop.key.value
+							: prop.key.type === "Identifier"
+								? prop.key.name
+								: undefined;
+					const value = /** @type {Expression} */ (prop.value);
+					if (value.type === "ObjectExpression") {
+						walk(value, key);
+					} else if (value.type === "ArrayExpression") {
+						for (const element of value.elements) {
+							if (element && element.type === "ObjectExpression") {
+								walk(element, key);
+							}
+						}
+					} else if (
+						key === "src" &&
+						value.type === "Literal" &&
+						typeof value.value === "string" &&
+						parentKey !== undefined &&
+						ICON_CONTAINER_KEYS.has(parentKey)
+					) {
+						const url = value.value;
+						// Fragment-only refs aren't assets; absolute URLs need `buildHttp`.
+						if (!url || url.startsWith("#")) continue;
+						if (!buildHttp && ABSOLUTE_URL_SCHEME_REGEXP.test(url)) continue;
+						// acorn offsets are into `(${source})`; the leading `(` and the
+						// opening quote cancel, so the inner span is [start, end - 2].
+						const { start, end } = /** @type {Positioned} */ (
+							/** @type {unknown} */ (value)
+						);
+						const dep = new HtmlSourceDependency(url, [
+							toSourceOffset(start),
+							toSourceOffset(end - 2)
+						]);
+						module.addDependency(dep);
+						module.addCodeGenerationDependency(dep);
+					}
+				}
+			};
+			walk(statement.expression, undefined);
+		}
+
+		/** @type {BuildInfo} */
+		(state.module.buildInfo).strict = true;
+
+		return state;
+	}
+}
+
+module.exports = WebManifestParser;
diff --git a/lib/async-modules/AsyncModuleHelpers.js b/lib/async-modules/AsyncModuleHelpers.js
new file mode 100644
index 00000000000..66886e81d80
--- /dev/null
+++ b/lib/async-modules/AsyncModuleHelpers.js
@@ -0,0 +1,53 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Haijie Xie @hai-x
+*/
+
+"use strict";
+
+const HarmonyImportDependency = require("../dependencies/HarmonyImportDependency");
+
+/** @typedef {import("../ModuleGraph")} ModuleGraph */
+/** @typedef {import("../Module")} Module */
+
+/** @typedef {Set} Modules */
+
+/**
+ * Gets outgoing async modules.
+ * @param {ModuleGraph} moduleGraph module graph
+ * @param {Module} module module
+ * @returns {Modules} set of modules
+ */
+const getOutgoingAsyncModules = (moduleGraph, module) => {
+	/** @type {Modules} */
+	const set = new Set();
+	/** @type {Modules} */
+	const seen = new Set();
+	(function g(module) {
+		if (!moduleGraph.isAsync(module) || seen.has(module)) return;
+		seen.add(module);
+		if (module.buildMeta && module.buildMeta.async) {
+			set.add(module);
+		} else {
+			const outgoingConnectionMap =
+				moduleGraph.getOutgoingConnectionsByModule(module);
+			if (outgoingConnectionMap) {
+				for (const [module, connections] of outgoingConnectionMap) {
+					if (
+						connections.some(
+							(c) =>
+								c.dependency instanceof HarmonyImportDependency &&
+								c.isTargetActive(undefined)
+						) &&
+						module
+					) {
+						g(module);
+					}
+				}
+			}
+		}
+	})(module);
+	return set;
+};
+
+module.exports.getOutgoingAsyncModules = getOutgoingAsyncModules;
diff --git a/lib/async-modules/AwaitDependenciesInitFragment.js b/lib/async-modules/AwaitDependenciesInitFragment.js
new file mode 100644
index 00000000000..3830c18ff05
--- /dev/null
+++ b/lib/async-modules/AwaitDependenciesInitFragment.js
@@ -0,0 +1,104 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const InitFragment = require("../InitFragment");
+const RuntimeGlobals = require("../RuntimeGlobals");
+const Template = require("../Template");
+
+/** @typedef {import("webpack-sources").Source} Source */
+/** @typedef {import("../Generator").GenerateContext} GenerateContext */
+
+/** @typedef {Map} Dependencies */
+
+/**
+ * Represents AwaitDependenciesInitFragment.
+ * @extends {InitFragment}
+ */
+class AwaitDependenciesInitFragment extends InitFragment {
+	/**
+	 * Creates an instance of AwaitDependenciesInitFragment.
+	 * @param {Dependencies} dependencies maps an import var to an async module that needs to be awaited
+	 * @param {boolean=} generatorLowered module is emitted as a generator, so use `yield` instead of `await`
+	 */
+	constructor(dependencies, generatorLowered = false) {
+		super(
+			undefined,
+			InitFragment.STAGE_ASYNC_DEPENDENCIES,
+			0,
+			"await-dependencies"
+		);
+		/** @type {Dependencies} */
+		this.dependencies = dependencies;
+		/** @type {boolean} */
+		this.generatorLowered = generatorLowered;
+	}
+
+	/**
+	 * Merges another await-dependencies fragment into this fragment.
+	 * @param {AwaitDependenciesInitFragment} other other AwaitDependenciesInitFragment
+	 * @returns {AwaitDependenciesInitFragment} AwaitDependenciesInitFragment
+	 */
+	merge(other) {
+		const dependencies = new Map(other.dependencies);
+		for (const [key, value] of this.dependencies) {
+			dependencies.set(key, value);
+		}
+		return new AwaitDependenciesInitFragment(
+			dependencies,
+			this.generatorLowered || other.generatorLowered
+		);
+	}
+
+	/**
+	 * Returns the source code that will be included as initialization code.
+	 * @param {GenerateContext} context context
+	 * @returns {string | Source | undefined} the source code that will be included as initialization code
+	 */
+	getContent({ runtimeRequirements, runtimeTemplate }) {
+		runtimeRequirements.add(RuntimeGlobals.module);
+		if (this.dependencies.size === 0) {
+			return "";
+		}
+
+		const importVars = [...this.dependencies.keys()];
+		const asyncModuleValues = [...this.dependencies.values()].join(", ");
+
+		// In a generator-lowered module (no `async`/`await`, but generators) the
+		// dependency `await` becomes `yield`; the body stays a single scope.
+		const suspend = this.generatorLowered ? "yield" : "await";
+
+		const templateInput = [
+			`var __webpack_async_dependencies__ = __webpack_handle_async_dependencies__([${asyncModuleValues}]);`
+		];
+
+		if (
+			this.dependencies.size === 1 ||
+			!runtimeTemplate.supportsDestructuring()
+		) {
+			templateInput.push(
+				`var __webpack_async_dependencies_result__ = (__webpack_async_dependencies__.then ? (${suspend} __webpack_async_dependencies__)() : __webpack_async_dependencies__);`
+			);
+			for (const [index, importVar] of importVars.entries()) {
+				templateInput.push(
+					`${importVar} = __webpack_async_dependencies_result__[${index}];`
+				);
+			}
+		} else {
+			const importVarsStr = importVars.join(", ");
+
+			templateInput.push(
+				`([${importVarsStr}] = __webpack_async_dependencies__.then ? (${suspend} __webpack_async_dependencies__)() : __webpack_async_dependencies__);`
+			);
+		}
+
+		templateInput.push("");
+
+		return Template.asString(templateInput);
+	}
+}
+
+module.exports = AwaitDependenciesInitFragment;
diff --git a/lib/async-modules/InferAsyncModulesPlugin.js b/lib/async-modules/InferAsyncModulesPlugin.js
new file mode 100644
index 00000000000..f467472cad4
--- /dev/null
+++ b/lib/async-modules/InferAsyncModulesPlugin.js
@@ -0,0 +1,54 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const HarmonyImportDependency = require("../dependencies/HarmonyImportDependency");
+
+/** @typedef {import("../Compiler")} Compiler */
+/** @typedef {import("../Module")} Module */
+
+const PLUGIN_NAME = "InferAsyncModulesPlugin";
+
+class InferAsyncModulesPlugin {
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
+	apply(compiler) {
+		compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
+			const { moduleGraph } = compilation;
+			compilation.hooks.finishModules.tap(PLUGIN_NAME, (modules) => {
+				/** @type {Set} */
+				const queue = new Set();
+				for (const module of modules) {
+					if (module.buildMeta && module.buildMeta.async) {
+						queue.add(module);
+					}
+				}
+				for (const module of queue) {
+					moduleGraph.setAsync(module);
+					for (const [
+						originModule,
+						connections
+					] of moduleGraph.getIncomingConnectionsByOriginModule(module)) {
+						if (
+							connections.some(
+								(c) =>
+									c.dependency instanceof HarmonyImportDependency &&
+									c.isTargetActive(undefined)
+							)
+						) {
+							queue.add(/** @type {Module} */ (originModule));
+						}
+					}
+				}
+			});
+		});
+	}
+}
+
+module.exports = InferAsyncModulesPlugin;
diff --git a/lib/async-modules/isGeneratorLowered.js b/lib/async-modules/isGeneratorLowered.js
new file mode 100644
index 00000000000..2c93ab2596c
--- /dev/null
+++ b/lib/async-modules/isGeneratorLowered.js
@@ -0,0 +1,26 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+*/
+
+"use strict";
+
+/** @typedef {import("../Module")} Module */
+/** @typedef {import("../ModuleGraph")} ModuleGraph */
+/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */
+
+/**
+ * Whether an async module is lowered to a generator: the target has no
+ * `async`/`await` but supports generators, and the module contains no
+ * top-level `for await…of` or `await using` (which generators can't express).
+ * @param {Module} module module
+ * @param {ModuleGraph} moduleGraph module graph
+ * @param {RuntimeTemplate} runtimeTemplate runtime template
+ * @returns {boolean} true when the module should be emitted as a generator
+ */
+const isGeneratorLowered = (module, moduleGraph, runtimeTemplate) =>
+	moduleGraph.isAsync(module) &&
+	!runtimeTemplate.supportsAsyncFunction() &&
+	Boolean(runtimeTemplate.supportsGenerator()) &&
+	!(module.buildInfo && module.buildInfo.usesTopLevelAwaitForOf);
+
+module.exports = isGeneratorLowered;
diff --git a/lib/buildChunkGraph.js b/lib/buildChunkGraph.js
new file mode 100644
index 00000000000..b4c24f0d743
--- /dev/null
+++ b/lib/buildChunkGraph.js
@@ -0,0 +1,1494 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const ModuleGraphConnection = require("./ModuleGraphConnection");
+const memoize = require("./util/memoize");
+
+const { getEntryRuntime, mergeRuntime } = require("./util/runtime");
+
+const getAsyncDependencyToInitialChunkError = memoize(() =>
+	require("./errors/AsyncDependencyToInitialChunkError")
+);
+
+/** @typedef {import("./AsyncDependenciesBlock")} AsyncDependenciesBlock */
+/** @typedef {import("./Chunk")} Chunk */
+/** @typedef {import("./ChunkGroup")} ChunkGroup */
+/** @typedef {import("./Compilation")} Compilation */
+/** @typedef {import("./DependenciesBlock")} DependenciesBlock */
+/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */
+/** @typedef {import("./Entrypoint")} Entrypoint */
+/** @typedef {import("./Entrypoint").EntryOptions} EntryOptions */
+/** @typedef {import("./Module")} Module */
+/** @typedef {import("./ModuleGraph")} ModuleGraph */
+/** @typedef {import("./ModuleGraphConnection").ConnectionState} ConnectionState */
+/** @typedef {import("./logging/Logger").Logger} Logger */
+/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */
+
+/**
+ * Defines the queue item type used by this module.
+ * @typedef {object} QueueItem
+ * @property {number} action
+ * @property {DependenciesBlock} block
+ * @property {Module} module
+ * @property {Chunk} chunk
+ * @property {ChunkGroup} chunkGroup
+ * @property {ChunkGroupInfo} chunkGroupInfo
+ */
+
+/**
+ * Defines the chunk group info type used by this module.
+ * @typedef {object} ChunkGroupInfo
+ * @property {ChunkGroup} chunkGroup the chunk group
+ * @property {RuntimeSpec} runtime the runtimes
+ * @property {boolean} initialized is this chunk group initialized
+ * @property {bigint | undefined} minAvailableModules current minimal set of modules available at this point
+ * @property {bigint[]} availableModulesToBeMerged enqueued updates to the minimal set of available modules
+ * @property {Set=} skippedItems modules that were skipped because module is already available in parent chunks (need to reconsider when minAvailableModules is shrinking)
+ * @property {Set<[Module, ModuleGraphConnection[]]>=} skippedModuleConnections referenced modules that where skipped because they were not active in this runtime
+ * @property {bigint | undefined} resultingAvailableModules set of modules available including modules from this chunk group
+ * @property {Set | undefined} children set of children chunk groups, that will be revisited when availableModules shrink
+ * @property {Set | undefined} availableSources set of chunk groups that are the source for minAvailableModules
+ * @property {Set | undefined} availableChildren set of chunk groups which depend on the this chunk group as availableSource
+ * @property {number} preOrderIndex next pre order index
+ * @property {number} postOrderIndex next post order index
+ * @property {boolean} chunkLoading has a chunk loading mechanism
+ * @property {boolean} asyncChunks create async chunks
+ * @property {Module | null} depModule the module that is the dependency of the block
+ * @property {boolean} circular Whether to deduplicate to avoid circular references
+ */
+
+/**
+ * Defines the block chunk group connection type used by this module.
+ * @typedef {object} BlockChunkGroupConnection
+ * @property {ChunkGroupInfo} originChunkGroupInfo origin chunk group
+ * @property {ChunkGroup} chunkGroup referenced chunk group
+ */
+
+/** @typedef {(Module | ConnectionState | ModuleGraphConnection)[]} BlockModulesInTuples */
+/** @typedef {(Module | ConnectionState | ModuleGraphConnection[])[]} BlockModulesInFlattenTuples */
+/** @typedef {Map} BlockModulesMap */
+/** @typedef {Map} MaskByChunk */
+/** @typedef {Set} BlocksWithNestedBlocks */
+/** @typedef {Map} BlockConnections */
+/** @typedef {Map} ChunkGroupInfoMap */
+/** @typedef {Set} AllCreatedChunkGroups */
+/** @typedef {Map} InputEntrypointsAndModules */
+
+const ZERO_BIGINT = BigInt(0);
+const ONE_BIGINT = BigInt(1);
+
+/**
+ * Checks whether this object is ordinal set in mask.
+ * @param {bigint} mask The mask to test
+ * @param {number} ordinal The ordinal of the bit to test
+ * @returns {boolean} If the ordinal-th bit is set in the mask
+ */
+const isOrdinalSetInMask = (mask, ordinal) =>
+	BigInt.asUintN(1, mask >> BigInt(ordinal)) !== ZERO_BIGINT;
+
+/**
+ * Gets active state of connections.
+ * @param {ModuleGraphConnection[]} connections list of connections
+ * @param {RuntimeSpec} runtime for which runtime
+ * @returns {ConnectionState} connection state
+ */
+const getActiveStateOfConnections = (connections, runtime) => {
+	let merged = connections[0].getActiveState(runtime);
+	if (merged === true) return true;
+	for (let i = 1; i < connections.length; i++) {
+		const c = connections[i];
+		merged = ModuleGraphConnection.addConnectionStates(
+			merged,
+			c.getActiveState(runtime)
+		);
+		if (merged === true) return true;
+	}
+	return merged;
+};
+
+/**
+ * Extract block modules.
+ * @param {Module} module module
+ * @param {ModuleGraph} moduleGraph module graph
+ * @param {RuntimeSpec} runtime runtime
+ * @param {BlockModulesMap} blockModulesMap block modules map
+ */
+const extractBlockModules = (module, moduleGraph, runtime, blockModulesMap) => {
+	/** @type {DependenciesBlock | undefined} */
+	let blockCache;
+	/** @type {BlockModulesInTuples | undefined} */
+	let modules;
+
+	/** @type {BlockModulesInTuples[]} */
+	const arrays = [];
+
+	/** @type {DependenciesBlock[]} */
+	const queue = [module];
+	while (queue.length > 0) {
+		const block = /** @type {DependenciesBlock} */ (queue.pop());
+		/** @type {Module[]} */
+		const arr = [];
+		arrays.push(arr);
+		blockModulesMap.set(block, arr);
+		for (const b of block.blocks) {
+			queue.push(b);
+		}
+	}
+
+	for (const connection of moduleGraph.getOutgoingConnections(module)) {
+		const d = connection.dependency;
+		// We skip connections without dependency
+		if (!d) continue;
+		const m = connection.module;
+		// We skip connections without Module pointer
+		if (!m) continue;
+		// We skip weak connections
+		if (connection.weak) continue;
+
+		const block = moduleGraph.getParentBlock(d);
+		let index = moduleGraph.getParentBlockIndex(d);
+
+		// deprecated fallback
+		if (index < 0) {
+			index = /** @type {DependenciesBlock} */ (block).dependencies.indexOf(d);
+		}
+
+		if (blockCache !== block) {
+			modules =
+				/** @type {BlockModulesInTuples} */
+				(
+					blockModulesMap.get(
+						(blockCache = /** @type {DependenciesBlock} */ (block))
+					)
+				);
+		}
+
+		const i = index * 3;
+		/** @type {BlockModulesInTuples} */
+		(modules)[i] = m;
+		/** @type {BlockModulesInTuples} */
+		(modules)[i + 1] = connection.getActiveState(runtime);
+		/** @type {BlockModulesInTuples} */
+		(modules)[i + 2] = connection;
+	}
+
+	for (const modules of arrays) {
+		if (modules.length === 0) continue;
+		/** @type {undefined | Map} */
+		let indexMap;
+		let length = 0;
+		outer: for (let j = 0; j < modules.length; j += 3) {
+			const m = modules[j];
+			if (m === undefined) continue;
+			const state = /** @type {ConnectionState} */ (modules[j + 1]);
+			const connection = /** @type {ModuleGraphConnection} */ (modules[j + 2]);
+			if (indexMap === undefined) {
+				let i = 0;
+				for (; i < length; i += 3) {
+					if (modules[i] === m) {
+						const merged = /** @type {ConnectionState} */ (modules[i + 1]);
+						/** @type {ModuleGraphConnection[]} */
+						(/** @type {unknown} */ (modules[i + 2])).push(connection);
+						if (merged === true) continue outer;
+						modules[i + 1] = ModuleGraphConnection.addConnectionStates(
+							merged,
+							state
+						);
+						continue outer;
+					}
+				}
+				modules[length] = m;
+				length++;
+				modules[length] = state;
+				length++;
+				/** @type {ModuleGraphConnection[]} */
+				(/** @type {unknown} */ (modules[length])) = [connection];
+				length++;
+				if (length > 30) {
+					// To avoid worse case performance, we will use an index map for
+					// linear cost access, which allows to maintain O(n) complexity
+					// while keeping allocations down to a minimum
+					indexMap = new Map();
+					for (let i = 0; i < length; i += 3) {
+						indexMap.set(modules[i], i + 1);
+					}
+				}
+			} else {
+				const idx = indexMap.get(m);
+				if (idx !== undefined) {
+					const merged = /** @type {ConnectionState} */ (modules[idx]);
+					/** @type {ModuleGraphConnection[]} */
+					(/** @type {unknown} */ (modules[idx + 1])).push(connection);
+					if (merged === true) continue;
+					modules[idx] = ModuleGraphConnection.addConnectionStates(
+						merged,
+						state
+					);
+				} else {
+					modules[length] = m;
+					length++;
+					modules[length] = state;
+					indexMap.set(m, length);
+					length++;
+					/** @type {ModuleGraphConnection[]} */
+					(
+						/** @type {unknown} */
+						(modules[length])
+					) = [connection];
+					length++;
+				}
+			}
+		}
+		modules.length = length;
+	}
+};
+
+/**
+ * Derives block modules for another runtime from an already extracted result.
+ * Only the merged active state of each connection group can differ per
+ * runtime; modules and connection groups are reused. When all states turn
+ * out equal, the tuple array is shared instead of copied.
+ * @param {Module} module the root module
+ * @param {BlockModulesMap} source map containing the extracted result for all blocks of the module
+ * @param {RuntimeSpec} runtime runtime to derive for
+ * @param {BlockModulesMap} blockModulesMap block modules map to fill
+ */
+const deriveBlockModules = (module, source, runtime, blockModulesMap) => {
+	/** @type {DependenciesBlock[]} */
+	const queue = [module];
+	while (queue.length > 0) {
+		const block = /** @type {DependenciesBlock} */ (queue.pop());
+		const sourceModules =
+			/** @type {BlockModulesInFlattenTuples} */
+			(source.get(block));
+		let target = sourceModules;
+		for (let i = 0, len = sourceModules.length; i < len; i += 3) {
+			const state = getActiveStateOfConnections(
+				/** @type {ModuleGraphConnection[]} */ (sourceModules[i + 2]),
+				runtime
+			);
+			if (target === sourceModules) {
+				if (state === sourceModules[i + 1]) continue;
+				// states diverged, copy (states before i are identical)
+				target = [...sourceModules];
+			}
+			target[i + 1] = state;
+		}
+		blockModulesMap.set(block, target);
+		for (const b of block.blocks) {
+			queue.push(b);
+		}
+	}
+};
+
+/**
+ * Processes the provided logger.
+ * @param {Logger} logger a logger
+ * @param {Compilation} compilation the compilation
+ * @param {InputEntrypointsAndModules} inputEntrypointsAndModules chunk groups which are processed with the modules
+ * @param {ChunkGroupInfoMap} chunkGroupInfoMap mapping from chunk group to available modules
+ * @param {BlockConnections} blockConnections connection for blocks
+ * @param {BlocksWithNestedBlocks} blocksWithNestedBlocks flag for blocks that have nested blocks
+ * @param {AllCreatedChunkGroups} allCreatedChunkGroups filled with all chunk groups that are created here
+ * @param {MaskByChunk} maskByChunk module content mask by chunk
+ */
+const visitModules = (
+	logger,
+	compilation,
+	inputEntrypointsAndModules,
+	chunkGroupInfoMap,
+	blockConnections,
+	blocksWithNestedBlocks,
+	allCreatedChunkGroups,
+	maskByChunk
+) => {
+	const { moduleGraph, chunkGraph, moduleMemCaches } = compilation;
+
+	/** @type {Map} */
+	const blockModulesRuntimeMap = new Map();
+
+	// map containing the first extraction per root module,
+	// other runtimes derive their result from it
+	/** @type {Map} */
+	const firstBlockModulesMapByModule = new Map();
+
+	/** @type {Map} */
+	const ordinalByModule = new Map();
+
+	/**
+	 * Gets module ordinal.
+	 * @param {Module} module The module to look up
+	 * @returns {number} The ordinal of the module in masks
+	 */
+	const getModuleOrdinal = (module) => {
+		let ordinal = ordinalByModule.get(module);
+		if (ordinal === undefined) {
+			ordinal = ordinalByModule.size;
+			ordinalByModule.set(module, ordinal);
+		}
+		return ordinal;
+	};
+
+	for (const chunk of compilation.chunks) {
+		let mask = ZERO_BIGINT;
+		for (const m of chunkGraph.getChunkModulesIterable(chunk)) {
+			mask |= ONE_BIGINT << BigInt(getModuleOrdinal(m));
+		}
+		maskByChunk.set(chunk, mask);
+	}
+
+	/**
+	 * Gets block modules.
+	 * @param {DependenciesBlock} block block
+	 * @param {RuntimeSpec} runtime runtime
+	 * @returns {BlockModulesInFlattenTuples | undefined} block modules in flatten tuples
+	 */
+	const getBlockModules = (block, runtime) => {
+		let blockModulesMap = blockModulesRuntimeMap.get(runtime);
+		if (blockModulesMap === undefined) {
+			/** @type {BlockModulesMap} */
+			blockModulesMap = new Map();
+			blockModulesRuntimeMap.set(runtime, blockModulesMap);
+		}
+		let blockModules = blockModulesMap.get(block);
+		if (blockModules !== undefined) return blockModules;
+		const module = /** @type {Module} */ (block.getRootBlock());
+		const memCache = moduleMemCaches && moduleMemCaches.get(module);
+		if (memCache !== undefined) {
+			/** @type {BlockModulesMap} */
+			const map = memCache.provide(
+				"bundleChunkGraph.blockModules",
+				runtime,
+				() => {
+					logger.time("visitModules: prepare");
+					const map = new Map();
+					const source = firstBlockModulesMapByModule.get(module);
+					if (source !== undefined) {
+						deriveBlockModules(module, source, runtime, map);
+					} else {
+						extractBlockModules(module, moduleGraph, runtime, map);
+					}
+					logger.timeAggregate("visitModules: prepare");
+					return map;
+				}
+			);
+			// allow other runtimes to derive from a memCache hit too
+			if (!firstBlockModulesMapByModule.has(module)) {
+				firstBlockModulesMapByModule.set(module, map);
+			}
+			for (const [block, blockModules] of map) {
+				blockModulesMap.set(block, blockModules);
+			}
+			return map.get(block);
+		}
+		logger.time("visitModules: prepare");
+		const source = firstBlockModulesMapByModule.get(module);
+		if (source !== undefined) {
+			deriveBlockModules(module, source, runtime, blockModulesMap);
+		} else {
+			extractBlockModules(module, moduleGraph, runtime, blockModulesMap);
+			firstBlockModulesMapByModule.set(module, blockModulesMap);
+		}
+		blockModules =
+			/** @type {BlockModulesInFlattenTuples} */
+			(blockModulesMap.get(block));
+		logger.timeAggregate("visitModules: prepare");
+		return blockModules;
+	};
+
+	let statProcessedQueueItems = 0;
+	let statProcessedBlocks = 0;
+	let statConnectedChunkGroups = 0;
+	let statProcessedChunkGroupsForMerging = 0;
+	let statMergedAvailableModuleSets = 0;
+	const statForkedAvailableModules = 0;
+	const statForkedAvailableModulesCount = 0;
+	const statForkedAvailableModulesCountPlus = 0;
+	const statForkedMergedModulesCount = 0;
+	const statForkedMergedModulesCountPlus = 0;
+	const statForkedResultModulesCount = 0;
+	let statChunkGroupInfoUpdated = 0;
+	let statChildChunkGroupsReconnected = 0;
+
+	let nextChunkGroupIndex = 0;
+	let nextFreeModulePreOrderIndex = 0;
+	let nextFreeModulePostOrderIndex = 0;
+
+	/** @type {Map} */
+	const blockChunkGroups = new Map();
+
+	/** @type {Map>} */
+	const blocksByChunkGroups = new Map();
+
+	/** @typedef {Map} NamedChunkGroup */
+
+	/** @type {NamedChunkGroup} */
+	const namedChunkGroups = new Map();
+
+	/** @type {NamedChunkGroup} */
+	const namedAsyncEntrypoints = new Map();
+
+	/** @type {Map} */
+	const depModuleAsyncEntrypoints = new Map();
+
+	/** @type {Set} */
+	const outdatedOrderIndexChunkGroups = new Set();
+
+	const ADD_AND_ENTER_ENTRY_MODULE = 0;
+	const ADD_AND_ENTER_MODULE = 1;
+	const ENTER_MODULE = 2;
+	const PROCESS_BLOCK = 3;
+	const PROCESS_ENTRY_BLOCK = 4;
+	const LEAVE_MODULE = 5;
+
+	/** @type {QueueItem[]} */
+	let queue = [];
+
+	/** @typedef {Set<[ChunkGroupInfo, QueueItem | null]>} ConnectList */
+	/** @type {Map} */
+	const queueConnect = new Map();
+	/** @type {Set} */
+	const chunkGroupsForCombining = new Set();
+
+	// Fill queue with entrypoint modules
+	// Create ChunkGroupInfo for entrypoints
+	for (const [chunkGroup, modules] of inputEntrypointsAndModules) {
+		const runtime = getEntryRuntime(
+			compilation,
+			/** @type {string} */ (chunkGroup.name),
+			chunkGroup.options
+		);
+		/** @type {ChunkGroupInfo} */
+		const chunkGroupInfo = {
+			depModule: null,
+			circular: false,
+			initialized: false,
+			chunkGroup,
+			runtime,
+			minAvailableModules: undefined,
+			availableModulesToBeMerged: [],
+			skippedItems: undefined,
+			resultingAvailableModules: undefined,
+			children: undefined,
+			availableSources: undefined,
+			availableChildren: undefined,
+			preOrderIndex: 0,
+			postOrderIndex: 0,
+			chunkLoading:
+				chunkGroup.options.chunkLoading !== undefined
+					? chunkGroup.options.chunkLoading !== false
+					: compilation.outputOptions.chunkLoading !== false,
+			asyncChunks:
+				chunkGroup.options.asyncChunks !== undefined
+					? chunkGroup.options.asyncChunks
+					: compilation.outputOptions.asyncChunks !== false
+		};
+		chunkGroup.index = nextChunkGroupIndex++;
+		if (chunkGroup.getNumberOfParents() > 0) {
+			// minAvailableModules for child entrypoints are unknown yet, set to undefined.
+			// This means no module is added until other sets are merged into
+			// this minAvailableModules (by the parent entrypoints)
+			const skippedItems = new Set(modules);
+			chunkGroupInfo.skippedItems = skippedItems;
+			chunkGroupsForCombining.add(chunkGroupInfo);
+		} else {
+			// The application may start here: We start with an empty list of available modules
+			chunkGroupInfo.minAvailableModules = ZERO_BIGINT;
+			const chunk = chunkGroup.getEntrypointChunk();
+			for (const module of modules) {
+				queue.push({
+					action: ADD_AND_ENTER_MODULE,
+					block: module,
+					module,
+					chunk,
+					chunkGroup,
+					chunkGroupInfo
+				});
+			}
+		}
+		chunkGroupInfoMap.set(chunkGroup, chunkGroupInfo);
+		if (chunkGroup.name) {
+			namedChunkGroups.set(chunkGroup.name, chunkGroupInfo);
+		}
+	}
+	// Fill availableSources with parent-child dependencies between entrypoints
+	for (const chunkGroupInfo of chunkGroupsForCombining) {
+		const { chunkGroup } = chunkGroupInfo;
+		chunkGroupInfo.availableSources = new Set();
+		for (const parent of chunkGroup.parentsIterable) {
+			const parentChunkGroupInfo =
+				/** @type {ChunkGroupInfo} */
+				(chunkGroupInfoMap.get(parent));
+			chunkGroupInfo.availableSources.add(parentChunkGroupInfo);
+			if (parentChunkGroupInfo.availableChildren === undefined) {
+				parentChunkGroupInfo.availableChildren = new Set();
+			}
+			parentChunkGroupInfo.availableChildren.add(chunkGroupInfo);
+		}
+	}
+	// pop() is used to read from the queue
+	// so it need to be reversed to be iterated in
+	// correct order
+	queue.reverse();
+
+	/** @type {Set} */
+	const outdatedChunkGroupInfo = new Set();
+	/** @type {Set<[ChunkGroupInfo, QueueItem | null]>} */
+	const chunkGroupsForMerging = new Set();
+	/** @type {QueueItem[]} */
+	let queueDelayed = [];
+
+	/** @type {[Module, ModuleGraphConnection[]][]} */
+	const skipConnectionBuffer = [];
+	/** @type {Module[]} */
+	const skipBuffer = [];
+	/** @type {QueueItem[]} */
+	const queueBuffer = [];
+
+	/** @type {Module} */
+	let module;
+	/** @type {Chunk} */
+	let chunk;
+	/** @type {ChunkGroup} */
+	let chunkGroup;
+	/** @type {DependenciesBlock} */
+	let block;
+	/** @type {ChunkGroupInfo} */
+	let chunkGroupInfo;
+
+	// For each async Block in graph
+	/**
+	 * Processes the provided b.
+	 * @param {AsyncDependenciesBlock} b iterating over each Async DepBlock
+	 * @returns {void}
+	 */
+	const iteratorBlock = (b) => {
+		// 1. We create a chunk group with single chunk in it for this Block
+		// but only once (blockChunkGroups map)
+		/** @type {ChunkGroupInfo | undefined} */
+		let cgi = blockChunkGroups.get(b);
+		/** @type {ChunkGroup | undefined} */
+		let c;
+		/** @type {Entrypoint | undefined} */
+		let entrypoint;
+		/** @type {Module | null} */
+		const depModule = moduleGraph.getModule(b.dependencies[0]);
+		const entryOptions = b.groupOptions && b.groupOptions.entryOptions;
+		if (cgi === undefined) {
+			const chunkName = (b.groupOptions && b.groupOptions.name) || b.chunkName;
+			if (entryOptions) {
+				cgi = namedAsyncEntrypoints.get(/** @type {string} */ (chunkName));
+				if (!cgi && !b.circular && depModule) {
+					cgi = depModuleAsyncEntrypoints.get(depModule);
+				}
+				if (!cgi) {
+					entrypoint = compilation.addAsyncEntrypoint(
+						entryOptions,
+						module,
+						/** @type {DependencyLocation} */ (b.loc),
+						/** @type {string} */ (b.request)
+					);
+					maskByChunk.set(entrypoint.chunks[0], ZERO_BIGINT);
+					entrypoint.index = nextChunkGroupIndex++;
+					cgi = {
+						depModule,
+						circular: b.circular,
+						chunkGroup: entrypoint,
+						initialized: false,
+						runtime:
+							entrypoint.options.runtime ||
+							/** @type {string | undefined} */ (entrypoint.name),
+						minAvailableModules: ZERO_BIGINT,
+						availableModulesToBeMerged: [],
+						skippedItems: undefined,
+						resultingAvailableModules: undefined,
+						children: undefined,
+						availableSources: undefined,
+						availableChildren: undefined,
+						preOrderIndex: 0,
+						postOrderIndex: 0,
+						chunkLoading:
+							entryOptions.chunkLoading !== undefined
+								? entryOptions.chunkLoading !== false
+								: chunkGroupInfo.chunkLoading,
+						asyncChunks:
+							entryOptions.asyncChunks !== undefined
+								? entryOptions.asyncChunks
+								: chunkGroupInfo.asyncChunks
+					};
+					chunkGroupInfoMap.set(
+						entrypoint,
+						/** @type {ChunkGroupInfo} */
+						(cgi)
+					);
+
+					chunkGraph.connectBlockAndChunkGroup(b, entrypoint);
+					if (chunkName) {
+						namedAsyncEntrypoints.set(
+							chunkName,
+							/** @type {ChunkGroupInfo} */
+							(cgi)
+						);
+					}
+					if (!b.circular && depModule) {
+						depModuleAsyncEntrypoints.set(
+							depModule,
+							/** @type {ChunkGroupInfo} */ (cgi)
+						);
+					}
+				} else {
+					entrypoint = /** @type {Entrypoint} */ (cgi.chunkGroup);
+					// Fill in options the existing entrypoint hasn't set yet. We never
+					// overwrite: blocks that dedupe to one entrypoint (e.g. several
+					// workers pointing at the same module) legitimately carry distinct
+					// values such as `runtime`, so the first block to create the
+					// entrypoint wins and later ones only contribute missing keys.
+					// `name` is excluded: it is the entrypoint's identity, fixed at
+					// creation (and used to key namedChunkGroups), so back-filling it
+					// from a block that deduped in via its module would leave
+					// `entrypoint.name` out of sync and make module codegen
+					// order-dependent, which breaks persistent caching.
+					const existingOptions = entrypoint.options;
+					for (const key_ of Object.keys(entryOptions)) {
+						const key =
+							/** @type {keyof EntryOptions} */
+							(key_);
+						if (key === "name") continue;
+						if (entryOptions[key] === undefined) continue;
+						if (existingOptions[key] !== undefined) continue;
+						/** @type {EntryOptions[keyof EntryOptions]} */
+						(existingOptions[key]) = entryOptions[key];
+					}
+					entrypoint.addOrigin(
+						module,
+						/** @type {DependencyLocation} */ (b.loc),
+						/** @type {string} */ (b.request)
+					);
+					chunkGraph.connectBlockAndChunkGroup(b, entrypoint);
+				}
+
+				// 2. We enqueue the DependenciesBlock for traversal
+				queueDelayed.push({
+					action: PROCESS_ENTRY_BLOCK,
+					block: b,
+					module,
+					chunk: entrypoint.chunks[0],
+					chunkGroup: entrypoint,
+					chunkGroupInfo: /** @type {ChunkGroupInfo} */ (cgi)
+				});
+			} else if (!chunkGroupInfo.asyncChunks || !chunkGroupInfo.chunkLoading) {
+				// Just queue the block into the current chunk group
+				queue.push({
+					action: PROCESS_BLOCK,
+					block: b,
+					module,
+					chunk,
+					chunkGroup,
+					chunkGroupInfo
+				});
+			} else {
+				cgi = chunkName ? namedChunkGroups.get(chunkName) : undefined;
+				if (!cgi) {
+					c = compilation.addChunkInGroup(
+						b.groupOptions || b.chunkName,
+						module,
+						/** @type {DependencyLocation} */ (b.loc),
+						/** @type {string} */ (b.request)
+					);
+					maskByChunk.set(c.chunks[0], ZERO_BIGINT);
+					c.index = nextChunkGroupIndex++;
+					cgi = {
+						depModule,
+						circular: b.circular,
+						initialized: false,
+						chunkGroup: c,
+						runtime: chunkGroupInfo.runtime,
+						minAvailableModules: undefined,
+						availableModulesToBeMerged: [],
+						skippedItems: undefined,
+						resultingAvailableModules: undefined,
+						children: undefined,
+						availableSources: undefined,
+						availableChildren: undefined,
+						preOrderIndex: 0,
+						postOrderIndex: 0,
+						chunkLoading: chunkGroupInfo.chunkLoading,
+						asyncChunks: chunkGroupInfo.asyncChunks
+					};
+					allCreatedChunkGroups.add(c);
+					chunkGroupInfoMap.set(c, cgi);
+					if (chunkName) {
+						namedChunkGroups.set(chunkName, cgi);
+					}
+				} else {
+					c = cgi.chunkGroup;
+					if (c.isInitial()) {
+						compilation.errors.push(
+							new (getAsyncDependencyToInitialChunkError())(
+								/** @type {string} */ (chunkName),
+								module,
+								/** @type {DependencyLocation} */ (b.loc)
+							)
+						);
+						c = chunkGroup;
+					} else {
+						c.addOptions(b.groupOptions);
+					}
+					c.addOrigin(
+						module,
+						/** @type {DependencyLocation} */ (b.loc),
+						/** @type {string} */ (b.request)
+					);
+				}
+				blockConnections.set(b, []);
+			}
+			blockChunkGroups.set(b, /** @type {ChunkGroupInfo} */ (cgi));
+		} else if (entryOptions) {
+			entrypoint = /** @type {Entrypoint} */ (cgi.chunkGroup);
+		} else {
+			c = cgi.chunkGroup;
+		}
+
+		if (c !== undefined) {
+			// 2. We store the connection for the block
+			// to connect it later if needed
+			/** @type {BlockChunkGroupConnection[]} */
+			(blockConnections.get(b)).push({
+				originChunkGroupInfo: chunkGroupInfo,
+				chunkGroup: c
+			});
+
+			// 3. We enqueue the chunk group info creation/updating
+			let connectList = queueConnect.get(chunkGroupInfo);
+			if (connectList === undefined) {
+				/** @type {ConnectList} */
+				connectList = new Set();
+				queueConnect.set(chunkGroupInfo, connectList);
+			}
+			connectList.add([
+				/** @type {ChunkGroupInfo} */ (cgi),
+				{
+					action: PROCESS_BLOCK,
+					block: b,
+					module,
+					chunk: c.chunks[0],
+					chunkGroup: c,
+					chunkGroupInfo: /** @type {ChunkGroupInfo} */ (cgi)
+				}
+			]);
+		} else if (
+			entrypoint !== undefined &&
+			(chunkGroupInfo.circular || chunkGroupInfo.depModule !== depModule)
+		) {
+			chunkGroupInfo.chunkGroup.addAsyncEntrypoint(entrypoint);
+		}
+	};
+
+	/**
+	 * Processes the provided block.
+	 * @param {DependenciesBlock} block the block
+	 * @returns {void}
+	 */
+	const processBlock = (block) => {
+		statProcessedBlocks++;
+		// get prepared block info
+		const blockModules = getBlockModules(block, chunkGroupInfo.runtime);
+
+		if (blockModules !== undefined) {
+			const minAvailableModules =
+				/** @type {bigint} */
+				(chunkGroupInfo.minAvailableModules);
+			// Buffer items because order need to be reversed to get indices correct
+			// Traverse all referenced modules
+			for (let i = 0, len = blockModules.length; i < len; i += 3) {
+				const refModule = /** @type {Module} */ (blockModules[i]);
+				// For single comparisons this might be cheaper
+				const isModuleInChunk = chunkGraph.isModuleInChunk(refModule, chunk);
+
+				if (isModuleInChunk) {
+					// skip early if already connected
+					continue;
+				}
+
+				const activeState = /** @type {ConnectionState} */ (
+					blockModules[i + 1]
+				);
+				if (activeState !== true) {
+					const connections = /** @type {ModuleGraphConnection[]} */ (
+						blockModules[i + 2]
+					);
+					skipConnectionBuffer.push([refModule, connections]);
+					// We skip inactive connections
+					if (activeState === false) continue;
+				} else if (
+					// assigning ordinals lazily here keeps masks small
+					isOrdinalSetInMask(minAvailableModules, getModuleOrdinal(refModule))
+				) {
+					// already in parent chunks, skip it for now
+					skipBuffer.push(refModule);
+					continue;
+				}
+				// enqueue, then add and enter to be in the correct order
+				// this is relevant with circular dependencies
+				queueBuffer.push({
+					action: activeState === true ? ADD_AND_ENTER_MODULE : PROCESS_BLOCK,
+					block: refModule,
+					module: refModule,
+					chunk,
+					chunkGroup,
+					chunkGroupInfo
+				});
+			}
+			// Add buffered items in reverse order
+			if (skipConnectionBuffer.length > 0) {
+				let { skippedModuleConnections } = chunkGroupInfo;
+				if (skippedModuleConnections === undefined) {
+					chunkGroupInfo.skippedModuleConnections = skippedModuleConnections =
+						new Set();
+				}
+				// Drained by popping (still reverse order): `length = 0` would drop
+				// the buffer's backing store, so the next push reallocates one.
+				while (skipConnectionBuffer.length > 0) {
+					skippedModuleConnections.add(
+						/** @type {[Module, ModuleGraphConnection[]]} */
+						(skipConnectionBuffer.pop())
+					);
+				}
+			}
+			if (skipBuffer.length > 0) {
+				let { skippedItems } = chunkGroupInfo;
+				if (skippedItems === undefined) {
+					chunkGroupInfo.skippedItems = skippedItems = new Set();
+				}
+				while (skipBuffer.length > 0) {
+					skippedItems.add(/** @type {Module} */ (skipBuffer.pop()));
+				}
+			}
+			if (queueBuffer.length > 0) {
+				while (queueBuffer.length > 0) {
+					queue.push(/** @type {QueueItem} */ (queueBuffer.pop()));
+				}
+			}
+		}
+
+		// Traverse all Blocks
+		for (const b of block.blocks) {
+			iteratorBlock(b);
+		}
+
+		if (block.blocks.length > 0 && module !== block) {
+			blocksWithNestedBlocks.add(block);
+		}
+	};
+
+	/**
+	 * Process entry block.
+	 * @param {DependenciesBlock} block the block
+	 * @returns {void}
+	 */
+	const processEntryBlock = (block) => {
+		statProcessedBlocks++;
+		// get prepared block info
+		const blockModules = getBlockModules(block, chunkGroupInfo.runtime);
+
+		if (blockModules !== undefined) {
+			// Traverse all referenced modules in reverse order
+			for (let i = blockModules.length - 3; i >= 0; i -= 3) {
+				const refModule = /** @type {Module} */ (blockModules[i]);
+				const activeState = /** @type {ConnectionState} */ (
+					blockModules[i + 1]
+				);
+				// enqueue, then add and enter to be in the correct order
+				// this is relevant with circular dependencies
+				queue.push({
+					action:
+						activeState === true ? ADD_AND_ENTER_ENTRY_MODULE : PROCESS_BLOCK,
+					block: refModule,
+					module: refModule,
+					chunk,
+					chunkGroup,
+					chunkGroupInfo
+				});
+			}
+		}
+
+		// Traverse all Blocks
+		for (const b of block.blocks) {
+			iteratorBlock(b);
+		}
+
+		if (block.blocks.length > 0 && module !== block) {
+			blocksWithNestedBlocks.add(block);
+		}
+	};
+
+	const processQueue = () => {
+		while (queue.length) {
+			statProcessedQueueItems++;
+			const queueItem = /** @type {QueueItem} */ (queue.pop());
+			module = queueItem.module;
+			block = queueItem.block;
+			chunk = queueItem.chunk;
+			chunkGroup = queueItem.chunkGroup;
+			chunkGroupInfo = queueItem.chunkGroupInfo;
+
+			switch (queueItem.action) {
+				case ADD_AND_ENTER_ENTRY_MODULE:
+					chunkGraph.connectChunkAndEntryModule(
+						chunk,
+						module,
+						/** @type {Entrypoint} */ (chunkGroup)
+					);
+				// fallthrough
+				case ADD_AND_ENTER_MODULE: {
+					const isModuleInChunk = chunkGraph.isModuleInChunk(module, chunk);
+
+					if (isModuleInChunk) {
+						// already connected, skip it
+						break;
+					}
+					// We connect Module and Chunk
+					chunkGraph.connectChunkAndModule(chunk, module);
+					const moduleOrdinal = getModuleOrdinal(module);
+					let chunkMask = /** @type {bigint} */ (maskByChunk.get(chunk));
+					chunkMask |= ONE_BIGINT << BigInt(moduleOrdinal);
+					maskByChunk.set(chunk, chunkMask);
+				}
+				// fallthrough
+				case ENTER_MODULE: {
+					const index = chunkGroup.getModulePreOrderIndex(module);
+					if (index === undefined) {
+						chunkGroup.setModulePreOrderIndex(
+							module,
+							chunkGroupInfo.preOrderIndex++
+						);
+					}
+
+					if (
+						moduleGraph.setPreOrderIndexIfUnset(
+							module,
+							nextFreeModulePreOrderIndex
+						)
+					) {
+						nextFreeModulePreOrderIndex++;
+					}
+
+					// reuse queueItem
+					queueItem.action = LEAVE_MODULE;
+					queue.push(queueItem);
+				}
+				// fallthrough
+				case PROCESS_BLOCK: {
+					processBlock(block);
+					break;
+				}
+				case PROCESS_ENTRY_BLOCK: {
+					processEntryBlock(block);
+					break;
+				}
+				case LEAVE_MODULE: {
+					const index = chunkGroup.getModulePostOrderIndex(module);
+					if (index === undefined) {
+						chunkGroup.setModulePostOrderIndex(
+							module,
+							chunkGroupInfo.postOrderIndex++
+						);
+					}
+
+					if (
+						moduleGraph.setPostOrderIndexIfUnset(
+							module,
+							nextFreeModulePostOrderIndex
+						)
+					) {
+						nextFreeModulePostOrderIndex++;
+					}
+					break;
+				}
+			}
+		}
+	};
+
+	/**
+	 * Calculate resulting available modules.
+	 * @param {ChunkGroupInfo} chunkGroupInfo The info object for the chunk group
+	 * @returns {bigint} The mask of available modules after the chunk group
+	 */
+	const calculateResultingAvailableModules = (chunkGroupInfo) => {
+		if (chunkGroupInfo.resultingAvailableModules !== undefined) {
+			return chunkGroupInfo.resultingAvailableModules;
+		}
+
+		let resultingAvailableModules = /** @type {bigint} */ (
+			chunkGroupInfo.minAvailableModules
+		);
+
+		// add the modules from the chunk group to the set
+		for (const chunk of chunkGroupInfo.chunkGroup.chunks) {
+			const mask = /** @type {bigint} */ (maskByChunk.get(chunk));
+			resultingAvailableModules |= mask;
+		}
+
+		return (chunkGroupInfo.resultingAvailableModules =
+			resultingAvailableModules);
+	};
+
+	const processConnectQueue = () => {
+		// Figure out new parents for chunk groups
+		// to get new available modules for these children
+		for (const [chunkGroupInfo, targets] of queueConnect) {
+			// 1. Add new targets to the list of children
+			if (chunkGroupInfo.children === undefined) {
+				chunkGroupInfo.children = new Set();
+			}
+			for (const [target] of targets) {
+				chunkGroupInfo.children.add(target);
+			}
+
+			// 2. Calculate resulting available modules
+			const resultingAvailableModules =
+				calculateResultingAvailableModules(chunkGroupInfo);
+
+			const runtime = chunkGroupInfo.runtime;
+
+			// 3. Update chunk group info
+			for (const [target, processBlock] of targets) {
+				target.availableModulesToBeMerged.push(resultingAvailableModules);
+				chunkGroupsForMerging.add([target, processBlock]);
+				const oldRuntime = target.runtime;
+				const newRuntime = mergeRuntime(oldRuntime, runtime);
+				if (oldRuntime !== newRuntime) {
+					target.runtime = newRuntime;
+					outdatedChunkGroupInfo.add(target);
+				}
+			}
+
+			statConnectedChunkGroups += targets.size;
+		}
+		queueConnect.clear();
+	};
+
+	const processChunkGroupsForMerging = () => {
+		statProcessedChunkGroupsForMerging += chunkGroupsForMerging.size;
+
+		// Execute the merge
+		for (const [info, processBlock] of chunkGroupsForMerging) {
+			const availableModulesToBeMerged = info.availableModulesToBeMerged;
+			const cachedMinAvailableModules = info.minAvailableModules;
+			let minAvailableModules = cachedMinAvailableModules;
+
+			statMergedAvailableModuleSets += availableModulesToBeMerged.length;
+
+			// Drained by popping: the fold is commutative, and `length = 0` would
+			// drop the backing store that the next enqueue then reallocates.
+			while (availableModulesToBeMerged.length > 0) {
+				const availableModules =
+					/** @type {bigint} */
+					(availableModulesToBeMerged.pop());
+				if (minAvailableModules === undefined) {
+					minAvailableModules = availableModules;
+				} else {
+					minAvailableModules &= availableModules;
+				}
+			}
+
+			const changed = minAvailableModules !== cachedMinAvailableModules;
+			if (changed) {
+				info.minAvailableModules = minAvailableModules;
+				info.resultingAvailableModules = undefined;
+				outdatedChunkGroupInfo.add(info);
+			}
+
+			if (processBlock) {
+				let blocks = blocksByChunkGroups.get(info);
+				if (!blocks) {
+					blocksByChunkGroups.set(info, (blocks = new Set()));
+				}
+
+				// Whether to walk block depends on minAvailableModules and input block.
+				// We can treat creating chunk group as a function with 2 input, entry block and minAvailableModules
+				// If input is the same, we can skip re-walk
+				let needWalkBlock = !info.initialized || changed;
+				if (!blocks.has(processBlock.block)) {
+					needWalkBlock = true;
+					blocks.add(processBlock.block);
+				}
+
+				if (needWalkBlock) {
+					info.initialized = true;
+					queueDelayed.push(processBlock);
+				}
+			}
+		}
+		chunkGroupsForMerging.clear();
+	};
+
+	const processChunkGroupsForCombining = () => {
+		for (const info of chunkGroupsForCombining) {
+			for (const source of /** @type {Set} */ (
+				info.availableSources
+			)) {
+				if (source.minAvailableModules === undefined) {
+					chunkGroupsForCombining.delete(info);
+					break;
+				}
+			}
+		}
+
+		for (const info of chunkGroupsForCombining) {
+			let availableModules = ZERO_BIGINT;
+			// combine minAvailableModules from all resultingAvailableModules
+			for (const source of /** @type {Set} */ (
+				info.availableSources
+			)) {
+				const resultingAvailableModules =
+					calculateResultingAvailableModules(source);
+				availableModules |= resultingAvailableModules;
+			}
+			info.minAvailableModules = availableModules;
+			info.resultingAvailableModules = undefined;
+			outdatedChunkGroupInfo.add(info);
+		}
+		chunkGroupsForCombining.clear();
+	};
+
+	const processOutdatedChunkGroupInfo = () => {
+		statChunkGroupInfoUpdated += outdatedChunkGroupInfo.size;
+		// Revisit skipped elements
+		for (const info of outdatedChunkGroupInfo) {
+			// 1. Reconsider skipped items
+			if (info.skippedItems !== undefined) {
+				const minAvailableModules =
+					/** @type {bigint} */
+					(info.minAvailableModules);
+				for (const module of info.skippedItems) {
+					const ordinal = getModuleOrdinal(module);
+					if (!isOrdinalSetInMask(minAvailableModules, ordinal)) {
+						queue.push({
+							action: ADD_AND_ENTER_MODULE,
+							block: module,
+							module,
+							chunk: info.chunkGroup.chunks[0],
+							chunkGroup: info.chunkGroup,
+							chunkGroupInfo: info
+						});
+						info.skippedItems.delete(module);
+					}
+				}
+			}
+
+			// 2. Reconsider skipped connections
+			if (info.skippedModuleConnections !== undefined) {
+				const minAvailableModules =
+					/** @type {bigint} */
+					(info.minAvailableModules);
+				for (const entry of info.skippedModuleConnections) {
+					const [module, connections] = entry;
+					const activeState = getActiveStateOfConnections(
+						connections,
+						info.runtime
+					);
+					if (activeState === false) continue;
+					if (activeState === true) {
+						const ordinal = getModuleOrdinal(module);
+						info.skippedModuleConnections.delete(entry);
+						if (isOrdinalSetInMask(minAvailableModules, ordinal)) {
+							/** @type {NonNullable} */
+							(info.skippedItems).add(module);
+							continue;
+						}
+					}
+					queue.push({
+						action: activeState === true ? ADD_AND_ENTER_MODULE : PROCESS_BLOCK,
+						block: module,
+						module,
+						chunk: info.chunkGroup.chunks[0],
+						chunkGroup: info.chunkGroup,
+						chunkGroupInfo: info
+					});
+				}
+			}
+
+			// 2. Reconsider children chunk groups
+			if (info.children !== undefined) {
+				statChildChunkGroupsReconnected += info.children.size;
+				for (const cgi of info.children) {
+					let connectList = queueConnect.get(info);
+					if (connectList === undefined) {
+						/** @type {ConnectList} */
+						connectList = new Set();
+						queueConnect.set(info, connectList);
+					}
+					connectList.add([cgi, null]);
+				}
+			}
+
+			// 3. Reconsider chunk groups for combining
+			if (info.availableChildren !== undefined) {
+				for (const cgi of info.availableChildren) {
+					chunkGroupsForCombining.add(cgi);
+				}
+			}
+			outdatedOrderIndexChunkGroups.add(info);
+		}
+		outdatedChunkGroupInfo.clear();
+	};
+
+	// Iterative traversal of the Module graph
+	// Recursive would be simpler to write but could result in Stack Overflows
+	while (queue.length || queueConnect.size) {
+		logger.time("visitModules: visiting");
+		processQueue();
+		logger.timeAggregateEnd("visitModules: prepare");
+		logger.timeEnd("visitModules: visiting");
+
+		if (chunkGroupsForCombining.size > 0) {
+			logger.time("visitModules: combine available modules");
+			processChunkGroupsForCombining();
+			logger.timeEnd("visitModules: combine available modules");
+		}
+
+		if (queueConnect.size > 0) {
+			logger.time("visitModules: calculating available modules");
+			processConnectQueue();
+			logger.timeEnd("visitModules: calculating available modules");
+
+			if (chunkGroupsForMerging.size > 0) {
+				logger.time("visitModules: merging available modules");
+				processChunkGroupsForMerging();
+				logger.timeEnd("visitModules: merging available modules");
+			}
+		}
+
+		if (outdatedChunkGroupInfo.size > 0) {
+			logger.time("visitModules: check modules for revisit");
+			processOutdatedChunkGroupInfo();
+			logger.timeEnd("visitModules: check modules for revisit");
+		}
+
+		// Run queueDelayed when all items of the queue are processed
+		// This is important to get the global indexing correct
+		// Async blocks should be processed after all sync blocks are processed
+		if (queue.length === 0) {
+			const tempQueue = queue;
+			queue = queueDelayed.reverse();
+			queueDelayed = tempQueue;
+		}
+	}
+
+	for (const info of outdatedOrderIndexChunkGroups) {
+		const { chunkGroup, runtime } = info;
+
+		const blocks = blocksByChunkGroups.get(info);
+
+		if (!blocks) {
+			continue;
+		}
+
+		for (const block of blocks) {
+			let preOrderIndex = 0;
+			let postOrderIndex = 0;
+			/**
+			 * Processes the provided current.
+			 * @param {DependenciesBlock} current current
+			 * @param {BlocksWithNestedBlocks} visited visited dependencies blocks
+			 */
+			const process = (current, visited) => {
+				const blockModules =
+					/** @type {BlockModulesInFlattenTuples} */
+					(getBlockModules(current, runtime));
+				for (let i = 0, len = blockModules.length; i < len; i += 3) {
+					const activeState = /** @type {ConnectionState} */ (
+						blockModules[i + 1]
+					);
+					if (activeState === false) {
+						continue;
+					}
+					const refModule = /** @type {Module} */ (blockModules[i]);
+					if (visited.has(refModule)) {
+						continue;
+					}
+
+					visited.add(refModule);
+
+					if (refModule) {
+						chunkGroup.setModulePreOrderIndex(refModule, preOrderIndex++);
+						process(refModule, visited);
+						chunkGroup.setModulePostOrderIndex(refModule, postOrderIndex++);
+					}
+				}
+			};
+			process(block, new Set());
+		}
+	}
+	outdatedOrderIndexChunkGroups.clear();
+	ordinalByModule.clear();
+
+	logger.log(
+		`${statProcessedQueueItems} queue items processed (${statProcessedBlocks} blocks)`
+	);
+	logger.log(`${statConnectedChunkGroups} chunk groups connected`);
+	logger.log(
+		`${statProcessedChunkGroupsForMerging} chunk groups processed for merging (${statMergedAvailableModuleSets} module sets, ${statForkedAvailableModules} forked, ${statForkedAvailableModulesCount} + ${statForkedAvailableModulesCountPlus} modules forked, ${statForkedMergedModulesCount} + ${statForkedMergedModulesCountPlus} modules merged into fork, ${statForkedResultModulesCount} resulting modules)`
+	);
+	logger.log(
+		`${statChunkGroupInfoUpdated} chunk group info updated (${statChildChunkGroupsReconnected} already connected chunk groups reconnected)`
+	);
+};
+
+/**
+ * Connects chunk groups.
+ * @param {Compilation} compilation the compilation
+ * @param {BlocksWithNestedBlocks} blocksWithNestedBlocks flag for blocks that have nested blocks
+ * @param {BlockConnections} blockConnections connection for blocks
+ * @param {MaskByChunk} maskByChunk mapping from chunk to module mask
+ */
+const connectChunkGroups = (
+	compilation,
+	blocksWithNestedBlocks,
+	blockConnections,
+	maskByChunk
+) => {
+	const { chunkGraph } = compilation;
+
+	/**
+	 * Helper function to check if all modules of a chunk are available
+	 * @param {ChunkGroup} chunkGroup the chunkGroup to scan
+	 * @param {bigint} availableModules the comparator set
+	 * @returns {boolean} return true if all modules of a chunk are available
+	 */
+	const areModulesAvailable = (chunkGroup, availableModules) => {
+		for (const chunk of chunkGroup.chunks) {
+			const chunkMask = /** @type {bigint} */ (maskByChunk.get(chunk));
+			if ((chunkMask & availableModules) !== chunkMask) return false;
+		}
+		return true;
+	};
+
+	// For each edge in the basic chunk graph
+	for (const [block, connections] of blockConnections) {
+		// 1. Check if connection is needed
+		// When none of the dependencies need to be connected
+		// we can skip all of them
+		// It's not possible to filter each item so it doesn't create inconsistent
+		// connections and modules can only create one version
+		// TODO maybe decide this per runtime
+		if (
+			// Blocks with nested blocks must stay connected — skipping orphans the
+			// nested block's chunk group from this block's chunk group parent.
+			!blocksWithNestedBlocks.has(block) &&
+			connections.every(({ chunkGroup, originChunkGroupInfo }) =>
+				areModulesAvailable(
+					chunkGroup,
+					/** @type {bigint} */ (originChunkGroupInfo.resultingAvailableModules)
+				)
+			)
+		) {
+			continue;
+		}
+
+		// 2. Foreach edge
+		for (let i = 0; i < connections.length; i++) {
+			const { chunkGroup, originChunkGroupInfo } = connections[i];
+
+			// 3. Connect block with chunk
+			chunkGraph.connectBlockAndChunkGroup(block, chunkGroup);
+
+			// 4. Connect chunk with parent
+			if (originChunkGroupInfo.chunkGroup.addChild(chunkGroup)) {
+				chunkGroup.addParent(originChunkGroupInfo.chunkGroup);
+			}
+		}
+	}
+};
+
+/**
+ * Remove all unconnected chunk groups
+ * @param {Compilation} compilation the compilation
+ * @param {Iterable} allCreatedChunkGroups all chunk groups that where created before
+ */
+const cleanupUnconnectedGroups = (compilation, allCreatedChunkGroups) => {
+	const { chunkGraph } = compilation;
+
+	for (const chunkGroup of allCreatedChunkGroups) {
+		if (chunkGroup.getNumberOfParents() === 0) {
+			for (const chunk of chunkGroup.chunks) {
+				compilation.chunks.delete(chunk);
+				chunkGraph.disconnectChunk(chunk);
+			}
+			chunkGraph.disconnectChunkGroup(chunkGroup);
+			chunkGroup.remove();
+		}
+	}
+};
+
+/**
+ * This method creates the Chunk graph from the Module graph
+ * @param {Compilation} compilation the compilation
+ * @param {InputEntrypointsAndModules} inputEntrypointsAndModules chunk groups which are processed with the modules
+ * @returns {void}
+ */
+const buildChunkGraph = (compilation, inputEntrypointsAndModules) => {
+	const logger = compilation.getLogger("webpack.buildChunkGraph");
+
+	// SHARED STATE
+
+	/** @type {BlockConnections} */
+	const blockConnections = new Map();
+
+	/** @type {AllCreatedChunkGroups} */
+	const allCreatedChunkGroups = new Set();
+
+	/** @type {ChunkGroupInfoMap} */
+	const chunkGroupInfoMap = new Map();
+
+	/** @type {BlocksWithNestedBlocks} */
+	const blocksWithNestedBlocks = new Set();
+
+	/** @type {MaskByChunk} */
+	const maskByChunk = new Map();
+
+	// PART ONE
+
+	logger.time("visitModules");
+	visitModules(
+		logger,
+		compilation,
+		inputEntrypointsAndModules,
+		chunkGroupInfoMap,
+		blockConnections,
+		blocksWithNestedBlocks,
+		allCreatedChunkGroups,
+		maskByChunk
+	);
+	logger.timeEnd("visitModules");
+
+	// PART TWO
+
+	logger.time("connectChunkGroups");
+	connectChunkGroups(
+		compilation,
+		blocksWithNestedBlocks,
+		blockConnections,
+		maskByChunk
+	);
+	logger.timeEnd("connectChunkGroups");
+
+	for (const [chunkGroup, chunkGroupInfo] of chunkGroupInfoMap) {
+		for (const chunk of chunkGroup.chunks) {
+			chunk.runtime = mergeRuntime(chunk.runtime, chunkGroupInfo.runtime);
+		}
+	}
+
+	// Cleanup work
+
+	logger.time("cleanup");
+	cleanupUnconnectedGroups(compilation, allCreatedChunkGroups);
+	logger.timeEnd("cleanup");
+};
+
+module.exports = buildChunkGraph;
diff --git a/lib/bun/BunTargetPlugin.js b/lib/bun/BunTargetPlugin.js
new file mode 100644
index 00000000000..c12d8c94c39
--- /dev/null
+++ b/lib/bun/BunTargetPlugin.js
@@ -0,0 +1,42 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author sheo13666q @sheo13666q
+*/
+
+"use strict";
+
+const ExternalsPlugin = require("../ExternalsPlugin");
+const { coreModules } = require("../node/nodeBuiltins");
+
+/** @typedef {import("../Compiler")} Compiler */
+
+// Bun exposes the node.js core modules; externalize them with the `node:`
+// specifier (Bun resolves both forms) like the deno target.
+
+// Bun's own built-in modules (`bun`, `bun:sqlite`, ...) are provided by the
+// runtime, never bundled.
+const BUN_BUILTINS = /^bun(?::|$)/;
+
+class BunTargetPlugin {
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
+	apply(compiler) {
+		new ExternalsPlugin(
+			(dependency) =>
+				dependency.category === "commonjs" ? "node-commonjs" : "module-import",
+			({ request }, callback) => {
+				if (!request) return callback();
+				if (request.startsWith("node:") || BUN_BUILTINS.test(request)) {
+					return callback(null, request);
+				}
+				if (coreModules.has(request)) return callback(null, `node:${request}`);
+				callback();
+			}
+		).apply(compiler);
+	}
+}
+
+module.exports = BunTargetPlugin;
diff --git a/lib/cache/AddBuildDependenciesPlugin.js b/lib/cache/AddBuildDependenciesPlugin.js
new file mode 100644
index 00000000000..e2ab6bcb2d6
--- /dev/null
+++ b/lib/cache/AddBuildDependenciesPlugin.js
@@ -0,0 +1,34 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+/** @typedef {import("../Compiler")} Compiler */
+
+const PLUGIN_NAME = "AddBuildDependenciesPlugin";
+
+class AddBuildDependenciesPlugin {
+	/**
+	 * Creates an instance of AddBuildDependenciesPlugin.
+	 * @param {Iterable} buildDependencies list of build dependencies
+	 */
+	constructor(buildDependencies) {
+		/** @type {Set} */
+		this.buildDependencies = new Set(buildDependencies);
+	}
+
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
+	apply(compiler) {
+		compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
+			compilation.buildDependencies.addAll(this.buildDependencies);
+		});
+	}
+}
+
+module.exports = AddBuildDependenciesPlugin;
diff --git a/lib/cache/AddManagedPathsPlugin.js b/lib/cache/AddManagedPathsPlugin.js
new file mode 100644
index 00000000000..5ef93998a6e
--- /dev/null
+++ b/lib/cache/AddManagedPathsPlugin.js
@@ -0,0 +1,44 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+/** @typedef {import("../Compiler")} Compiler */
+
+class AddManagedPathsPlugin {
+	/**
+	 * Creates an instance of AddManagedPathsPlugin.
+	 * @param {Iterable} managedPaths list of managed paths
+	 * @param {Iterable} immutablePaths list of immutable paths
+	 * @param {Iterable} unmanagedPaths list of unmanaged paths
+	 */
+	constructor(managedPaths, immutablePaths, unmanagedPaths) {
+		/** @type {Set} */
+		this.managedPaths = new Set(managedPaths);
+		/** @type {Set} */
+		this.immutablePaths = new Set(immutablePaths);
+		/** @type {Set} */
+		this.unmanagedPaths = new Set(unmanagedPaths);
+	}
+
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
+	apply(compiler) {
+		for (const managedPath of this.managedPaths) {
+			compiler.managedPaths.add(managedPath);
+		}
+		for (const immutablePath of this.immutablePaths) {
+			compiler.immutablePaths.add(immutablePath);
+		}
+		for (const unmanagedPath of this.unmanagedPaths) {
+			compiler.unmanagedPaths.add(unmanagedPath);
+		}
+	}
+}
+
+module.exports = AddManagedPathsPlugin;
diff --git a/lib/cache/IdleFileCachePlugin.js b/lib/cache/IdleFileCachePlugin.js
new file mode 100644
index 00000000000..3de39c007e7
--- /dev/null
+++ b/lib/cache/IdleFileCachePlugin.js
@@ -0,0 +1,247 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const Cache = require("../Cache");
+const ProgressPlugin = require("../ProgressPlugin");
+
+/** @typedef {import("../Compiler")} Compiler */
+/** @typedef {import("./PackFileCacheStrategy")} PackFileCacheStrategy */
+
+const BUILD_DEPENDENCIES_KEY = Symbol("build dependencies key");
+const PLUGIN_NAME = "IdleFileCachePlugin";
+
+class IdleFileCachePlugin {
+	/**
+	 * Creates an instance of IdleFileCachePlugin.
+	 * @param {PackFileCacheStrategy} strategy cache strategy
+	 * @param {number} idleTimeout timeout
+	 * @param {number} idleTimeoutForInitialStore initial timeout
+	 * @param {number} idleTimeoutAfterLargeChanges timeout after changes
+	 */
+	constructor(
+		strategy,
+		idleTimeout,
+		idleTimeoutForInitialStore,
+		idleTimeoutAfterLargeChanges
+	) {
+		/** @type {PackFileCacheStrategy} */
+		this.strategy = strategy;
+		/** @type {number} */
+		this.idleTimeout = idleTimeout;
+		/** @type {number} */
+		this.idleTimeoutForInitialStore = idleTimeoutForInitialStore;
+		/** @type {number} */
+		this.idleTimeoutAfterLargeChanges = idleTimeoutAfterLargeChanges;
+	}
+
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
+	apply(compiler) {
+		const strategy = this.strategy;
+		const idleTimeout = this.idleTimeout;
+		const idleTimeoutForInitialStore = Math.min(
+			idleTimeout,
+			this.idleTimeoutForInitialStore
+		);
+		const idleTimeoutAfterLargeChanges = this.idleTimeoutAfterLargeChanges;
+		const resolvedPromise = Promise.resolve();
+
+		let timeSpendInBuild = 0;
+		let timeSpendInStore = 0;
+		let avgTimeSpendInStore = 0;
+
+		/** @type {Map Promise>} */
+		const pendingIdleTasks = new Map();
+
+		compiler.cache.hooks.store.tap(
+			{ name: PLUGIN_NAME, stage: Cache.STAGE_DISK },
+			(identifier, etag, data) => {
+				pendingIdleTasks.set(identifier, () =>
+					strategy.store(identifier, etag, data)
+				);
+			}
+		);
+
+		compiler.cache.hooks.get.tapPromise(
+			{ name: PLUGIN_NAME, stage: Cache.STAGE_DISK },
+			(identifier, etag, gotHandlers) => {
+				const restore = () =>
+					strategy.restore(identifier, etag).then((cacheEntry) => {
+						if (cacheEntry === undefined) {
+							gotHandlers.push((result, callback) => {
+								if (result !== undefined) {
+									pendingIdleTasks.set(identifier, () =>
+										strategy.store(identifier, etag, result)
+									);
+								}
+								callback();
+							});
+						} else {
+							return cacheEntry;
+						}
+					});
+				const pendingTask = pendingIdleTasks.get(identifier);
+				if (pendingTask !== undefined) {
+					pendingIdleTasks.delete(identifier);
+					return pendingTask().then(restore);
+				}
+				return restore();
+			}
+		);
+
+		compiler.cache.hooks.storeBuildDependencies.tap(
+			{ name: PLUGIN_NAME, stage: Cache.STAGE_DISK },
+			(dependencies) => {
+				pendingIdleTasks.set(BUILD_DEPENDENCIES_KEY, () =>
+					Promise.resolve().then(() =>
+						strategy.storeBuildDependencies(dependencies)
+					)
+				);
+			}
+		);
+
+		compiler.cache.hooks.shutdown.tapPromise(
+			{ name: PLUGIN_NAME, stage: Cache.STAGE_DISK },
+			() => {
+				if (idleTimer) {
+					clearTimeout(idleTimer);
+					idleTimer = undefined;
+				}
+				isIdle = false;
+				const reportProgress = ProgressPlugin.getReporter(compiler);
+				const jobs = [...pendingIdleTasks.values()];
+				if (reportProgress) reportProgress(0, "process pending cache items");
+				const promises = jobs.map((fn) => fn());
+				pendingIdleTasks.clear();
+				promises.push(currentIdlePromise);
+				const promise = Promise.all(promises);
+				currentIdlePromise = promise.then(() => strategy.afterAllStored());
+				if (reportProgress) {
+					currentIdlePromise = currentIdlePromise.then(() => {
+						reportProgress(1, "stored");
+					});
+				}
+				return currentIdlePromise.then(() => {
+					// Reset strategy
+					if (strategy.clear) strategy.clear();
+				});
+			}
+		);
+
+		/** @type {Promise} */
+		let currentIdlePromise = resolvedPromise;
+		let isIdle = false;
+		let isInitialStore = true;
+		const processIdleTasks = () => {
+			if (isIdle) {
+				const startTime = Date.now();
+				if (pendingIdleTasks.size > 0) {
+					const promises = [currentIdlePromise];
+					const maxTime = startTime + 100;
+					let maxCount = 100;
+					for (const [filename, factory] of pendingIdleTasks) {
+						pendingIdleTasks.delete(filename);
+						promises.push(factory());
+						if (maxCount-- <= 0 || Date.now() > maxTime) break;
+					}
+					currentIdlePromise = Promise.all(
+						/** @type {Promise[]} */
+						(promises)
+					);
+					currentIdlePromise.then(() => {
+						timeSpendInStore += Date.now() - startTime;
+						// Allow to exit the process between
+						idleTimer = setTimeout(processIdleTasks, 0);
+						idleTimer.unref();
+					});
+					return;
+				}
+				currentIdlePromise = currentIdlePromise
+					.then(async () => {
+						await strategy.afterAllStored();
+						timeSpendInStore += Date.now() - startTime;
+						avgTimeSpendInStore =
+							Math.max(avgTimeSpendInStore, timeSpendInStore) * 0.9 +
+							timeSpendInStore * 0.1;
+						timeSpendInStore = 0;
+						timeSpendInBuild = 0;
+					})
+					.catch((err) => {
+						const logger = compiler.getInfrastructureLogger(PLUGIN_NAME);
+						logger.warn(`Background tasks during idle failed: ${err.message}`);
+						logger.debug(err.stack);
+					});
+				isInitialStore = false;
+			}
+		};
+		/** @type {ReturnType | undefined} */
+		let idleTimer;
+		compiler.cache.hooks.beginIdle.tap(
+			{ name: PLUGIN_NAME, stage: Cache.STAGE_DISK },
+			() => {
+				const isLargeChange = timeSpendInBuild > avgTimeSpendInStore * 2;
+				if (isInitialStore && idleTimeoutForInitialStore < idleTimeout) {
+					compiler
+						.getInfrastructureLogger(PLUGIN_NAME)
+						.log(
+							`Initial cache was generated and cache will be persisted in ${
+								idleTimeoutForInitialStore / 1000
+							}s.`
+						);
+				} else if (
+					isLargeChange &&
+					idleTimeoutAfterLargeChanges < idleTimeout
+				) {
+					compiler
+						.getInfrastructureLogger(PLUGIN_NAME)
+						.log(
+							`Spend ${Math.round(timeSpendInBuild) / 1000}s in build and ${
+								Math.round(avgTimeSpendInStore) / 1000
+							}s in average in cache store. This is considered as large change and cache will be persisted in ${
+								idleTimeoutAfterLargeChanges / 1000
+							}s.`
+						);
+				}
+				idleTimer = setTimeout(
+					() => {
+						idleTimer = undefined;
+						isIdle = true;
+						resolvedPromise.then(processIdleTasks);
+					},
+					Math.min(
+						isInitialStore ? idleTimeoutForInitialStore : Infinity,
+						isLargeChange ? idleTimeoutAfterLargeChanges : Infinity,
+						idleTimeout
+					)
+				);
+				idleTimer.unref();
+			}
+		);
+		compiler.cache.hooks.endIdle.tap(
+			{ name: PLUGIN_NAME, stage: Cache.STAGE_DISK },
+			() => {
+				if (idleTimer) {
+					clearTimeout(idleTimer);
+					idleTimer = undefined;
+				}
+				isIdle = false;
+			}
+		);
+		compiler.hooks.done.tap(PLUGIN_NAME, (stats) => {
+			// 10% build overhead is ignored, as it's not cacheable
+			timeSpendInBuild *= 0.9;
+			timeSpendInBuild +=
+				/** @type {number} */ (stats.endTime) -
+				/** @type {number} */ (stats.startTime);
+		});
+	}
+}
+
+module.exports = IdleFileCachePlugin;
diff --git a/lib/cache/MemoryCachePlugin.js b/lib/cache/MemoryCachePlugin.js
new file mode 100644
index 00000000000..ee4e992a4e0
--- /dev/null
+++ b/lib/cache/MemoryCachePlugin.js
@@ -0,0 +1,66 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const Cache = require("../Cache");
+
+/** @typedef {import("../Cache").Data} Data */
+/** @typedef {import("../Cache").Etag} Etag */
+/** @typedef {import("../Compiler")} Compiler */
+
+class MemoryCachePlugin {
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
+	apply(compiler) {
+		/** @type {Map} */
+		const cache = new Map();
+		compiler.cache.hooks.store.tap(
+			{ name: "MemoryCachePlugin", stage: Cache.STAGE_MEMORY },
+			(identifier, etag, data) => {
+				cache.set(identifier, { etag, data });
+			}
+		);
+		compiler.cache.hooks.get.tap(
+			{ name: "MemoryCachePlugin", stage: Cache.STAGE_MEMORY },
+			(identifier, etag, gotHandlers) => {
+				const cacheEntry = cache.get(identifier);
+				// A recorded miss: the whole chain was asked for this identifier already.
+				if (cacheEntry === null) return null;
+				// Etags are compared by identity — a lazy one is interned per source
+				// object, so equal content reached through a second object is a
+				// different etag. Hashing to tell those apart would cost every hit what
+				// laziness saves, so a mismatch falls through to the next stage instead:
+				// the file cache compares etags by value and can still answer. Returning
+				// `null` here would bail the hook and lose that (`Cache.get` maps it to
+				// `undefined` regardless, so it buys the caller nothing).
+				if (cacheEntry !== undefined && cacheEntry.etag === etag) {
+					return cacheEntry.data;
+				}
+				gotHandlers.push((result, callback) => {
+					if (result !== undefined) {
+						cache.set(identifier, { etag, data: result });
+					} else if (cacheEntry === undefined) {
+						// Record the miss only for an identifier nothing was known about:
+						// an entry reached with a different etag still answers its own.
+						cache.set(identifier, null);
+					}
+					return callback();
+				});
+			}
+		);
+		compiler.cache.hooks.shutdown.tap(
+			{ name: "MemoryCachePlugin", stage: Cache.STAGE_MEMORY },
+			() => {
+				cache.clear();
+			}
+		);
+	}
+}
+
+module.exports = MemoryCachePlugin;
diff --git a/lib/cache/MemoryWithGcCachePlugin.js b/lib/cache/MemoryWithGcCachePlugin.js
new file mode 100644
index 00000000000..524982fce2d
--- /dev/null
+++ b/lib/cache/MemoryWithGcCachePlugin.js
@@ -0,0 +1,158 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const Cache = require("../Cache");
+
+/** @typedef {import("../Cache").Data} Data */
+/** @typedef {import("../Cache").Etag} Etag */
+/** @typedef {import("../Compiler")} Compiler */
+
+/**
+ * Defines the memory with gc cache plugin options type used by this module.
+ * @typedef {object} MemoryWithGcCachePluginOptions
+ * @property {number} maxGenerations max generations
+ */
+
+const PLUGIN_NAME = "MemoryWithGcCachePlugin";
+
+class MemoryWithGcCachePlugin {
+	/**
+	 * Creates an instance of MemoryWithGcCachePlugin.
+	 * @param {MemoryWithGcCachePluginOptions} options options
+	 */
+	constructor({ maxGenerations }) {
+		/** @type {number} */
+		this._maxGenerations = maxGenerations;
+	}
+
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
+	apply(compiler) {
+		const maxGenerations = this._maxGenerations;
+		/** @type {Map} */
+		const cache = new Map();
+		/** @type {Map} */
+		const oldCache = new Map();
+		let generation = 0;
+		let cachePosition = 0;
+		const logger = compiler.getInfrastructureLogger(PLUGIN_NAME);
+		compiler.hooks.afterDone.tap(PLUGIN_NAME, () => {
+			generation++;
+			let clearedEntries = 0;
+			/** @type {undefined | string} */
+			let lastClearedIdentifier;
+			// Avoid coverage problems due indirect changes
+			/* istanbul ignore next */
+			for (const [identifier, entry] of oldCache) {
+				if (entry.until > generation) break;
+
+				oldCache.delete(identifier);
+				if (cache.get(identifier) === undefined) {
+					cache.delete(identifier);
+					clearedEntries++;
+					lastClearedIdentifier = identifier;
+				}
+			}
+			if (clearedEntries > 0 || oldCache.size > 0) {
+				logger.log(
+					`${cache.size - oldCache.size} active entries, ${
+						oldCache.size
+					} recently unused cached entries${
+						clearedEntries > 0
+							? `, ${clearedEntries} old unused cache entries removed e. g. ${lastClearedIdentifier}`
+							: ""
+					}`
+				);
+			}
+			let i = (cache.size / maxGenerations) | 0;
+			let j = cachePosition >= cache.size ? 0 : cachePosition;
+			cachePosition = j + i;
+			for (const [identifier, entry] of cache) {
+				if (j !== 0) {
+					j--;
+					continue;
+				}
+				if (entry !== undefined) {
+					// We don't delete the cache entry, but set it to undefined instead
+					// This reserves the location in the data table and avoids rehashing
+					// when constantly adding and removing entries.
+					// It will be deleted when removed from oldCache.
+					cache.set(identifier, undefined);
+					oldCache.delete(identifier);
+					oldCache.set(identifier, {
+						entry,
+						until: generation + maxGenerations
+					});
+					if (i-- === 0) break;
+				}
+			}
+		});
+		compiler.cache.hooks.store.tap(
+			{ name: PLUGIN_NAME, stage: Cache.STAGE_MEMORY },
+			(identifier, etag, data) => {
+				cache.set(identifier, { etag, data });
+			}
+		);
+		compiler.cache.hooks.get.tap(
+			{ name: PLUGIN_NAME, stage: Cache.STAGE_MEMORY },
+			(identifier, etag, gotHandlers) => {
+				const cacheEntry = cache.get(identifier);
+				// A recorded miss: the whole chain was asked for this identifier already.
+				if (cacheEntry === null) return null;
+				// Etags are compared by identity — a lazy one is interned per source
+				// object, so equal content reached through a second object is a
+				// different etag. Hashing to tell those apart would cost every hit what
+				// laziness saves, so a mismatch falls through to the next stage instead:
+				// the file cache compares etags by value and can still answer. Returning
+				// `null` here would bail the hook and lose that (`Cache.get` maps it to
+				// `undefined` regardless, so it buys the caller nothing).
+				let known = cacheEntry !== undefined;
+				if (cacheEntry !== undefined) {
+					if (cacheEntry.etag === etag) return cacheEntry.data;
+				} else {
+					const oldCacheEntry = oldCache.get(identifier);
+					if (oldCacheEntry !== undefined) {
+						const entry = oldCacheEntry.entry;
+						if (entry === null) {
+							oldCache.delete(identifier);
+							cache.set(identifier, entry);
+							return null;
+						}
+						known = true;
+						if (entry.etag === etag) {
+							oldCache.delete(identifier);
+							cache.set(identifier, entry);
+							return entry.data;
+						}
+					}
+				}
+				gotHandlers.push((result, callback) => {
+					if (result !== undefined) {
+						cache.set(identifier, { etag, data: result });
+					} else if (!known) {
+						// Record the miss only for an identifier nothing was known about:
+						// an entry reached with a different etag still answers its own.
+						cache.set(identifier, null);
+					}
+					return callback();
+				});
+			}
+		);
+		compiler.cache.hooks.shutdown.tap(
+			{ name: PLUGIN_NAME, stage: Cache.STAGE_MEMORY },
+			() => {
+				cache.clear();
+				oldCache.clear();
+			}
+		);
+	}
+}
+
+module.exports = MemoryWithGcCachePlugin;
diff --git a/lib/cache/PackFileCacheStrategy.js b/lib/cache/PackFileCacheStrategy.js
new file mode 100644
index 00000000000..75503d905cc
--- /dev/null
+++ b/lib/cache/PackFileCacheStrategy.js
@@ -0,0 +1,1890 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const FileSystemInfo = require("../FileSystemInfo");
+const ProgressPlugin = require("../ProgressPlugin");
+const { getReferencedFilenames } = require("../serialization/FileMiddleware");
+const SerializerMiddleware = require("../serialization/SerializerMiddleware");
+const LazySet = require("../util/LazySet");
+const formatSize = require("../util/formatSize");
+const makeSerializable = require("../util/makeSerializable");
+const memoize = require("../util/memoize");
+const {
+	NOT_SERIALIZABLE,
+	createFileSerializer
+} = require("../util/serialization");
+
+/** @typedef {import("../../declarations/WebpackOptions").SnapshotOptions} SnapshotOptions */
+/** @typedef {import("../Compilation").FileSystemDependencies} FileSystemDependencies */
+/** @typedef {import("../Cache").Data} Data */
+/** @typedef {import("../Cache").Etag} Etag */
+/** @typedef {import("../Compiler")} Compiler */
+/** @typedef {import("../FileSystemInfo").ResolveBuildDependenciesResult} ResolveBuildDependenciesResult */
+/** @typedef {import("../FileSystemInfo").ResolveResults} ResolveResults */
+/** @typedef {import("../FileSystemInfo").Snapshot} Snapshot */
+/** @typedef {import("../logging/Logger").Logger} Logger */
+/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */
+/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */
+/** @typedef {import("../util/Hash").HashFunction} HashFunction */
+/** @typedef {import("../util/fs").IntermediateFileSystem} IntermediateFileSystem */
+
+/** @typedef {Set} Items */
+/** @typedef {Set} BuildDependencies */
+/** @typedef {Map} ItemInfo */
+/** @typedef {{ firstSeen: number, size: number }} UnreferencedFile */
+/** @typedef {Map} UnreferencedFiles */
+
+// Unreferenced files are kept for this long to not race concurrent builds sharing the cache directory.
+const CLEANUP_GRACE_PERIOD = 30 * 60 * 1000;
+// Records when each unreferenced file was first seen. Aging by recorded time keeps
+// orphans expiring across caches restored with refreshed modification times.
+const UNREFERENCED_FILE = "unreferenced.json";
+// A file written this recently may belong to a concurrent build that reused the name,
+// in which case the recorded time describes the previous file and must not be trusted.
+const CLEANUP_RECENT_WRITE_PERIOD = 60 * 1000;
+
+class PackContainer {
+	/**
+	 * Creates an instance of PackContainer.
+	 * @param {Pack} data stored data
+	 * @param {string} version version identifier
+	 * @param {Snapshot} buildSnapshot snapshot of all build dependencies
+	 * @param {BuildDependencies} buildDependencies list of all unresolved build dependencies captured
+	 * @param {ResolveResults} resolveResults result of the resolved build dependencies
+	 * @param {Snapshot} resolveBuildDependenciesSnapshot snapshot of the dependencies of the build dependencies resolving
+	 */
+	constructor(
+		data,
+		version,
+		buildSnapshot,
+		buildDependencies,
+		resolveResults,
+		resolveBuildDependenciesSnapshot
+	) {
+		/** @type {Pack | (() => Pack)} */
+		this.data = data;
+		/** @type {string} */
+		this.version = version;
+		/** @type {Snapshot} */
+		this.buildSnapshot = buildSnapshot;
+		/** @type {BuildDependencies} */
+		this.buildDependencies = buildDependencies;
+		/** @type {ResolveResults} */
+		this.resolveResults = resolveResults;
+		/** @type {Snapshot} */
+		this.resolveBuildDependenciesSnapshot = resolveBuildDependenciesSnapshot;
+	}
+
+	/**
+	 * Serializes this instance into the provided serializer context.
+	 * @param {ObjectSerializerContext} context context
+	 */
+	serialize({ write, writeLazy }) {
+		write(this.version);
+		write(this.buildSnapshot);
+		write(this.buildDependencies);
+		write(this.resolveResults);
+		write(this.resolveBuildDependenciesSnapshot);
+		/** @type {NonNullable} */
+		(writeLazy)(this.data);
+	}
+
+	/**
+	 * Restores this instance from the provided deserializer context.
+	 * @param {ObjectDeserializerContext} context context
+	 */
+	deserialize({ read }) {
+		this.version = read();
+		this.buildSnapshot = read();
+		this.buildDependencies = read();
+		this.resolveResults = read();
+		this.resolveBuildDependenciesSnapshot = read();
+		this.data = read();
+	}
+}
+
+makeSerializable(
+	PackContainer,
+	"webpack/lib/cache/PackFileCacheStrategy",
+	"PackContainer"
+);
+
+const MIN_CONTENT_SIZE = 1024 * 1024; // 1 MB
+const CONTENT_COUNT_TO_MERGE = 10;
+const MIN_ITEMS_IN_FRESH_PACK = 100;
+const MAX_ITEMS_IN_FRESH_PACK = 50000;
+const MAX_TIME_IN_FRESH_PACK = 60 * 1000; // 1 min
+
+class PackItemInfo {
+	/**
+	 * Creates an instance of PackItemInfo.
+	 * @param {string} identifier identifier of item
+	 * @param {string | null | undefined} etag etag of item
+	 * @param {Data} value fresh value of item
+	 */
+	constructor(identifier, etag, value) {
+		/** @type {string} */
+		this.identifier = identifier;
+		/** @type {string | null | undefined} */
+		this.etag = etag;
+		/** @type {number} */
+		this.location = -1;
+		/** @type {number} */
+		this.lastAccess = Date.now();
+		/** @type {Data} */
+		this.freshValue = value;
+	}
+}
+
+class Pack {
+	/**
+	 * Creates an instance of Pack.
+	 * @param {Logger} logger a logger
+	 * @param {number} maxAge max age of cache items
+	 */
+	constructor(logger, maxAge) {
+		/** @type {ItemInfo} */
+		this.itemInfo = new Map();
+		/** @type {(string | undefined)[]} */
+		this.requests = [];
+		/** @type {undefined | NodeJS.Timeout} */
+		this.requestsTimeout = undefined;
+		/** @type {ItemInfo} */
+		this.freshContent = new Map();
+		/** @type {(undefined | PackContent)[]} */
+		this.content = [];
+		/** @type {boolean} */
+		this.invalid = false;
+		/** @type {Logger} */
+		this.logger = logger;
+		/** @type {number} */
+		this.maxAge = maxAge;
+	}
+
+	/**
+	 * Adds the provided identifier to the pack.
+	 * @param {string} identifier identifier
+	 */
+	_addRequest(identifier) {
+		this.requests.push(identifier);
+		if (this.requestsTimeout === undefined) {
+			this.requestsTimeout = setTimeout(() => {
+				this.requests.push(undefined);
+				this.requestsTimeout = undefined;
+			}, MAX_TIME_IN_FRESH_PACK);
+			if (this.requestsTimeout.unref) this.requestsTimeout.unref();
+		}
+	}
+
+	stopCapturingRequests() {
+		if (this.requestsTimeout !== undefined) {
+			clearTimeout(this.requestsTimeout);
+			this.requestsTimeout = undefined;
+		}
+	}
+
+	/**
+	 * Returns cached content.
+	 * @param {string} identifier unique name for the resource
+	 * @param {string | null} etag etag of the resource
+	 * @returns {Data} cached content
+	 */
+	get(identifier, etag) {
+		const info = this.itemInfo.get(identifier);
+		this._addRequest(identifier);
+		if (info === undefined) {
+			return;
+		}
+		if (info.etag !== etag) return null;
+		info.lastAccess = Date.now();
+		const loc = info.location;
+		if (loc === -1) {
+			return info.freshValue;
+		}
+		if (!this.content[loc]) {
+			return;
+		}
+		return /** @type {PackContent} */ (this.content[loc]).get(identifier);
+	}
+
+	/**
+	 * Updates value using the provided identifier.
+	 * @param {string} identifier unique name for the resource
+	 * @param {string | null} etag etag of the resource
+	 * @param {Data} data cached content
+	 * @returns {void}
+	 */
+	set(identifier, etag, data) {
+		if (!this.invalid) {
+			this.invalid = true;
+			this.logger.log(`Pack got invalid because of write to: ${identifier}`);
+		}
+		const info = this.itemInfo.get(identifier);
+		if (info === undefined) {
+			const newInfo = new PackItemInfo(identifier, etag, data);
+			this.itemInfo.set(identifier, newInfo);
+			this._addRequest(identifier);
+			this.freshContent.set(identifier, newInfo);
+		} else {
+			const loc = info.location;
+			if (loc >= 0) {
+				this._addRequest(identifier);
+				this.freshContent.set(identifier, info);
+				const content = /** @type {PackContent} */ (this.content[loc]);
+				content.delete(identifier);
+				if (content.items.size === 0) {
+					this.content[loc] = undefined;
+					this.logger.debug("Pack %d got empty and is removed", loc);
+				}
+			}
+			info.freshValue = data;
+			info.lastAccess = Date.now();
+			info.etag = etag;
+			info.location = -1;
+		}
+	}
+
+	getContentStats() {
+		let count = 0;
+		let size = 0;
+		for (const content of this.content) {
+			if (content !== undefined) {
+				count++;
+				const s = content.getSize();
+				if (s > 0) {
+					size += s;
+				}
+			}
+		}
+		return { count, size };
+	}
+
+	/**
+	 * Returns new location of data entries.
+	 * @returns {number} new location of data entries
+	 */
+	_findLocation() {
+		/** @type {number} */
+		let i;
+		for (i = 0; i < this.content.length && this.content[i] !== undefined; i++);
+		return i;
+	}
+
+	/**
+	 * Gc and update location.
+	 * @private
+	 * @param {Items} items items
+	 * @param {Items} usedItems used items
+	 * @param {number} newLoc new location
+	 */
+	_gcAndUpdateLocation(items, usedItems, newLoc) {
+		let count = 0;
+		/** @type {undefined | string} */
+		let lastGC;
+		const now = Date.now();
+		for (const identifier of items) {
+			const info = /** @type {PackItemInfo} */ (this.itemInfo.get(identifier));
+			if (now - info.lastAccess > this.maxAge) {
+				this.itemInfo.delete(identifier);
+				items.delete(identifier);
+				usedItems.delete(identifier);
+				count++;
+				lastGC = identifier;
+			} else {
+				info.location = newLoc;
+			}
+		}
+		if (count > 0) {
+			this.logger.log(
+				"Garbage Collected %d old items at pack %d (%d items remaining) e. g. %s",
+				count,
+				newLoc,
+				items.size,
+				lastGC
+			);
+		}
+	}
+
+	_persistFreshContent() {
+		/** @typedef {{ items: Items, map: Content, loc: number }} PackItem */
+		const itemsCount = this.freshContent.size;
+		if (itemsCount > 0) {
+			const packCount = Math.ceil(itemsCount / MAX_ITEMS_IN_FRESH_PACK);
+			const itemsPerPack = Math.ceil(itemsCount / packCount);
+			/** @type {PackItem[]} */
+			const packs = [];
+			let i = 0;
+			let ignoreNextTimeTick = false;
+			const createNextPack = () => {
+				const loc = this._findLocation();
+				this.content[loc] = /** @type {EXPECTED_ANY} */ (null); // reserve
+				/** @type {PackItem} */
+				const pack = {
+					items: new Set(),
+					map: new Map(),
+					loc
+				};
+				packs.push(pack);
+				return pack;
+			};
+			let pack = createNextPack();
+			if (this.requestsTimeout !== undefined) {
+				clearTimeout(this.requestsTimeout);
+			}
+			for (const identifier of this.requests) {
+				if (identifier === undefined) {
+					if (ignoreNextTimeTick) {
+						ignoreNextTimeTick = false;
+					} else if (pack.items.size >= MIN_ITEMS_IN_FRESH_PACK) {
+						i = 0;
+						pack = createNextPack();
+					}
+					continue;
+				}
+				const info = this.freshContent.get(identifier);
+				if (info === undefined) continue;
+				pack.items.add(identifier);
+				pack.map.set(identifier, info.freshValue);
+				info.location = pack.loc;
+				info.freshValue = undefined;
+				this.freshContent.delete(identifier);
+				if (++i > itemsPerPack) {
+					i = 0;
+					pack = createNextPack();
+					ignoreNextTimeTick = true;
+				}
+			}
+			this.requests.length = 0;
+			for (const pack of packs) {
+				this.content[pack.loc] = new PackContent(
+					pack.items,
+					new Set(pack.items),
+					new PackContentItems(pack.map)
+				);
+			}
+			this.logger.log(
+				`${itemsCount} fresh items in cache put into pack ${
+					packs.length > 1
+						? packs
+								.map((pack) => `${pack.loc} (${pack.items.size} items)`)
+								.join(", ")
+						: packs[0].loc
+				}`
+			);
+		}
+	}
+
+	/**
+	 * Merges small content files to a single content file
+	 */
+	_optimizeSmallContent() {
+		// 1. Find all small content files
+		// Treat unused content files separately to avoid
+		// a merge-split cycle
+		/** @type {number[]} */
+		const smallUsedContents = [];
+		/** @type {number} */
+		let smallUsedContentSize = 0;
+		/** @type {number[]} */
+		const smallUnusedContents = [];
+		/** @type {number} */
+		let smallUnusedContentSize = 0;
+		for (let i = 0; i < this.content.length; i++) {
+			const content = this.content[i];
+			if (content === undefined) continue;
+			if (content.outdated) continue;
+			const size = content.getSize();
+			if (size < 0 || size > MIN_CONTENT_SIZE) continue;
+			if (content.used.size > 0) {
+				smallUsedContents.push(i);
+				smallUsedContentSize += size;
+			} else {
+				smallUnusedContents.push(i);
+				smallUnusedContentSize += size;
+			}
+		}
+
+		// 2. Check if minimum number is reached
+		/** @type {number[]} */
+		let mergedIndices;
+		if (
+			smallUsedContents.length >= CONTENT_COUNT_TO_MERGE ||
+			smallUsedContentSize > MIN_CONTENT_SIZE
+		) {
+			mergedIndices = smallUsedContents;
+		} else if (
+			smallUnusedContents.length >= CONTENT_COUNT_TO_MERGE ||
+			smallUnusedContentSize > MIN_CONTENT_SIZE
+		) {
+			mergedIndices = smallUnusedContents;
+		} else {
+			return;
+		}
+
+		/** @type {PackContent[]} */
+		const mergedContent = [];
+
+		// 3. Remove old content entries
+		for (const i of mergedIndices) {
+			mergedContent.push(/** @type {PackContent} */ (this.content[i]));
+			this.content[i] = undefined;
+		}
+
+		// 4. Determine merged items
+		/** @type {Items} */
+		const mergedItems = new Set();
+		/** @type {Items} */
+		const mergedUsedItems = new Set();
+		/** @type {((map: Content) => Promise)[]} */
+		const addToMergedMap = [];
+		for (const content of mergedContent) {
+			for (const identifier of content.items) {
+				mergedItems.add(identifier);
+			}
+			for (const identifier of content.used) {
+				mergedUsedItems.add(identifier);
+			}
+			addToMergedMap.push(async (map) => {
+				// unpack existing content
+				// after that values are accessible in .content
+				await content.unpack(
+					"it should be merged with other small pack contents"
+				);
+				for (const [identifier, value] of /** @type {Content} */ (
+					content.content
+				)) {
+					map.set(identifier, value);
+				}
+			});
+		}
+
+		// 5. GC and update location of merged items
+		const newLoc = this._findLocation();
+		this._gcAndUpdateLocation(mergedItems, mergedUsedItems, newLoc);
+
+		// 6. If not empty, store content somewhere
+		if (mergedItems.size > 0) {
+			this.content[newLoc] = new PackContent(
+				mergedItems,
+				mergedUsedItems,
+				memoize(async () => {
+					/** @type {Content} */
+					const map = new Map();
+					await Promise.all(addToMergedMap.map((fn) => fn(map)));
+					return new PackContentItems(map);
+				})
+			);
+			this.logger.log(
+				"Merged %d small files with %d cache items into pack %d",
+				mergedContent.length,
+				mergedItems.size,
+				newLoc
+			);
+		}
+	}
+
+	/**
+	 * Split large content files with used and unused items
+	 * into two parts to separate used from unused items
+	 */
+	_optimizeUnusedContent() {
+		// 1. Find a large content file with used and unused items
+		for (let i = 0; i < this.content.length; i++) {
+			const content = this.content[i];
+			if (content === undefined) continue;
+			const size = content.getSize();
+			if (size < MIN_CONTENT_SIZE) continue;
+			const used = content.used.size;
+			const total = content.items.size;
+			if (used > 0 && used < total) {
+				// 2. Remove this content
+				this.content[i] = undefined;
+
+				// 3. Determine items for the used content file
+				const usedItems = new Set(content.used);
+				const newLoc = this._findLocation();
+				this._gcAndUpdateLocation(usedItems, usedItems, newLoc);
+
+				// 4. Create content file for used items
+				if (usedItems.size > 0) {
+					this.content[newLoc] = new PackContent(
+						usedItems,
+						new Set(usedItems),
+						async () => {
+							await content.unpack(
+								"it should be splitted into used and unused items"
+							);
+							/** @type {Content} */
+							const map = new Map();
+							for (const identifier of usedItems) {
+								map.set(
+									identifier,
+									/** @type {Content} */
+									(content.content).get(identifier)
+								);
+							}
+							return new PackContentItems(map);
+						}
+					);
+				}
+
+				// 5. Determine items for the unused content file
+				const unusedItems = new Set(content.items);
+				/** @type {Items} */
+				const usedOfUnusedItems = new Set();
+				for (const identifier of usedItems) {
+					unusedItems.delete(identifier);
+				}
+				const newUnusedLoc = this._findLocation();
+				this._gcAndUpdateLocation(unusedItems, usedOfUnusedItems, newUnusedLoc);
+
+				// 6. Create content file for unused items
+				if (unusedItems.size > 0) {
+					this.content[newUnusedLoc] = new PackContent(
+						unusedItems,
+						usedOfUnusedItems,
+						async () => {
+							await content.unpack(
+								"it should be splitted into used and unused items"
+							);
+							/** @type {Content} */
+							const map = new Map();
+							for (const identifier of unusedItems) {
+								map.set(
+									identifier,
+									/** @type {Content} */
+									(content.content).get(identifier)
+								);
+							}
+							return new PackContentItems(map);
+						}
+					);
+				}
+
+				this.logger.log(
+					"Split pack %d into pack %d with %d used items and pack %d with %d unused items",
+					i,
+					newLoc,
+					usedItems.size,
+					newUnusedLoc,
+					unusedItems.size
+				);
+
+				// optimizing only one of them is good enough and
+				// reduces the amount of serialization needed
+				return;
+			}
+		}
+	}
+
+	/**
+	 * Drops every content whose items all expired. Unlike a partial collection this
+	 * never unpacks, so it is not limited to a single content per store and lets a
+	 * long unused cache shrink in one go instead of one pack per build.
+	 */
+	_gcExpiredContent() {
+		const now = Date.now();
+		let packCount = 0;
+		let itemCount = 0;
+		for (let loc = 0; loc < this.content.length; loc++) {
+			const content = this.content[loc];
+			if (!content) continue;
+			let expired = true;
+			for (const identifier of content.items) {
+				const info = this.itemInfo.get(identifier);
+				if (info !== undefined && now - info.lastAccess <= this.maxAge) {
+					expired = false;
+					break;
+				}
+			}
+			if (!expired) continue;
+			for (const identifier of content.items) this.itemInfo.delete(identifier);
+			this.content[loc] = undefined;
+			packCount++;
+			itemCount += content.items.size;
+		}
+		if (packCount > 0) {
+			this.logger.log(
+				"Garbage Collected %d completely expired packs with %d items",
+				packCount,
+				itemCount
+			);
+		}
+	}
+
+	/**
+	 * Find the content with the oldest item and run GC on that.
+	 * Only runs for one content to avoid large invalidation.
+	 */
+	_gcOldestContent() {
+		/** @type {PackItemInfo | undefined} */
+		let oldest;
+		for (const info of this.itemInfo.values()) {
+			if (oldest === undefined || info.lastAccess < oldest.lastAccess) {
+				oldest = info;
+			}
+		}
+		// collecting expired content may have left no items at all
+		if (oldest === undefined) return;
+		if (Date.now() - oldest.lastAccess > this.maxAge) {
+			const loc = oldest.location;
+			if (loc < 0) return;
+			const content = /** @type {PackContent} */ (this.content[loc]);
+			const items = new Set(content.items);
+			const usedItems = new Set(content.used);
+			this._gcAndUpdateLocation(items, usedItems, loc);
+
+			this.content[loc] =
+				items.size > 0
+					? new PackContent(items, usedItems, async () => {
+							await content.unpack(
+								"it contains old items that should be garbage collected"
+							);
+							/** @type {Content} */
+							const map = new Map();
+							for (const identifier of items) {
+								map.set(
+									identifier,
+									/** @type {Content} */
+									(content.content).get(identifier)
+								);
+							}
+							return new PackContentItems(map);
+						})
+					: undefined;
+		}
+	}
+
+	/**
+	 * Serializes this instance into the provided serializer context.
+	 * @param {ObjectSerializerContext} context context
+	 */
+	serialize({ write, writeSeparate }) {
+		this._persistFreshContent();
+		this._optimizeSmallContent();
+		this._optimizeUnusedContent();
+		this._gcExpiredContent();
+		this._gcOldestContent();
+		for (const identifier of this.itemInfo.keys()) {
+			write(identifier);
+		}
+		write(null); // null as marker of the end of keys
+		for (const info of this.itemInfo.values()) {
+			write(info.etag);
+		}
+		for (const info of this.itemInfo.values()) {
+			write(info.lastAccess);
+		}
+		for (let i = 0; i < this.content.length; i++) {
+			const content = this.content[i];
+			if (content !== undefined) {
+				write(content.items);
+				content.writeLazy((lazy) =>
+					/** @type {NonNullable} */
+					(writeSeparate)(lazy, { name: `${i}` })
+				);
+			} else {
+				write(undefined); // undefined marks an empty content slot
+			}
+		}
+		write(null); // null as marker of the end of items
+	}
+
+	/**
+	 * Restores this instance from the provided deserializer context.
+	 * @param {ObjectDeserializerContext & { logger: Logger }} context context
+	 */
+	deserialize({ read, logger }) {
+		this.logger = logger;
+		{
+			const items = [];
+			let item = read();
+			while (item !== null) {
+				items.push(item);
+				item = read();
+			}
+			this.itemInfo.clear();
+			const infoItems = items.map((identifier) => {
+				const info = new PackItemInfo(identifier, undefined, undefined);
+				this.itemInfo.set(identifier, info);
+				return info;
+			});
+			for (const info of infoItems) {
+				info.etag = read();
+			}
+			for (const info of infoItems) {
+				info.lastAccess = read();
+			}
+		}
+		this.content.length = 0;
+		let items = read();
+		while (items !== null) {
+			if (items === undefined) {
+				this.content.push(items);
+			} else {
+				const idx = this.content.length;
+				const lazy = read();
+				this.content.push(
+					new PackContent(
+						items,
+						new Set(),
+						lazy,
+						logger,
+						`${this.content.length}`
+					)
+				);
+				for (const identifier of items) {
+					/** @type {PackItemInfo} */
+					(this.itemInfo.get(identifier)).location = idx;
+				}
+			}
+			items = read();
+		}
+	}
+}
+
+makeSerializable(Pack, "webpack/lib/cache/PackFileCacheStrategy", "Pack");
+
+/** @typedef {Map} Content */
+
+class PackContentItems {
+	/**
+	 * Creates an instance of PackContentItems.
+	 * @param {Content} map items
+	 */
+	constructor(map) {
+		/** @type {Content} */
+		this.map = map;
+	}
+
+	/**
+	 * Serializes this instance into the provided serializer context.
+	 * @param {ObjectSerializerContext & { logger: Logger, profile: boolean | undefined }} context context
+	 */
+	serialize({ write, snapshot, rollback, logger, profile }) {
+		if (profile) {
+			write(false);
+			for (const [key, value] of this.map) {
+				const s = snapshot();
+				try {
+					write(key);
+					const start = process.hrtime();
+					write(value);
+					const durationHr = process.hrtime(start);
+					const duration = durationHr[0] * 1000 + durationHr[1] / 1e6;
+					if (duration > 1) {
+						if (duration > 500) {
+							logger.error(`Serialization of '${key}': ${duration} ms`);
+						} else if (duration > 50) {
+							logger.warn(`Serialization of '${key}': ${duration} ms`);
+						} else if (duration > 10) {
+							logger.info(`Serialization of '${key}': ${duration} ms`);
+						} else if (duration > 5) {
+							logger.log(`Serialization of '${key}': ${duration} ms`);
+						} else {
+							logger.debug(`Serialization of '${key}': ${duration} ms`);
+						}
+					}
+				} catch (err) {
+					rollback(s);
+					if (err === NOT_SERIALIZABLE) continue;
+					const msg = "Skipped not serializable cache item";
+					const notSerializableErr = /** @type {Error} */ (err);
+					if (notSerializableErr.message.includes("ModuleBuildError")) {
+						logger.log(
+							`${msg} (in build error): ${notSerializableErr.message}`
+						);
+						logger.debug(
+							`${msg} '${key}' (in build error): ${notSerializableErr.stack}`
+						);
+					} else {
+						logger.warn(`${msg}: ${notSerializableErr.message}`);
+						logger.debug(`${msg} '${key}': ${notSerializableErr.stack}`);
+					}
+				}
+			}
+			write(null);
+			return;
+		}
+		// Try to serialize all at once
+		const s = snapshot();
+		try {
+			write(true);
+			write(this.map);
+		} catch (_err) {
+			rollback(s);
+
+			// Try to serialize each item on it's own
+			write(false);
+			for (const [key, value] of this.map) {
+				const s = snapshot();
+				try {
+					write(key);
+					write(value);
+				} catch (err) {
+					rollback(s);
+					if (err === NOT_SERIALIZABLE) continue;
+					const notSerializableErr = /** @type {Error} */ (err);
+					logger.warn(
+						`Skipped not serializable cache item '${key}': ${notSerializableErr.message}`
+					);
+					logger.debug(notSerializableErr.stack);
+				}
+			}
+			write(null);
+		}
+	}
+
+	/**
+	 * Restores this instance from the provided deserializer context.
+	 * @param {ObjectDeserializerContext & { logger: Logger, profile: boolean | undefined }} context context
+	 */
+	deserialize({ read, logger, profile }) {
+		if (read()) {
+			this.map = read();
+		} else if (profile) {
+			/** @type {Content} */
+			const map = new Map();
+			let key = read();
+			while (key !== null) {
+				const start = process.hrtime();
+				const value = read();
+				const durationHr = process.hrtime(start);
+				const duration = durationHr[0] * 1000 + durationHr[1] / 1e6;
+				if (duration > 1) {
+					if (duration > 100) {
+						logger.error(`Deserialization of '${key}': ${duration} ms`);
+					} else if (duration > 20) {
+						logger.warn(`Deserialization of '${key}': ${duration} ms`);
+					} else if (duration > 5) {
+						logger.info(`Deserialization of '${key}': ${duration} ms`);
+					} else if (duration > 2) {
+						logger.log(`Deserialization of '${key}': ${duration} ms`);
+					} else {
+						logger.debug(`Deserialization of '${key}': ${duration} ms`);
+					}
+				}
+				map.set(key, value);
+				key = read();
+			}
+			this.map = map;
+		} else {
+			/** @type {Content} */
+			const map = new Map();
+			let key = read();
+			while (key !== null) {
+				map.set(key, read());
+				key = read();
+			}
+			this.map = map;
+		}
+	}
+}
+
+makeSerializable(
+	PackContentItems,
+	"webpack/lib/cache/PackFileCacheStrategy",
+	"PackContentItems"
+);
+
+/** @typedef {(() => Promise | PackContentItems) & Partial<{ options: { size?: number } }>} LazyFunction */
+
+class PackContent {
+	/*
+		This class can be in these states:
+		   |   this.lazy    | this.content | this.outdated | state
+		A1 |   undefined    |     Map      |     false     | fresh content
+		A2 |   undefined    |     Map      |     true      | (will not happen)
+		B1 | lazy () => {}  |  undefined   |     false     | not deserialized
+		B2 | lazy () => {}  |  undefined   |     true      | not deserialized, but some items has been removed
+		C1 | lazy* () => {} |     Map      |     false     | deserialized
+		C2 | lazy* () => {} |     Map      |     true      | deserialized, and some items has been removed
+
+		this.used is a subset of this.items.
+		this.items is a subset of this.content.keys() resp. this.lazy().map.keys()
+		When this.outdated === false, this.items === this.content.keys() resp. this.lazy().map.keys()
+		When this.outdated === true, this.items should be used to recreated this.lazy/this.content.
+		When this.lazy and this.content is set, they contain the same data.
+		this.get must only be called with a valid item from this.items.
+		In state C this.lazy is unMemoized
+	*/
+
+	/**
+	 * Creates an instance of PackContent.
+	 * @param {Items} items keys
+	 * @param {Items} usedItems used keys
+	 * @param {PackContentItems | (() => Promise)} dataOrFn sync or async content
+	 * @param {Logger=} logger logger for logging
+	 * @param {string=} lazyName name of dataOrFn for logging
+	 */
+	constructor(items, usedItems, dataOrFn, logger, lazyName) {
+		/** @type {Items} */
+		this.items = items;
+		/** @type {LazyFunction | undefined} */
+		this.lazy = typeof dataOrFn === "function" ? dataOrFn : undefined;
+		/** @type {Content | undefined} */
+		this.content = typeof dataOrFn === "function" ? undefined : dataOrFn.map;
+		/** @type {boolean} */
+		this.outdated = false;
+		/** @type {Items} */
+		this.used = usedItems;
+		/** @type {Logger | undefined} */
+		this.logger = logger;
+		/** @type {string | undefined} */
+		this.lazyName = lazyName;
+	}
+
+	/**
+	 * Returns result.
+	 * @param {string} identifier identifier
+	 * @returns {string | Promise} result
+	 */
+	get(identifier) {
+		this.used.add(identifier);
+		if (this.content) {
+			return this.content.get(identifier);
+		}
+
+		const logger = /** @type {Logger} */ (this.logger);
+		// We are in state B
+		const { lazyName } = this;
+		/** @type {string | undefined} */
+		let timeMessage;
+		if (lazyName) {
+			// only log once
+			this.lazyName = undefined;
+			timeMessage = `restore cache content ${lazyName} (${formatSize(
+				this.getSize()
+			)})`;
+			logger.log(
+				`starting to restore cache content ${lazyName} (${formatSize(
+					this.getSize()
+				)}) because of request to: ${identifier}`
+			);
+			logger.time(timeMessage);
+		}
+		const value = /** @type {LazyFunction} */ (this.lazy)();
+		if ("then" in value) {
+			return value.then((data) => {
+				const map = data.map;
+				if (timeMessage) {
+					logger.timeEnd(timeMessage);
+				}
+				// Move to state C
+				this.content = map;
+				this.lazy = SerializerMiddleware.unMemoizeLazy(this.lazy);
+				return map.get(identifier);
+			});
+		}
+
+		const map = value.map;
+		if (timeMessage) {
+			logger.timeEnd(timeMessage);
+		}
+		// Move to state C
+		this.content = map;
+		this.lazy = SerializerMiddleware.unMemoizeLazy(this.lazy);
+		return map.get(identifier);
+	}
+
+	/**
+	 * Returns maybe a promise if lazy.
+	 * @param {string} reason explanation why unpack is necessary
+	 * @returns {void | Promise} maybe a promise if lazy
+	 */
+	unpack(reason) {
+		if (this.content) return;
+
+		const logger = /** @type {Logger} */ (this.logger);
+		// Move from state B to C
+		if (this.lazy) {
+			const { lazyName } = this;
+			/** @type {string | undefined} */
+			let timeMessage;
+			if (lazyName) {
+				// only log once
+				this.lazyName = undefined;
+				timeMessage = `unpack cache content ${lazyName} (${formatSize(
+					this.getSize()
+				)})`;
+				logger.log(
+					`starting to unpack cache content ${lazyName} (${formatSize(
+						this.getSize()
+					)}) because ${reason}`
+				);
+				logger.time(timeMessage);
+			}
+			const value =
+				/** @type {PackContentItems | Promise} */
+				(this.lazy());
+			if ("then" in value) {
+				return value.then((data) => {
+					if (timeMessage) {
+						logger.timeEnd(timeMessage);
+					}
+					this.content = data.map;
+				});
+			}
+			if (timeMessage) {
+				logger.timeEnd(timeMessage);
+			}
+			this.content = value.map;
+		}
+	}
+
+	/**
+	 * Returns the estimated size for the requested source type.
+	 * @returns {number} size of the content or -1 if not known
+	 */
+	getSize() {
+		if (!this.lazy) return -1;
+		const options =
+			/** @type {{ options: { size?: number } }} */
+			(this.lazy).options;
+		if (!options) return -1;
+		const size = options.size;
+		if (typeof size !== "number") return -1;
+		return size;
+	}
+
+	/**
+	 * Processes the provided identifier.
+	 * @param {string} identifier identifier
+	 */
+	delete(identifier) {
+		this.items.delete(identifier);
+		this.used.delete(identifier);
+		this.outdated = true;
+	}
+
+	/**
+	 * Processes the provided write.
+	 * @param {(lazy: LazyFunction) => (() => PackContentItems | Promise)} write write function
+	 * @returns {void}
+	 */
+	writeLazy(write) {
+		if (!this.outdated && this.lazy) {
+			// State B1 or C1
+			// this.lazy is still the valid deserialized version
+			write(this.lazy);
+			return;
+		}
+		if (!this.outdated && this.content) {
+			// State A1
+			const map = new Map(this.content);
+			// Move to state C1
+			this.lazy = SerializerMiddleware.unMemoizeLazy(
+				write(() => new PackContentItems(map))
+			);
+			return;
+		}
+		if (this.content) {
+			// State A2 or C2
+			/** @type {Content} */
+			const map = new Map();
+			for (const item of this.items) {
+				map.set(item, this.content.get(item));
+			}
+			// Move to state C1
+			this.outdated = false;
+			this.content = map;
+			this.lazy = SerializerMiddleware.unMemoizeLazy(
+				write(() => new PackContentItems(map))
+			);
+			return;
+		}
+		const logger = /** @type {Logger} */ (this.logger);
+		// State B2
+		const { lazyName } = this;
+		/** @type {string | undefined} */
+		let timeMessage;
+		if (lazyName) {
+			// only log once
+			this.lazyName = undefined;
+			timeMessage = `unpack cache content ${lazyName} (${formatSize(
+				this.getSize()
+			)})`;
+			logger.log(
+				`starting to unpack cache content ${lazyName} (${formatSize(
+					this.getSize()
+				)}) because it's outdated and need to be serialized`
+			);
+			logger.time(timeMessage);
+		}
+		const value = /** @type {LazyFunction} */ (this.lazy)();
+		this.outdated = false;
+		if ("then" in value) {
+			// Move to state B1
+			this.lazy = write(() =>
+				value.then((data) => {
+					if (timeMessage) {
+						logger.timeEnd(timeMessage);
+					}
+					const oldMap = data.map;
+					/** @type {Content} */
+					const map = new Map();
+					for (const item of this.items) {
+						map.set(item, oldMap.get(item));
+					}
+					// Move to state C1 (or maybe C2)
+					this.content = map;
+					this.lazy = SerializerMiddleware.unMemoizeLazy(this.lazy);
+
+					return new PackContentItems(map);
+				})
+			);
+		} else {
+			// Move to state C1
+			if (timeMessage) {
+				logger.timeEnd(timeMessage);
+			}
+			const oldMap = value.map;
+			/** @type {Content} */
+			const map = new Map();
+			for (const item of this.items) {
+				map.set(item, oldMap.get(item));
+			}
+			this.content = map;
+			this.lazy = write(() => new PackContentItems(map));
+		}
+	}
+}
+
+/**
+ * Allow collecting memory.
+ * @param {Buffer} buf buffer
+ * @returns {Buffer} buffer that can be collected
+ */
+const allowCollectingMemory = (buf) => {
+	const wasted = buf.buffer.byteLength - buf.byteLength;
+	if (wasted > 8192 && (wasted > 1048576 || wasted > buf.byteLength)) {
+		return Buffer.from(buf);
+	}
+	return buf;
+};
+
+class PackFileCacheStrategy {
+	/**
+	 * Creates an instance of PackFileCacheStrategy.
+	 * @param {object} options options
+	 * @param {Compiler} options.compiler the compiler
+	 * @param {IntermediateFileSystem} options.fs the filesystem
+	 * @param {string} options.context the context directory
+	 * @param {string} options.cacheLocation the location of the cache data
+	 * @param {string} options.version version identifier
+	 * @param {Logger} options.logger a logger
+	 * @param {SnapshotOptions} options.snapshot options regarding snapshotting
+	 * @param {number} options.maxAge max age of cache items
+	 * @param {boolean=} options.profile track and log detailed timing information for individual cache items
+	 * @param {boolean=} options.allowCollectingMemory allow to collect unused memory created during deserialization
+	 * @param {false | "gzip" | "brotli" | "zstd"=} options.compression compression used
+	 * @param {boolean=} options.readonly disable storing cache into filesystem
+	 */
+	constructor({
+		compiler,
+		fs,
+		context,
+		cacheLocation,
+		version,
+		logger,
+		snapshot,
+		maxAge,
+		profile,
+		allowCollectingMemory,
+		compression,
+		readonly
+	}) {
+		/** @type {import("../serialization/Serializer")} */
+		this.fileSerializer = createFileSerializer(
+			fs,
+			/** @type {HashFunction} */
+			(compiler.options.output.hashFunction)
+		);
+		/** @type {FileSystemInfo} */
+		this.fileSystemInfo = new FileSystemInfo(fs, {
+			managedPaths: snapshot.managedPaths,
+			immutablePaths: snapshot.immutablePaths,
+			logger: logger.getChildLogger("webpack.FileSystemInfo"),
+			hashFunction: compiler.options.output.hashFunction
+		});
+		/** @type {Compiler} */
+		this.compiler = compiler;
+		/** @type {IntermediateFileSystem} */
+		this.fs = fs;
+		/** @type {string} */
+		this.context = context;
+		/** @type {string} */
+		this.cacheLocation = cacheLocation;
+		/** @type {string} */
+		this.version = version;
+		/** @type {Logger} */
+		this.logger = logger;
+		/** @type {number} */
+		this.maxAge = maxAge;
+		/** @type {boolean | undefined} */
+		this.profile = profile;
+		/** @type {boolean | undefined} */
+		this.readonly = readonly;
+		/** @type {boolean | undefined} */
+		this.allowCollectingMemory = allowCollectingMemory;
+		// referenced names per on-disk file, versioned by mtime since concurrent
+		// processes may rewrite packs in place under the same name
+		/** @type {Map} */
+		this._referencedFilesCache = new Map();
+		/** @type {false | "gzip" | "brotli" | "zstd" | undefined} */
+		this.compression = compression;
+		// eslint-disable-next-line n/no-unsupported-features/node-builtins
+		if (compression === "zstd" && !require("zlib").createZstdCompress) {
+			throw new Error("cache.compression: 'zstd' requires Node.js >= 22.15.0");
+		}
+		/** @type {string} */
+		this._extension =
+			compression === "brotli"
+				? ".pack.br"
+				: compression === "gzip"
+					? ".pack.gz"
+					: compression === "zstd"
+						? ".pack.zst"
+						: ".pack";
+		/** @type {SnapshotOptions} */
+		this.snapshot = snapshot;
+		/** @type {BuildDependencies} */
+		this.buildDependencies = new Set();
+		/** @type {FileSystemDependencies} */
+		this.newBuildDependencies = new LazySet();
+		/** @type {Snapshot | undefined} */
+		this.resolveBuildDependenciesSnapshot = undefined;
+		/** @type {ResolveResults | undefined} */
+		this.resolveResults = undefined;
+		/** @type {Snapshot | undefined} */
+		this.buildSnapshot = undefined;
+		/** @type {Promise | undefined} */
+		this.packPromise = this._openPack();
+		/** @type {Promise} */
+		this.storePromise = Promise.resolve();
+	}
+
+	/**
+	 * Returns pack.
+	 * @returns {Promise} pack
+	 */
+	_getPack() {
+		if (this.packPromise === undefined) {
+			this.packPromise = this.storePromise.then(() => this._openPack());
+		}
+		return this.packPromise;
+	}
+
+	/**
+	 * Returns the pack.
+	 * @returns {Promise} the pack
+	 */
+	_openPack() {
+		const { logger, profile, cacheLocation, version } = this;
+		/** @type {Snapshot} */
+		let buildSnapshot;
+		/** @type {BuildDependencies} */
+		let buildDependencies;
+		/** @type {BuildDependencies} */
+		let newBuildDependencies;
+		/** @type {Snapshot} */
+		let resolveBuildDependenciesSnapshot;
+		/** @type {ResolveResults | undefined} */
+		let resolveResults;
+		logger.time("restore cache container");
+		return this.fileSerializer
+			.deserialize(null, {
+				filename: `${cacheLocation}/index${this._extension}`,
+				extension: `${this._extension}`,
+				logger,
+				profile,
+				retainedBuffer: this.allowCollectingMemory
+					? allowCollectingMemory
+					: undefined
+			})
+			.catch((err) => {
+				if (err.code !== "ENOENT") {
+					logger.warn(
+						`Restoring pack failed from ${cacheLocation}${this._extension}: ${err}`
+					);
+					logger.debug(err.stack);
+				} else {
+					logger.debug(
+						`No pack exists at ${cacheLocation}${this._extension}: ${err}`
+					);
+				}
+				return undefined;
+			})
+			.then((packContainer) => {
+				logger.timeEnd("restore cache container");
+				if (!packContainer) return;
+				if (!(packContainer instanceof PackContainer)) {
+					logger.warn(
+						`Restored pack from ${cacheLocation}${this._extension}, but contained content is unexpected.`,
+						packContainer
+					);
+					return;
+				}
+				if (packContainer.version !== version) {
+					logger.log(
+						`Restored pack from ${cacheLocation}${this._extension}, but version doesn't match.`
+					);
+					return;
+				}
+				logger.time("check build dependencies");
+				return Promise.all([
+					new Promise((resolve, _reject) => {
+						this.fileSystemInfo.checkSnapshotValid(
+							packContainer.buildSnapshot,
+							(err, valid) => {
+								if (err) {
+									logger.log(
+										`Restored pack from ${cacheLocation}${this._extension}, but checking snapshot of build dependencies errored: ${err}.`
+									);
+									logger.debug(err.stack);
+									return resolve(false);
+								}
+								if (!valid) {
+									logger.log(
+										`Restored pack from ${cacheLocation}${this._extension}, but build dependencies have changed.`
+									);
+									return resolve(false);
+								}
+								buildSnapshot = packContainer.buildSnapshot;
+								return resolve(true);
+							}
+						);
+					}),
+					new Promise((resolve, _reject) => {
+						this.fileSystemInfo.checkSnapshotValid(
+							packContainer.resolveBuildDependenciesSnapshot,
+							(err, valid) => {
+								if (err) {
+									logger.log(
+										`Restored pack from ${cacheLocation}${this._extension}, but checking snapshot of resolving of build dependencies errored: ${err}.`
+									);
+									logger.debug(err.stack);
+									return resolve(false);
+								}
+								if (valid) {
+									resolveBuildDependenciesSnapshot =
+										packContainer.resolveBuildDependenciesSnapshot;
+									buildDependencies = packContainer.buildDependencies;
+									resolveResults = packContainer.resolveResults;
+									return resolve(true);
+								}
+								logger.log(
+									"resolving of build dependencies is invalid, will re-resolve build dependencies"
+								);
+								this.fileSystemInfo.checkResolveResultsValid(
+									packContainer.resolveResults,
+									(err, valid) => {
+										if (err) {
+											logger.log(
+												`Restored pack from ${cacheLocation}${this._extension}, but resolving of build dependencies errored: ${err}.`
+											);
+											logger.debug(err.stack);
+											return resolve(false);
+										}
+										if (valid) {
+											newBuildDependencies = packContainer.buildDependencies;
+											resolveResults = packContainer.resolveResults;
+											return resolve(true);
+										}
+										logger.log(
+											`Restored pack from ${cacheLocation}${this._extension}, but build dependencies resolve to different locations.`
+										);
+										return resolve(false);
+									}
+								);
+							}
+						);
+					})
+				])
+					.catch((err) => {
+						logger.timeEnd("check build dependencies");
+						throw err;
+					})
+					.then(([buildSnapshotValid, resolveValid]) => {
+						logger.timeEnd("check build dependencies");
+						if (buildSnapshotValid && resolveValid) {
+							logger.time("restore cache content metadata");
+							const d =
+								/** @type {() => Pack} */
+								(packContainer.data)();
+							logger.timeEnd("restore cache content metadata");
+							return d;
+						}
+						return undefined;
+					});
+			})
+			.then((pack) => {
+				if (pack) {
+					pack.maxAge = this.maxAge;
+					this.buildSnapshot = buildSnapshot;
+					if (buildDependencies) this.buildDependencies = buildDependencies;
+					if (newBuildDependencies) {
+						this.newBuildDependencies.addAll(newBuildDependencies);
+					}
+					this.resolveResults = resolveResults;
+					this.resolveBuildDependenciesSnapshot =
+						resolveBuildDependenciesSnapshot;
+					return pack;
+				}
+				return new Pack(logger, this.maxAge);
+			})
+			.catch((err) => {
+				this.logger.warn(
+					`Restoring pack from ${cacheLocation}${this._extension} failed: ${err}`
+				);
+				this.logger.debug(err.stack);
+				return new Pack(logger, this.maxAge);
+			});
+	}
+
+	/**
+	 * Returns promise.
+	 * @param {string} identifier unique name for the resource
+	 * @param {Etag | null} etag etag of the resource
+	 * @param {Data} data cached content
+	 * @returns {Promise} promise
+	 */
+	store(identifier, etag, data) {
+		if (this.readonly) return Promise.resolve();
+
+		return this._getPack().then((pack) => {
+			pack.set(identifier, etag === null ? null : etag.toString(), data);
+		});
+	}
+
+	/**
+	 * Returns promise to the cached content.
+	 * @param {string} identifier unique name for the resource
+	 * @param {Etag | null} etag etag of the resource
+	 * @returns {Promise} promise to the cached content
+	 */
+	restore(identifier, etag) {
+		return this._getPack()
+			.then((pack) =>
+				pack.get(identifier, etag === null ? null : etag.toString())
+			)
+			.catch((err) => {
+				if (err && err.code !== "ENOENT") {
+					this.logger.warn(
+						`Restoring failed for ${identifier} from pack: ${err}`
+					);
+					this.logger.debug(err.stack);
+				}
+			});
+	}
+
+	/**
+	 * Stores build dependencies.
+	 * @param {FileSystemDependencies | Iterable} dependencies dependencies to store
+	 */
+	storeBuildDependencies(dependencies) {
+		if (this.readonly) return;
+		this.newBuildDependencies.addAll(dependencies);
+	}
+
+	afterAllStored() {
+		const packPromise = this.packPromise;
+		if (packPromise === undefined) return Promise.resolve();
+		const reportProgress = ProgressPlugin.getReporter(this.compiler);
+		return (this.storePromise = packPromise
+			.then((pack) => {
+				pack.stopCapturingRequests();
+				if (!pack.invalid) return;
+				this.packPromise = undefined;
+				this.logger.log("Storing pack...");
+				/** @type {undefined | Promise} */
+				let promise;
+				/** @type {Set} */
+				const newBuildDependencies = new Set();
+				for (const dep of this.newBuildDependencies) {
+					if (!this.buildDependencies.has(dep)) {
+						newBuildDependencies.add(dep);
+					}
+				}
+				if (newBuildDependencies.size > 0 || !this.buildSnapshot) {
+					if (reportProgress) reportProgress(0.5, "resolve build dependencies");
+					this.logger.debug(
+						`Capturing build dependencies... (${[...newBuildDependencies].join(", ")})`
+					);
+					promise = new Promise(
+						/**
+						 * Handles the callback logic for this hook.
+						 * @param {(value?: undefined) => void} resolve resolve
+						 * @param {(reason?: Error) => void} reject reject
+						 */
+						(resolve, reject) => {
+							this.logger.time("resolve build dependencies");
+							this.fileSystemInfo.resolveBuildDependencies(
+								this.context,
+								newBuildDependencies,
+								(err, result) => {
+									this.logger.timeEnd("resolve build dependencies");
+									if (err) return reject(err);
+
+									this.logger.time("snapshot build dependencies");
+									const {
+										files,
+										directories,
+										missing,
+										resolveResults,
+										resolveDependencies
+									} = /** @type {ResolveBuildDependenciesResult} */ (result);
+									if (this.resolveResults) {
+										for (const [key, value] of resolveResults) {
+											this.resolveResults.set(key, value);
+										}
+									} else {
+										this.resolveResults = resolveResults;
+									}
+									if (reportProgress) {
+										reportProgress(
+											0.6,
+											"snapshot build dependencies",
+											"resolving"
+										);
+									}
+									this.fileSystemInfo.createSnapshot(
+										undefined,
+										resolveDependencies.files,
+										resolveDependencies.directories,
+										resolveDependencies.missing,
+										this.snapshot.resolveBuildDependencies,
+										(err, snapshot) => {
+											if (err) {
+												this.logger.timeEnd("snapshot build dependencies");
+												return reject(err);
+											}
+											if (!snapshot) {
+												this.logger.timeEnd("snapshot build dependencies");
+												return reject(
+													new Error("Unable to snapshot resolve dependencies")
+												);
+											}
+											if (this.resolveBuildDependenciesSnapshot) {
+												this.resolveBuildDependenciesSnapshot =
+													this.fileSystemInfo.mergeSnapshots(
+														this.resolveBuildDependenciesSnapshot,
+														snapshot
+													);
+											} else {
+												this.resolveBuildDependenciesSnapshot = snapshot;
+											}
+											if (reportProgress) {
+												reportProgress(
+													0.7,
+													"snapshot build dependencies",
+													"modules"
+												);
+											}
+											this.fileSystemInfo.createSnapshot(
+												undefined,
+												files,
+												directories,
+												missing,
+												this.snapshot.buildDependencies,
+												(err, snapshot) => {
+													this.logger.timeEnd("snapshot build dependencies");
+													if (err) return reject(err);
+													if (!snapshot) {
+														return reject(
+															new Error("Unable to snapshot build dependencies")
+														);
+													}
+													this.logger.debug("Captured build dependencies");
+
+													if (this.buildSnapshot) {
+														this.buildSnapshot =
+															this.fileSystemInfo.mergeSnapshots(
+																this.buildSnapshot,
+																snapshot
+															);
+													} else {
+														this.buildSnapshot = snapshot;
+													}
+
+													resolve();
+												}
+											);
+										}
+									);
+								}
+							);
+						}
+					);
+				} else {
+					promise = Promise.resolve();
+				}
+				return promise.then(() => {
+					if (reportProgress) reportProgress(0.8, "serialize pack");
+					this.logger.time("store pack");
+					const updatedBuildDependencies = new Set(this.buildDependencies);
+					for (const dep of newBuildDependencies) {
+						updatedBuildDependencies.add(dep);
+					}
+					const content = new PackContainer(
+						pack,
+						this.version,
+						/** @type {Snapshot} */
+						(this.buildSnapshot),
+						updatedBuildDependencies,
+						/** @type {ResolveResults} */
+						(this.resolveResults),
+						/** @type {Snapshot} */
+						(this.resolveBuildDependenciesSnapshot)
+					);
+					const cleanup = this.fs.unlink !== undefined;
+					/** @type {Set | undefined} */
+					const writtenFiles = cleanup ? new Set() : undefined;
+					/** @type {Set | undefined} */
+					const retainedFiles = cleanup ? new Set() : undefined;
+					return this.fileSerializer
+						.serialize(content, {
+							filename: `${this.cacheLocation}/index${this._extension}`,
+							extension: `${this._extension}`,
+							writtenFiles,
+							retainedFiles,
+							logger: this.logger,
+							profile: this.profile
+						})
+						.then(() => {
+							for (const dep of newBuildDependencies) {
+								this.buildDependencies.add(dep);
+							}
+							this.newBuildDependencies.clear();
+							this.logger.timeEnd("store pack");
+							const stats = pack.getContentStats();
+							this.logger.log(
+								"Stored pack (%d items, %d files, %d MiB)",
+								pack.itemInfo.size,
+								stats.count,
+								Math.round(stats.size / 1024 / 1024)
+							);
+							if (writtenFiles !== undefined) {
+								return this._cleanupUnusedFiles(
+									writtenFiles,
+									/** @type {Set} */ (retainedFiles)
+								);
+							}
+						})
+						.catch((err) => {
+							this.logger.timeEnd("store pack");
+							// files may be in an unknown state after a failed store
+							this._referencedFilesCache.clear();
+							this.logger.warn(`Caching failed for pack: ${err}`);
+							this.logger.debug(err.stack);
+						});
+				});
+			})
+			.catch((err) => {
+				this.logger.warn(`Caching failed for pack: ${err}`);
+				this.logger.debug(err.stack);
+			}));
+	}
+
+	/**
+	 * Reads when the currently unreferenced files were first seen. A missing or
+	 * unreadable file just restarts the grace period for every orphan.
+	 * @returns {Promise} first seen time and size per file name
+	 */
+	_readUnreferencedFiles() {
+		return new Promise((resolve) => {
+			this.fs.readFile(
+				`${this.cacheLocation}/${UNREFERENCED_FILE}`,
+				(err, content) => {
+					/** @type {UnreferencedFiles} */
+					const result = new Map();
+					if (err) return resolve(result);
+					try {
+						const data = JSON.parse(
+							/** @type {Buffer} */ (content).toString("utf8")
+						);
+						for (const [file, entry] of Object.entries(data)) {
+							const { firstSeen, size } = /** @type {UnreferencedFile} */ (
+								entry
+							);
+							if (typeof firstSeen === "number" && typeof size === "number") {
+								result.set(file, { firstSeen, size });
+							}
+						}
+					} catch (_err) {
+						result.clear();
+					}
+					resolve(result);
+				}
+			);
+		});
+	}
+
+	/**
+	 * Persists when the still unreferenced files were first seen. Failing to write
+	 * only costs the orphans another grace period, so errors are ignored.
+	 * @param {UnreferencedFiles} unreferenced first seen time and size per file name
+	 * @param {boolean} hadEntries whether a previous state exists that must be replaced
+	 * @returns {Promise} promise
+	 */
+	_writeUnreferencedFiles(unreferenced, hadEntries) {
+		if (unreferenced.size === 0 && !hadEntries) return Promise.resolve();
+		/** @type {Record} */
+		const data = {};
+		for (const [file, entry] of unreferenced) data[file] = entry;
+		return new Promise((resolve) => {
+			this.fs.writeFile(
+				`${this.cacheLocation}/${UNREFERENCED_FILE}`,
+				JSON.stringify(data),
+				() => resolve()
+			);
+		});
+	}
+
+	/**
+	 * Deletes files from the cache directory that are no longer referenced by the
+	 * stored pack. Retained files are walked on disk since nested lazy segments
+	 * reference files not visible during serialization. Errors only log a warning.
+	 * @param {Set} writtenNames names (without extension) written by this store
+	 * @param {Set} retainedNames names (without extension) referenced but not rewritten
+	 * @returns {Promise} promise
+	 */
+	async _cleanupUnusedFiles(writtenNames, retainedNames) {
+		this.logger.time("cleanup unused cache files");
+		const fs = this.fs;
+		const extension = this._extension;
+		const cacheLocation = this.cacheLocation;
+		const referencedFilesCache = this._referencedFilesCache;
+		try {
+			// rewritten files may reference different names now
+			for (const name of writtenNames) referencedFilesCache.delete(name);
+			/** @type {Set} */
+			const liveFiles = new Set([`index${extension}`, UNREFERENCED_FILE]);
+			for (const name of writtenNames) liveFiles.add(`${name}${extension}`);
+			/** @type {string[]} */
+			const queue = [];
+			/**
+			 * Marks a file live and queues it for walking its references.
+			 * @param {string} name file name without extension
+			 */
+			const enqueue = (name) => {
+				const file = `${name}${extension}`;
+				if (liveFiles.has(file)) return;
+				liveFiles.add(file);
+				queue.push(name);
+			};
+			for (const name of retainedNames) enqueue(name);
+			while (queue.length > 0) {
+				const name = /** @type {string} */ (queue.pop());
+				const file = `${cacheLocation}/${name}${extension}`;
+				// an unchanged mtime proves the memo entry still matches the disk
+				const mtimeMs = await new Promise((resolve, reject) => {
+					fs.stat(file, (err, stats) => {
+						if (err) return reject(err);
+						resolve(
+							/** @type {number} */ (
+								/** @type {import("../util/fs").IStats} */ (stats).mtimeMs
+							)
+						);
+					});
+				});
+				const entry = referencedFilesCache.get(name);
+				let referenced;
+				if (entry !== undefined && entry.mtimeMs === mtimeMs) {
+					referenced = entry.referenced;
+				} else {
+					referenced = await getReferencedFilenames(fs, file);
+					referencedFilesCache.set(name, { mtimeMs, referenced });
+				}
+				for (const referencedName of referenced) enqueue(referencedName);
+			}
+			const files = await new Promise((resolve, reject) => {
+				fs.readdir(cacheLocation, (err, files) => {
+					if (err) return reject(err);
+					resolve(/** @type {string[]} */ (files));
+				});
+			});
+			const seenFiles = await this._readUnreferencedFiles();
+			const now = Date.now();
+			// every store rewrites the index backup, so a recorded time would age a file
+			// that is in fact new; renaming carries the previous index mtime onto it
+			const indexBackup = `index${extension}.old`;
+			/** @type {UnreferencedFiles} */
+			const stillUnreferenced = new Map();
+			let deletedCount = 0;
+			for (const file of files) {
+				if (typeof file !== "string" || liveFiles.has(file)) continue;
+				const path = `${cacheLocation}/${file}`;
+				const stats = await new Promise((resolve) => {
+					fs.stat(path, (err, stats) => {
+						resolve(
+							err
+								? undefined
+								: /** @type {import("../util/fs").IStats} */ (stats)
+						);
+					});
+				});
+				if (stats === undefined || !stats.isFile()) continue;
+				const size = /** @type {number} */ (stats.size);
+				const seen = seenFiles.get(file);
+				// a differing size means the name was rewritten, so it is a new orphan
+				const firstSeen =
+					seen !== undefined && seen.size === size ? seen.firstSeen : now;
+				const expireTime = now - CLEANUP_GRACE_PERIOD;
+				const mtimeMs = /** @type {number} */ (stats.mtimeMs);
+				// either signal is enough: modification times are lost when a cache is
+				// restored, and recorded times are lost when the cache directory is new
+				const expired =
+					mtimeMs <= expireTime ||
+					(file !== indexBackup &&
+						firstSeen <= expireTime &&
+						mtimeMs <= now - CLEANUP_RECENT_WRITE_PERIOD);
+				if (expired) {
+					const deleted = await new Promise((resolve) => {
+						/** @type {NonNullable} */
+						(fs.unlink)(path, (err) => resolve(!err));
+					});
+					if (deleted) {
+						deletedCount++;
+						continue;
+					}
+				}
+				if (file !== indexBackup) {
+					stillUnreferenced.set(file, { firstSeen, size });
+				}
+			}
+			await this._writeUnreferencedFiles(stillUnreferenced, seenFiles.size > 0);
+			// drop entries of files that are no longer alive
+			for (const name of referencedFilesCache.keys()) {
+				if (!liveFiles.has(`${name}${extension}`)) {
+					referencedFilesCache.delete(name);
+				}
+			}
+			if (deletedCount > 0) {
+				this.logger.log("Deleted %d unused cache files", deletedCount);
+			}
+		} catch (err) {
+			this.logger.warn(
+				`Cleanup of unused cache files failed: ${/** @type {Error} */ (err)}`
+			);
+			this.logger.debug(/** @type {Error} */ (err).stack);
+		}
+		this.logger.timeEnd("cleanup unused cache files");
+	}
+
+	clear() {
+		this.fileSystemInfo.clear();
+		this.buildDependencies.clear();
+		this.newBuildDependencies.clear();
+		this.resolveBuildDependenciesSnapshot = undefined;
+		this.resolveResults = undefined;
+		this.buildSnapshot = undefined;
+		this.packPromise = undefined;
+	}
+}
+
+module.exports = PackFileCacheStrategy;
diff --git a/lib/cache/ResolverCachePlugin.js b/lib/cache/ResolverCachePlugin.js
new file mode 100644
index 00000000000..2689a9e8cc7
--- /dev/null
+++ b/lib/cache/ResolverCachePlugin.js
@@ -0,0 +1,470 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const LazySet = require("../util/LazySet");
+const makeSerializable = require("../util/makeSerializable");
+
+/** @typedef {import("enhanced-resolve").ResolveContext} ResolveContext */
+/** @typedef {import("enhanced-resolve").ResolveOptions} ResolveOptions */
+/** @typedef {import("enhanced-resolve").ResolveRequest} ResolveRequest */
+/** @typedef {import("enhanced-resolve").Resolver} Resolver */
+/** @typedef {import("../CacheFacade").ItemCacheFacade} ItemCacheFacade */
+/** @typedef {import("../Compiler")} Compiler */
+/** @typedef {import("../FileSystemInfo")} FileSystemInfo */
+/** @typedef {import("../FileSystemInfo").Snapshot} Snapshot */
+/** @typedef {import("../FileSystemInfo").SnapshotOptions} SnapshotOptions */
+/** @typedef {import("../ResolverFactory").ResolveOptionsWithDependencyType} ResolveOptionsWithDependencyType */
+/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */
+/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */
+
+/**
+ * Defines the sync hook type used by this module.
+ * @template T
+ * @typedef {import("tapable").SyncHook} SyncHook
+ */
+
+/** @typedef {Set} Dependencies  */
+
+class CacheEntry {
+	/**
+	 * Creates an instance of CacheEntry.
+	 * @param {ResolveRequest} result result
+	 * @param {Snapshot} snapshot snapshot
+	 */
+	constructor(result, snapshot) {
+		/** @type {ResolveRequest} */
+		this.result = result;
+		/** @type {Snapshot} */
+		this.snapshot = snapshot;
+	}
+
+	/**
+	 * Serializes this instance into the provided serializer context.
+	 * @param {ObjectSerializerContext} context context
+	 */
+	serialize({ write }) {
+		write(this.result);
+		write(this.snapshot);
+	}
+
+	/**
+	 * Restores this instance from the provided deserializer context.
+	 * @param {ObjectDeserializerContext} context context
+	 */
+	deserialize({ read }) {
+		this.result = read();
+		this.snapshot = read();
+	}
+}
+
+makeSerializable(CacheEntry, "webpack/lib/cache/ResolverCachePlugin");
+
+/**
+ * Adds the provided set to the cache entry.
+ * @template T
+ * @param {Set | LazySet} set set to add items to
+ * @param {Set | LazySet | Iterable} otherSet set to add items from
+ * @returns {void}
+ */
+const addAllToSet = (set, otherSet) => {
+	if (set instanceof LazySet) {
+		set.addAll(otherSet);
+	} else {
+		for (const item of otherSet) {
+			set.add(item);
+		}
+	}
+};
+
+/**
+ * Returns stringified version.
+ * @template {object} T
+ * @param {T} object an object
+ * @param {boolean} excludeContext if true, context is not included in string
+ * @returns {string} stringified version
+ */
+const objectToString = (object, excludeContext) => {
+	let str = "";
+	for (const key in object) {
+		if (excludeContext && key === "context") continue;
+		const value = object[key];
+		str +=
+			typeof value === "object" && value !== null
+				? `|${key}=[${objectToString(value, false)}|]`
+				: `|${key}=|${value}`;
+	}
+	return str;
+};
+
+/** @typedef {NonNullable} Yield */
+
+const PLUGIN_NAME = "ResolverCachePlugin";
+
+class ResolverCachePlugin {
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
+	apply(compiler) {
+		const cache = compiler.getCache(PLUGIN_NAME);
+		/** @type {FileSystemInfo | undefined} */
+		let fileSystemInfo;
+		/** @type {SnapshotOptions | undefined} */
+		let snapshotOptions;
+		let realResolves = 0;
+		let cachedResolves = 0;
+		let cacheInvalidResolves = 0;
+		let concurrentResolves = 0;
+		// FileSystemInfo reaches the whole Compilation through its logger, so this
+		// compiler-lifetime closure would outlive what `Compiler.close()` releases.
+		compiler.hooks.shutdown.tap(PLUGIN_NAME, () => {
+			fileSystemInfo = undefined;
+			snapshotOptions = undefined;
+		});
+		compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
+			snapshotOptions = compilation.options.snapshot.resolve;
+			fileSystemInfo = compilation.fileSystemInfo;
+			compilation.hooks.finishModules.tap(PLUGIN_NAME, () => {
+				if (realResolves + cachedResolves > 0) {
+					const logger = compilation.getLogger(`webpack.${PLUGIN_NAME}`);
+					logger.log(
+						`${Math.round(
+							(100 * realResolves) / (realResolves + cachedResolves)
+						)}% really resolved (${realResolves} real resolves with ${cacheInvalidResolves} cached but invalid, ${cachedResolves} cached valid, ${concurrentResolves} concurrent)`
+					);
+					realResolves = 0;
+					cachedResolves = 0;
+					cacheInvalidResolves = 0;
+					concurrentResolves = 0;
+				}
+			});
+		});
+
+		/** @typedef {(err?: Error | null, resolveRequest?: ResolveRequest | null) => void} Callback */
+		/** @typedef {ResolveRequest & { _ResolverCachePluginCacheMiss: true }} ResolveRequestWithCacheMiss */
+
+		/**
+		 * Processes the provided item cache.
+		 * @param {ItemCacheFacade} itemCache cache
+		 * @param {Resolver} resolver the resolver
+		 * @param {ResolveContext} resolveContext context for resolving meta info
+		 * @param {ResolveRequest} request the request info object
+		 * @param {Callback} callback callback function
+		 * @returns {void}
+		 */
+		const doRealResolve = (
+			itemCache,
+			resolver,
+			resolveContext,
+			request,
+			callback
+		) => {
+			realResolves++;
+			const newRequest =
+				/** @type {ResolveRequestWithCacheMiss} */
+				({
+					_ResolverCachePluginCacheMiss: true,
+					...request
+				});
+			/** @type {ResolveContext} */
+			const newResolveContext = {
+				...resolveContext,
+				stack: new Set(),
+				missingDependencies: new LazySet(),
+				fileDependencies: new LazySet(),
+				contextDependencies: new LazySet()
+			};
+			/** @type {ResolveRequest[] | undefined} */
+			let yieldResult;
+			let withYield = false;
+			if (typeof newResolveContext.yield === "function") {
+				yieldResult = [];
+				withYield = true;
+				newResolveContext.yield = (obj) =>
+					/** @type {ResolveRequest[]} */
+					(yieldResult).push(obj);
+			}
+			/**
+			 * Processes the provided key.
+			 * @param {"fileDependencies" | "contextDependencies" | "missingDependencies"} key key
+			 */
+			const propagate = (key) => {
+				if (resolveContext[key]) {
+					addAllToSet(
+						/** @type {Dependencies} */ (resolveContext[key]),
+						/** @type {Dependencies} */ (newResolveContext[key])
+					);
+				}
+			};
+			const resolveTime = Date.now();
+			resolver.doResolve(
+				resolver.hooks.resolve,
+				newRequest,
+				"Cache miss",
+				newResolveContext,
+				(err, result) => {
+					propagate("fileDependencies");
+					propagate("contextDependencies");
+					propagate("missingDependencies");
+					if (err) return callback(err);
+					const fileDependencies = newResolveContext.fileDependencies;
+					const contextDependencies = newResolveContext.contextDependencies;
+					const missingDependencies = newResolveContext.missingDependencies;
+					// Only reachable past the `!fileSystemInfo` guard on the resolve tap.
+					const fsInfo = /** @type {FileSystemInfo} */ (fileSystemInfo);
+					fsInfo.createSnapshot(
+						resolveTime,
+						/** @type {Dependencies} */
+						(fileDependencies),
+						/** @type {Dependencies} */
+						(contextDependencies),
+						/** @type {Dependencies} */
+						(missingDependencies),
+						snapshotOptions,
+						(err, snapshot) => {
+							if (err) return callback(err);
+							const resolveResult = withYield ? yieldResult : result;
+							// since we intercept resolve hook
+							// we still can get result in callback
+							if (withYield && result) {
+								/** @type {ResolveRequest[]} */
+								(yieldResult).push(result);
+							}
+							if (!snapshot) {
+								if (resolveResult) {
+									return callback(
+										null,
+										/** @type {ResolveRequest} */
+										(resolveResult)
+									);
+								}
+								return callback();
+							}
+							itemCache.store(
+								new CacheEntry(
+									/** @type {ResolveRequest} */
+									(resolveResult),
+									snapshot
+								),
+								(storeErr) => {
+									if (storeErr) return callback(storeErr);
+									if (resolveResult) {
+										return callback(
+											null,
+											/** @type {ResolveRequest} */
+											(resolveResult)
+										);
+									}
+									callback();
+								}
+							);
+						}
+					);
+				}
+			);
+		};
+		compiler.resolverFactory.hooks.resolver.intercept({
+			factory(type, _hook) {
+				/** @typedef {(err?: Error, resolveRequest?: ResolveRequest) => void} ActiveRequest */
+				/** @type {Map} */
+				const activeRequests = new Map();
+				/** @type {Map} */
+				const activeRequestsWithYield = new Map();
+				const hook =
+					/** @type {SyncHook<[Resolver, ResolveOptions, ResolveOptionsWithDependencyType]>} */
+					(_hook);
+				hook.tap(PLUGIN_NAME, (resolver, options, userOptions) => {
+					if (
+						/** @type {ResolveOptions & { cache: boolean }} */
+						(options).cache !== true
+					) {
+						return;
+					}
+					const optionsIdent = objectToString(userOptions, false);
+					const cacheWithContext =
+						options.cacheWithContext !== undefined
+							? options.cacheWithContext
+							: false;
+					resolver.hooks.resolve.tapAsync(
+						{
+							name: PLUGIN_NAME,
+							stage: -100
+						},
+						(request, resolveContext, callback) => {
+							if (
+								/** @type {ResolveRequestWithCacheMiss} */
+								(request)._ResolverCachePluginCacheMiss ||
+								!fileSystemInfo
+							) {
+								return callback();
+							}
+							const withYield = typeof resolveContext.yield === "function";
+							const identifier = `${type}${
+								withYield ? "|yield" : "|default"
+							}${optionsIdent}${objectToString(request, !cacheWithContext)}`;
+
+							if (withYield) {
+								const activeRequest = activeRequestsWithYield.get(identifier);
+								if (activeRequest) {
+									activeRequest[0].push(callback);
+									activeRequest[1].push(
+										/** @type {Yield} */
+										(resolveContext.yield)
+									);
+									return;
+								}
+							} else {
+								const activeRequest = activeRequests.get(identifier);
+								if (activeRequest) {
+									activeRequest.push(callback);
+									return;
+								}
+							}
+							const itemCache = cache.getItemCache(identifier, null);
+							/** @type {Callback[] | false | undefined} */
+							let callbacks;
+							/** @type {Yield[] | undefined} */
+							let yields;
+
+							/**
+							 * @type {(err?: Error | null, result?: ResolveRequest | ResolveRequest[] | null) => void}
+							 */
+							const done = withYield
+								? (err, result) => {
+										if (callbacks === undefined) {
+											if (err) {
+												callback(err);
+											} else {
+												if (result) {
+													for (const r of /** @type {ResolveRequest[]} */ (
+														result
+													)) {
+														/** @type {Yield} */
+														(resolveContext.yield)(r);
+													}
+												}
+												callback(null, null);
+											}
+											yields = undefined;
+											callbacks = false;
+										} else {
+											const definedCallbacks =
+												/** @type {Callback[]} */
+												(callbacks);
+
+											if (err) {
+												for (const cb of definedCallbacks) cb(err);
+											} else {
+												for (let i = 0; i < definedCallbacks.length; i++) {
+													const cb = definedCallbacks[i];
+													const yield_ = /** @type {Yield[]} */ (yields)[i];
+													if (result) {
+														for (const r of /** @type {ResolveRequest[]} */ (
+															result
+														)) {
+															yield_(r);
+														}
+													}
+													cb(null, null);
+												}
+											}
+											activeRequestsWithYield.delete(identifier);
+											yields = undefined;
+											callbacks = false;
+										}
+									}
+								: (err, result) => {
+										if (callbacks === undefined) {
+											callback(err, /** @type {ResolveRequest} */ (result));
+											callbacks = false;
+										} else {
+											for (const callback of /** @type {Callback[]} */ (
+												callbacks
+											)) {
+												callback(err, /** @type {ResolveRequest} */ (result));
+											}
+											activeRequests.delete(identifier);
+											callbacks = false;
+										}
+									};
+							/**
+							 * Process cache result.
+							 * @param {(Error | null)=} err error if any
+							 * @param {(CacheEntry | null)=} cacheEntry cache entry
+							 * @returns {void}
+							 */
+							const processCacheResult = (err, cacheEntry) => {
+								if (err) return done(err);
+
+								if (cacheEntry) {
+									const { snapshot, result } = cacheEntry;
+									const fsInfo = /** @type {FileSystemInfo} */ (fileSystemInfo);
+									fsInfo.checkSnapshotValid(snapshot, (err, valid) => {
+										if (err || !valid) {
+											cacheInvalidResolves++;
+											return doRealResolve(
+												itemCache,
+												resolver,
+												resolveContext,
+												request,
+												done
+											);
+										}
+										cachedResolves++;
+										if (resolveContext.missingDependencies) {
+											addAllToSet(
+												/** @type {Dependencies} */
+												(resolveContext.missingDependencies),
+												snapshot.getMissingIterable()
+											);
+										}
+										if (resolveContext.fileDependencies) {
+											addAllToSet(
+												/** @type {Dependencies} */
+												(resolveContext.fileDependencies),
+												snapshot.getFileIterable()
+											);
+										}
+										if (resolveContext.contextDependencies) {
+											addAllToSet(
+												/** @type {Dependencies} */
+												(resolveContext.contextDependencies),
+												snapshot.getContextIterable()
+											);
+										}
+										done(null, result);
+									});
+								} else {
+									doRealResolve(
+										itemCache,
+										resolver,
+										resolveContext,
+										request,
+										done
+									);
+								}
+							};
+							itemCache.get(processCacheResult);
+							if (withYield && callbacks === undefined) {
+								callbacks = [callback];
+								yields = [/** @type {Yield} */ (resolveContext.yield)];
+								activeRequestsWithYield.set(identifier, [callbacks, yields]);
+							} else if (callbacks === undefined) {
+								callbacks = [callback];
+								activeRequests.set(identifier, callbacks);
+							}
+						}
+					);
+				});
+				return hook;
+			}
+		});
+	}
+}
+
+module.exports = ResolverCachePlugin;
diff --git a/lib/cache/getLazyHashedEtag.js b/lib/cache/getLazyHashedEtag.js
new file mode 100644
index 00000000000..a72fa67b790
--- /dev/null
+++ b/lib/cache/getLazyHashedEtag.js
@@ -0,0 +1,102 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const { DEFAULTS } = require("../config/defaults");
+const createHash = require("../util/createHash");
+
+/** @typedef {import("../util/Hash")} Hash */
+/** @typedef {typeof import("../util/Hash")} HashConstructor */
+/** @typedef {import("../util/Hash").HashFunction} HashFunction */
+
+/**
+ * Represents the lazy hashed etag runtime component.
+ * @typedef {object} HashableObject
+ * @property {(hash: Hash) => void} updateHash
+ */
+
+class LazyHashedEtag {
+	/**
+	 * Creates an instance of LazyHashedEtag.
+	 * @param {HashableObject} obj object with updateHash method
+	 * @param {HashFunction} hashFunction the hash function to use
+	 */
+	constructor(obj, hashFunction = DEFAULTS.HASH_FUNCTION) {
+		/** @type {HashableObject | undefined} */
+		this._obj = obj;
+		/** @type {undefined | string} */
+		this._hash = undefined;
+		/** @type {HashFunction} */
+		this._hashFunction = hashFunction;
+	}
+
+	/**
+	 * Returns a string representation.
+	 * @returns {string} hash of object
+	 */
+	toString() {
+		if (this._hash === undefined) {
+			const hash = createHash(this._hashFunction);
+			/** @type {HashableObject} */
+			(this._obj).updateHash(hash);
+			this._hash = hash.digest("base64");
+			// Drop the captured object once the hash is memoized. The hash is
+			// never reset, so we never need `_obj` again — and many callers
+			// (e.g. `SourceMapDevToolPlugin`, `RealContentHashPlugin`) capture
+			// a heavy `CachedSource` here that would otherwise stay reachable
+			// through this etag for the lifetime of the compilation cache.
+			this._obj = undefined;
+		}
+		return this._hash;
+	}
+}
+
+/** @typedef {WeakMap} InnerCache */
+
+/** @type {Map} */
+const mapStrings = new Map();
+
+/** @type {WeakMap} */
+const mapObjects = new WeakMap();
+
+/**
+ * Returns etag.
+ * @param {HashableObject} obj object with updateHash method
+ * @param {HashFunction=} hashFunction the hash function to use
+ * @returns {LazyHashedEtag} etag
+ */
+const getter = (obj, hashFunction = DEFAULTS.HASH_FUNCTION) => {
+	/** @type {undefined | InnerCache} */
+	let innerMap;
+	if (typeof hashFunction === "string") {
+		innerMap = mapStrings.get(hashFunction);
+		if (innerMap === undefined) {
+			const newHash = new LazyHashedEtag(obj, hashFunction);
+			/** @type {InnerCache} */
+			innerMap = new WeakMap();
+			innerMap.set(obj, newHash);
+			mapStrings.set(hashFunction, innerMap);
+			return newHash;
+		}
+	} else {
+		innerMap = mapObjects.get(hashFunction);
+		if (innerMap === undefined) {
+			const newHash = new LazyHashedEtag(obj, hashFunction);
+			/** @type {InnerCache} */
+			innerMap = new WeakMap();
+			innerMap.set(obj, newHash);
+			mapObjects.set(hashFunction, innerMap);
+			return newHash;
+		}
+	}
+	const hash = innerMap.get(obj);
+	if (hash !== undefined) return hash;
+	const newHash = new LazyHashedEtag(obj, hashFunction);
+	innerMap.set(obj, newHash);
+	return newHash;
+};
+
+module.exports = getter;
diff --git a/lib/cache/mergeEtags.js b/lib/cache/mergeEtags.js
new file mode 100644
index 00000000000..94257fb7f3a
--- /dev/null
+++ b/lib/cache/mergeEtags.js
@@ -0,0 +1,75 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+/** @typedef {import("../Cache").Etag} Etag */
+
+class MergedEtag {
+	/**
+	 * Creates an instance of MergedEtag.
+	 * @param {Etag} a first
+	 * @param {Etag} b second
+	 */
+	constructor(a, b) {
+		/** @type {Etag} */
+		this.a = a;
+		/** @type {Etag} */
+		this.b = b;
+	}
+
+	toString() {
+		return `${this.a.toString()}|${this.b.toString()}`;
+	}
+}
+
+/** @type {WeakMap>} */
+const dualObjectMap = new WeakMap();
+/** @type {WeakMap>} */
+const objectStringMap = new WeakMap();
+
+/**
+ * Merges the provided values into a single result.
+ * @param {Etag} a first
+ * @param {Etag} b second
+ * @returns {string | MergedEtag} result
+ */
+const mergeEtags = (a, b) => {
+	if (typeof a === "string") {
+		if (typeof b === "string") {
+			return `${a}|${b}`;
+		}
+		const temp = b;
+		b = a;
+		a = temp;
+	} else if (typeof b !== "string") {
+		// both a and b are objects
+		let map = dualObjectMap.get(a);
+		if (map === undefined) {
+			dualObjectMap.set(a, (map = new WeakMap()));
+		}
+		const mergedEtag = map.get(b);
+		if (mergedEtag === undefined) {
+			const newMergedEtag = new MergedEtag(a, b);
+			map.set(b, newMergedEtag);
+			return newMergedEtag;
+		}
+		return mergedEtag;
+	}
+	// a is object, b is string
+	let map = objectStringMap.get(a);
+	if (map === undefined) {
+		objectStringMap.set(a, (map = new Map()));
+	}
+	const mergedEtag = map.get(b);
+	if (mergedEtag === undefined) {
+		const newMergedEtag = new MergedEtag(a, b);
+		map.set(b, newMergedEtag);
+		return newMergedEtag;
+	}
+	return mergedEtag;
+};
+
+module.exports = mergeEtags;
diff --git a/lib/cli.js b/lib/cli.js
new file mode 100644
index 00000000000..ff82b287e96
--- /dev/null
+++ b/lib/cli.js
@@ -0,0 +1,983 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const path = require("path");
+const tty = require("tty");
+const webpackSchema =
+	/** @type {EXPECTED_ANY} */
+	(require("../schemas/WebpackOptions.json"));
+
+/** @typedef {import("json-schema").JSONSchema4} JSONSchema4 */
+/** @typedef {import("json-schema").JSONSchema6} JSONSchema6 */
+/** @typedef {import("json-schema").JSONSchema7} JSONSchema7 */
+/** @typedef {JSONSchema4 | JSONSchema6 | JSONSchema7} JSONSchema */
+/** @typedef {JSONSchema & { absolutePath: boolean, instanceof: string, cli: { helper?: boolean, exclude?: boolean, description?: string, negatedDescription?: string, resetDescription?: string } }} Schema */
+
+/**
+ * Defines the path item type used by this module.
+ * @typedef {object} PathItem
+ * @property {Schema} schema the part of the schema
+ * @property {string} path the path in the config
+ * @property {string} segment the JSON pointer segment of this schema part (absolute when starting with "#")
+ */
+
+/** @typedef {"unknown-argument" | "unexpected-non-array-in-path" | "unexpected-non-object-in-path" | "prototype-pollution-in-path" | "multiple-values-unexpected" | "invalid-value"} ProblemType */
+
+/** @typedef {string | number | boolean | RegExp} Value */
+
+/**
+ * Defines the problem type used by this module.
+ * @typedef {object} Problem
+ * @property {ProblemType} type
+ * @property {string} path
+ * @property {string} argument
+ * @property {Value=} value
+ * @property {number=} index
+ * @property {string=} expected
+ */
+
+/**
+ * Defines the local problem type used by this module.
+ * @typedef {object} LocalProblem
+ * @property {ProblemType} type
+ * @property {string} path
+ * @property {string=} expected
+ */
+
+/** @typedef {{ [key: string]: EnumValue }} EnumValueObject */
+/** @typedef {EnumValue[]} EnumValueArray */
+/** @typedef {string | number | boolean | EnumValueObject | EnumValueArray | null} EnumValue */
+
+/**
+ * Defines the argument config type used by this module.
+ * @typedef {object} ArgumentConfig
+ * @property {string=} description
+ * @property {string=} negatedDescription
+ * @property {string} path
+ * @property {boolean} multiple
+ * @property {"enum" | "string" | "path" | "number" | "boolean" | "RegExp" | "reset"} type
+ * @property {EnumValue[]=} values
+ */
+
+/** @typedef {"string" | "number" | "boolean"} SimpleType */
+
+/**
+ * Defines the argument type used by this module.
+ * @typedef {object} Argument
+ * @property {string | undefined} description
+ * @property {SimpleType} simpleType
+ * @property {boolean} multiple
+ * @property {ArgumentConfig[]} configs
+ */
+
+/** @typedef {Record} Flags */
+
+/** @typedef {Record} ObjectConfiguration */
+
+/**
+ * Returns object of arguments.
+ * @param {Schema=} schema a json schema to create arguments for (by default webpack schema is used)
+ * @returns {Flags} object of arguments
+ */
+const getArguments = (schema = webpackSchema) => {
+	/** @type {Flags} */
+	const flags = {};
+
+	/**
+	 * Path to argument name.
+	 * @param {string} input input
+	 * @returns {string} result
+	 */
+	const pathToArgumentName = (input) =>
+		input
+			.replace(/\./g, "-")
+			.replace(/\[\]/g, "")
+			.replace(
+				/(\p{Uppercase_Letter}+|\p{Lowercase_Letter}|\d)(\p{Uppercase_Letter}+)/gu,
+				"$1-$2"
+			)
+			.replace(/-?[^\p{Uppercase_Letter}\p{Lowercase_Letter}\d]+/gu, "-")
+			.toLowerCase();
+
+	/**
+	 * Returns schema part.
+	 * @param {string} path path
+	 * @returns {Schema} schema part
+	 */
+	const getSchemaPart = (path) => {
+		const newPath = path.split("/");
+
+		let schemaPart = schema;
+
+		for (let i = 1; i < newPath.length; i++) {
+			const inner = schemaPart[/** @type {keyof Schema} */ (newPath[i])];
+
+			if (!inner) {
+				break;
+			}
+
+			schemaPart = inner;
+		}
+
+		return schemaPart;
+	};
+
+	/**
+	 * Returns description.
+	 * @param {PathItem[]} path path in the schema
+	 * @returns {string | undefined} description
+	 */
+	const getDescription = (path) => {
+		for (const { schema } of path) {
+			if (schema.cli) {
+				if (schema.cli.helper) continue;
+				if (schema.cli.description) return schema.cli.description;
+			}
+			if (schema.description) return schema.description;
+		}
+	};
+
+	/**
+	 * Gets negated description.
+	 * @param {PathItem[]} path path in the schema
+	 * @returns {string | undefined} negative description
+	 */
+	const getNegatedDescription = (path) => {
+		for (const { schema } of path) {
+			if (schema.cli) {
+				if (schema.cli.helper) continue;
+				if (schema.cli.negatedDescription) return schema.cli.negatedDescription;
+			}
+		}
+	};
+
+	/**
+	 * Gets reset description.
+	 * @param {PathItem[]} path path in the schema
+	 * @returns {string | undefined} reset description
+	 */
+	const getResetDescription = (path) => {
+		for (const { schema } of path) {
+			if (schema.cli) {
+				if (schema.cli.helper) continue;
+				if (schema.cli.resetDescription) return schema.cli.resetDescription;
+			}
+		}
+	};
+
+	/**
+	 * Schema to argument config.
+	 * @param {Schema} schemaPart schema
+	 * @returns {Pick | undefined} partial argument config
+	 */
+	const schemaToArgumentConfig = (schemaPart) => {
+		if (schemaPart.enum) {
+			return {
+				type: "enum",
+				values: schemaPart.enum
+			};
+		}
+		if (schemaPart.const !== undefined) {
+			return {
+				type: "enum",
+				values: [schemaPart.const]
+			};
+		}
+		switch (schemaPart.type) {
+			case "number":
+				return {
+					type: "number"
+				};
+			case "string":
+				return {
+					type: schemaPart.absolutePath ? "path" : "string"
+				};
+			case "boolean":
+				return {
+					type: "boolean"
+				};
+		}
+		if (schemaPart.instanceof === "RegExp") {
+			return {
+				type: "RegExp"
+			};
+		}
+		return undefined;
+	};
+
+	/**
+	 * Adds the provided path to this object.
+	 * @param {PathItem[]} path path in the schema
+	 * @returns {void}
+	 */
+	const addResetFlag = (path) => {
+		const schemaPath = path[0].path;
+		const name = pathToArgumentName(`${schemaPath}.reset`);
+		const description =
+			getResetDescription(path) ||
+			`Clear all items provided in '${schemaPath}' configuration. ${getDescription(
+				path
+			)}`;
+		flags[name] = {
+			configs: [
+				{
+					type: "reset",
+					multiple: false,
+					description,
+					path: schemaPath
+				}
+			],
+			description: undefined,
+			simpleType:
+				/** @type {SimpleType} */
+				(/** @type {unknown} */ (undefined)),
+			multiple: /** @type {boolean} */ (/** @type {unknown} */ (undefined))
+		};
+	};
+
+	/**
+	 * Joins the per-item segments into a JSON pointer (built only when needed).
+	 * @param {PathItem[]} path full path in schema (deepest item first)
+	 * @returns {string} JSON pointer to the deepest schema part
+	 */
+	const getOriginPath = (path) => {
+		let origin = "";
+		for (let i = path.length - 1; i >= 0; i--) {
+			const { segment } = path[i];
+			origin = segment[0] === "#" ? segment : origin + segment;
+		}
+		return origin;
+	};
+
+	/**
+	 * Adds the provided path to this object.
+	 * @param {PathItem[]} path full path in schema
+	 * @param {boolean} multiple inside of an array
+	 * @returns {number} number of arguments added
+	 */
+	const addFlag = (path, multiple) => {
+		const argConfigBase = schemaToArgumentConfig(path[0].schema);
+		if (!argConfigBase) return 0;
+
+		const negatedDescription = getNegatedDescription(path);
+		const name = pathToArgumentName(path[0].path);
+		/** @type {ArgumentConfig} */
+		const argConfig = {
+			...argConfigBase,
+			multiple,
+			description: getDescription(path),
+			path: path[0].path
+		};
+
+		if (negatedDescription) {
+			argConfig.negatedDescription = negatedDescription;
+		}
+
+		if (!flags[name]) {
+			flags[name] = {
+				configs: [],
+				description: undefined,
+				simpleType:
+					/** @type {SimpleType} */
+					(/** @type {unknown} */ (undefined)),
+				multiple: /** @type {boolean} */ (/** @type {unknown} */ (undefined))
+			};
+		}
+
+		if (
+			flags[name].configs.some(
+				(item) => JSON.stringify(item) === JSON.stringify(argConfig)
+			)
+		) {
+			return 0;
+		}
+
+		if (
+			flags[name].configs.some(
+				(item) => item.type === argConfig.type && item.multiple !== multiple
+			)
+		) {
+			if (multiple) {
+				throw new Error(
+					`Conflicting schema for ${path[0].path} (${getOriginPath(
+						path
+					)}) with ${
+						argConfig.type
+					} type (array type must be before single item type)`
+				);
+			}
+			return 0;
+		}
+
+		flags[name].configs.push(argConfig);
+
+		return 1;
+	};
+
+	// TODO support `not`
+	/**
+	 * Returns added arguments.
+	 * @param {Schema} schemaPart the current schema
+	 * @param {string} schemaPath the current path in the config
+	 * @param {PathItem[]} path all previous visited schemaParts
+	 * @param {string | null} inArray if inside of an array, the path to the array
+	 * @param {string} segment the JSON pointer segment of the current schema
+	 * @returns {number} added arguments
+	 */
+	const traverse = (
+		schemaPart,
+		schemaPath = "",
+		path = [],
+		inArray = null,
+		segment = "#"
+	) => {
+		while (schemaPart.$ref) {
+			segment = schemaPart.$ref;
+			schemaPart = getSchemaPart(schemaPart.$ref);
+		}
+
+		const repetitions = path.filter(({ schema }) => schema === schemaPart);
+		if (
+			repetitions.length >= 2 ||
+			repetitions.some(({ path }) => path === schemaPath)
+		) {
+			return 0;
+		}
+
+		if (schemaPart.cli && schemaPart.cli.exclude) return 0;
+
+		/** @type {PathItem[]} */
+		const fullPath = [
+			{ schema: schemaPart, path: schemaPath, segment },
+			...path
+		];
+
+		let addedArguments = 0;
+
+		addedArguments += addFlag(fullPath, Boolean(inArray));
+
+		// Collect flags from both branches of a conditional schema.
+		const conditional = /** @type {JSONSchema7} */ (schemaPart);
+		if (conditional.if) {
+			for (const key of /** @type {const} */ (["if", "then", "else"])) {
+				const subSchema = conditional[key];
+				if (subSchema && typeof subSchema === "object") {
+					addedArguments += traverse(
+						/** @type {Schema} */
+						(subSchema),
+						schemaPath,
+						fullPath,
+						inArray,
+						`/${key}`
+					);
+				}
+			}
+		}
+
+		if (schemaPart.type === "object") {
+			if (schemaPart.properties) {
+				for (const property of Object.keys(schemaPart.properties)) {
+					addedArguments += traverse(
+						/** @type {Schema} */
+						(schemaPart.properties[property]),
+						schemaPath ? `${schemaPath}.${property}` : property,
+						fullPath,
+						inArray,
+						`/properties/${property}`
+					);
+				}
+			}
+
+			return addedArguments;
+		}
+
+		if (schemaPart.type === "array") {
+			if (inArray) {
+				return 0;
+			}
+			if (Array.isArray(schemaPart.items)) {
+				const i = 0;
+				for (const item of schemaPart.items) {
+					addedArguments += traverse(
+						/** @type {Schema} */
+						(item),
+						`${schemaPath}.${i}`,
+						fullPath,
+						schemaPath,
+						`/items/${i}`
+					);
+				}
+
+				return addedArguments;
+			}
+
+			addedArguments += traverse(
+				/** @type {Schema} */
+				(schemaPart.items),
+				`${schemaPath}[]`,
+				fullPath,
+				schemaPath,
+				"/items"
+			);
+
+			if (addedArguments > 0) {
+				addResetFlag(fullPath);
+				addedArguments++;
+			}
+
+			return addedArguments;
+		}
+
+		const ofKey = schemaPart.oneOf
+			? "oneOf"
+			: schemaPart.anyOf
+				? "anyOf"
+				: schemaPart.allOf
+					? "allOf"
+					: undefined;
+
+		if (ofKey) {
+			const items = /** @type {Schema[]} */ (schemaPart[ofKey]);
+
+			for (let i = 0; i < items.length; i++) {
+				addedArguments += traverse(
+					/** @type {Schema} */
+					(items[i]),
+					schemaPath,
+					fullPath,
+					inArray,
+					`/${ofKey}/${i}`
+				);
+			}
+
+			return addedArguments;
+		}
+
+		return addedArguments;
+	};
+
+	traverse(schema);
+
+	// Summarize flags
+	for (const name of Object.keys(flags)) {
+		/** @type {Argument} */
+		const argument = flags[name];
+		argument.description = argument.configs.reduce((desc, { description }) => {
+			if (!desc) return description;
+			if (!description) return desc;
+			if (desc.includes(description)) return desc;
+			return `${desc} ${description}`;
+		}, /** @type {string | undefined} */ (undefined));
+		argument.simpleType =
+			/** @type {SimpleType} */
+			(
+				argument.configs.reduce((t, argConfig) => {
+					/** @type {SimpleType} */
+					let type = "string";
+					switch (argConfig.type) {
+						case "number":
+							type = "number";
+							break;
+						case "reset":
+						case "boolean":
+							type = "boolean";
+							break;
+						case "enum": {
+							const values =
+								/** @type {NonNullable} */
+								(argConfig.values);
+
+							if (values.every((v) => typeof v === "boolean")) type = "boolean";
+							if (values.every((v) => typeof v === "number")) type = "number";
+							break;
+						}
+					}
+					if (t === undefined) return type;
+					return t === type ? t : "string";
+				}, /** @type {SimpleType | undefined} */ (undefined))
+			);
+		argument.multiple = argument.configs.some((c) => c.multiple);
+	}
+
+	return flags;
+};
+
+/** @type {WeakMap} */
+const cliAddedItems = new WeakMap();
+
+/** @typedef {string | number} Property */
+
+/**
+ * Whether a path segment would walk into the prototype chain.
+ * @param {string} name path segment
+ * @returns {boolean} true when the segment is unsafe to write through
+ */
+const isUnsafeKey = (name) =>
+	name === "__proto__" || name === "constructor" || name === "prototype";
+
+/**
+ * Gets object and property.
+ * @param {ObjectConfiguration} config configuration
+ * @param {string} schemaPath path in the config
+ * @param {number | undefined} index index of value when multiple values are provided, otherwise undefined
+ * @returns {{ problem?: LocalProblem, object?: ObjectConfiguration, property?: Property, value?: EXPECTED_OBJECT | EXPECTED_ANY[] }} problem or object with property and value
+ */
+const getObjectAndProperty = (config, schemaPath, index = 0) => {
+	if (!schemaPath) return { value: config };
+	const parts = schemaPath.split(".");
+	const property = /** @type {string} */ (parts.pop());
+	let current = config;
+	let i = 0;
+	for (const part of parts) {
+		const isArray = part.endsWith("[]");
+		const name = isArray ? part.slice(0, -2) : part;
+		if (isUnsafeKey(name)) {
+			return {
+				problem: {
+					type: "prototype-pollution-in-path",
+					path: parts.slice(0, i).join(".")
+				}
+			};
+		}
+		let value = current[name];
+		if (isArray) {
+			if (value === undefined) {
+				value = {};
+				current[name] = [...Array.from({ length: index }), value];
+				cliAddedItems.set(current[name], index + 1);
+			} else if (!Array.isArray(value)) {
+				return {
+					problem: {
+						type: "unexpected-non-array-in-path",
+						path: parts.slice(0, i).join(".")
+					}
+				};
+			} else {
+				let addedItems = cliAddedItems.get(value) || 0;
+				while (addedItems <= index) {
+					value.push(undefined);
+					addedItems++;
+				}
+				cliAddedItems.set(value, addedItems);
+				const x = value.length - addedItems + index;
+				if (value[x] === undefined) {
+					value[x] = {};
+				} else if (value[x] === null || typeof value[x] !== "object") {
+					return {
+						problem: {
+							type: "unexpected-non-object-in-path",
+							path: parts.slice(0, i).join(".")
+						}
+					};
+				}
+				value = value[x];
+			}
+		} else if (value === undefined) {
+			value = current[name] = {};
+		} else if (value === null || typeof value !== "object") {
+			return {
+				problem: {
+					type: "unexpected-non-object-in-path",
+					path: parts.slice(0, i).join(".")
+				}
+			};
+		}
+		current = value;
+		i++;
+	}
+	if (isUnsafeKey(property.endsWith("[]") ? property.slice(0, -2) : property)) {
+		return {
+			problem: {
+				type: "prototype-pollution-in-path",
+				path: parts.join(".")
+			}
+		};
+	}
+	const value = current[property];
+	if (property.endsWith("[]")) {
+		const name = property.slice(0, -2);
+		const value = current[name];
+		if (value === undefined) {
+			current[name] = [...Array.from({ length: index }), undefined];
+			cliAddedItems.set(current[name], index + 1);
+			return { object: current[name], property: index, value: undefined };
+		} else if (!Array.isArray(value)) {
+			current[name] = [value, ...Array.from({ length: index }), undefined];
+			cliAddedItems.set(current[name], index + 1);
+			return { object: current[name], property: index + 1, value: undefined };
+		}
+		let addedItems = cliAddedItems.get(value) || 0;
+		while (addedItems <= index) {
+			value.push(undefined);
+			addedItems++;
+		}
+		cliAddedItems.set(value, addedItems);
+		const x = value.length - addedItems + index;
+		if (value[x] === undefined) {
+			value[x] = {};
+		} else if (value[x] === null || typeof value[x] !== "object") {
+			return {
+				problem: {
+					type: "unexpected-non-object-in-path",
+					path: schemaPath
+				}
+			};
+		}
+		return {
+			object: value,
+			property: x,
+			value: value[x]
+		};
+	}
+	return { object: current, property, value };
+};
+
+/**
+ * Updates value using the provided config.
+ * @param {ObjectConfiguration} config configuration
+ * @param {string} schemaPath path in the config
+ * @param {ParsedValue} value parsed value
+ * @param {number | undefined} index index of value when multiple values are provided, otherwise undefined
+ * @returns {LocalProblem | null} problem or null for success
+ */
+const setValue = (config, schemaPath, value, index) => {
+	const { problem, object, property } = getObjectAndProperty(
+		config,
+		schemaPath,
+		index
+	);
+	if (problem) return problem;
+	/** @type {ObjectConfiguration} */
+	(object)[/** @type {Property} */ (property)] = value;
+	return null;
+};
+
+/**
+ * Process argument config.
+ * @param {ArgumentConfig} argConfig processing instructions
+ * @param {ObjectConfiguration} config configuration
+ * @param {Value} value the value
+ * @param {number | undefined} index the index if multiple values provided
+ * @returns {LocalProblem | null} a problem if any
+ */
+const processArgumentConfig = (argConfig, config, value, index) => {
+	if (index !== undefined && !argConfig.multiple) {
+		return {
+			type: "multiple-values-unexpected",
+			path: argConfig.path
+		};
+	}
+	const parsed = parseValueForArgumentConfig(argConfig, value);
+	if (parsed === undefined) {
+		return {
+			type: "invalid-value",
+			path: argConfig.path,
+			expected: getExpectedValue(argConfig)
+		};
+	}
+	const problem = setValue(config, argConfig.path, parsed, index);
+	if (problem) return problem;
+	return null;
+};
+
+/**
+ * Gets expected value.
+ * @param {ArgumentConfig} argConfig processing instructions
+ * @returns {string | undefined} expected message
+ */
+const getExpectedValue = (argConfig) => {
+	switch (argConfig.type) {
+		case "boolean":
+			return "true | false";
+		case "RegExp":
+			return "regular expression (example: /ab?c*/)";
+		case "enum":
+			return /** @type {NonNullable} */ (
+				argConfig.values
+			)
+				.map((v) => `${v}`)
+				.join(" | ");
+		case "reset":
+			return "true (will reset the previous value to an empty array)";
+		default:
+			return argConfig.type;
+	}
+};
+
+/** @typedef {null | string | number | boolean | RegExp | EnumValue | []} ParsedValue */
+
+const DECIMAL_NUMBER_REGEXP = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i;
+
+/**
+ * Parses value for argument config.
+ * @param {ArgumentConfig} argConfig processing instructions
+ * @param {Value} value the value
+ * @returns {ParsedValue | undefined} parsed value
+ */
+const parseValueForArgumentConfig = (argConfig, value) => {
+	switch (argConfig.type) {
+		case "string":
+			if (typeof value === "string") {
+				return value;
+			}
+			break;
+		case "path":
+			if (typeof value === "string") {
+				return path.resolve(value);
+			}
+			break;
+		case "number":
+			if (typeof value === "number") return value;
+			if (typeof value === "string" && DECIMAL_NUMBER_REGEXP.test(value)) {
+				const n = Number(value);
+				if (!Number.isNaN(n)) return n;
+			}
+			break;
+		case "boolean":
+			if (typeof value === "boolean") return value;
+			if (value === "true") return true;
+			if (value === "false") return false;
+			break;
+		case "RegExp":
+			if (value instanceof RegExp) return value;
+			if (typeof value === "string") {
+				// cspell:word yugi
+				const match = /^\/(.*)\/([yugi]*)$/.exec(value);
+				if (match && !/[^\\]\//.test(match[1])) {
+					return new RegExp(match[1], match[2]);
+				}
+			}
+			break;
+		case "enum": {
+			const values =
+				/** @type {EnumValue[]} */
+				(argConfig.values);
+			if (values.includes(/** @type {Exclude} */ (value))) {
+				return value;
+			}
+			for (const item of values) {
+				if (`${item}` === value) return item;
+			}
+			break;
+		}
+		case "reset":
+			if (value === true) return [];
+			break;
+	}
+};
+
+/** @typedef {Record} Values */
+
+/**
+ * Processes the provided arg.
+ * @param {Flags} args object of arguments
+ * @param {ObjectConfiguration} config configuration
+ * @param {Values} values object with values
+ * @returns {Problem[] | null} problems or null for success
+ */
+const processArguments = (args, config, values) => {
+	/** @type {Problem[]} */
+	const problems = [];
+	for (const key of Object.keys(values)) {
+		const arg = args[key];
+		if (!arg) {
+			problems.push({
+				type: "unknown-argument",
+				path: "",
+				argument: key
+			});
+			continue;
+		}
+		/**
+		 * Processes the provided value.
+		 * @param {Value} value value
+		 * @param {number | undefined} i index
+		 */
+		const processValue = (value, i) => {
+			/** @type {Problem[]} */
+			const currentProblems = [];
+			for (const argConfig of arg.configs) {
+				const problem = processArgumentConfig(argConfig, config, value, i);
+				if (!problem) {
+					return;
+				}
+				currentProblems.push({
+					...problem,
+					argument: key,
+					value,
+					index: i
+				});
+			}
+			problems.push(...currentProblems);
+		};
+		const value = values[key];
+		if (Array.isArray(value)) {
+			for (let i = 0; i < value.length; i++) {
+				processValue(value[i], i);
+			}
+		} else {
+			processValue(value, undefined);
+		}
+	}
+	if (problems.length === 0) return null;
+	return problems;
+};
+
+/**
+ * Checks whether this object is color supported.
+ * @returns {boolean} true when colors supported, otherwise false
+ */
+const isColorSupported = () => {
+	const { env = {}, argv = [], platform = "" } = process;
+
+	// Read values instead of using `in`: Deno's and Bun's `process.env` honor
+	// property access but not the `in`/`has` trap, so `"X" in env` is unreliable.
+	const isDisabled = env.NO_COLOR !== undefined || argv.includes("--no-color");
+	const isForced = env.FORCE_COLOR !== undefined || argv.includes("--color");
+	const isWindows = platform === "win32";
+	const isDumbTerminal = env.TERM === "dumb";
+
+	const isCompatibleTerminal = tty.isatty(1) && env.TERM && !isDumbTerminal;
+
+	const isCI =
+		env.CI !== undefined &&
+		(env.GITHUB_ACTIONS !== undefined ||
+			env.GITLAB_CI !== undefined ||
+			env.CIRCLECI !== undefined);
+
+	return (
+		!isDisabled &&
+		(isForced || (isWindows && !isDumbTerminal) || isCompatibleTerminal || isCI)
+	);
+};
+
+/**
+ * Returns result.
+ * @param {number} index index
+ * @param {string} string string
+ * @param {string} close close
+ * @param {string=} replace replace
+ * @param {string=} head head
+ * @param {string=} tail tail
+ * @param {number=} next next
+ * @returns {string} result
+ */
+const replaceClose = (
+	index,
+	string,
+	close,
+	replace,
+	head = string.slice(0, Math.max(0, index)) + replace,
+	tail = string.slice(Math.max(0, index + close.length)),
+	next = tail.indexOf(close)
+) => head + (next < 0 ? tail : replaceClose(next, tail, close, replace));
+
+/**
+ * Returns result.
+ * @param {number} index index to replace
+ * @param {string} string string
+ * @param {string} open open string
+ * @param {string} close close string
+ * @param {string=} replace extra replace
+ * @returns {string} result
+ */
+const clearBleed = (index, string, open, close, replace) =>
+	index < 0
+		? open + string + close
+		: open + replaceClose(index, string, close, replace) + close;
+
+/** @typedef {(value: EXPECTED_ANY) => string} PrintFunction */
+
+/**
+ * Returns function to create color.
+ * @param {string} open open string
+ * @param {string} close close string
+ * @param {string=} replace extra replace
+ * @param {number=} at at
+ * @returns {PrintFunction} function to create color
+ */
+const filterEmpty =
+	(open, close, replace = open, at = open.length + 1) =>
+	(string) =>
+		string || !(string === "" || string === undefined)
+			? clearBleed(`${string}`.indexOf(close, at), string, open, close, replace)
+			: "";
+
+/**
+ * Returns result.
+ * @param {number} open open code
+ * @param {number} close close code
+ * @param {string=} replace extra replace
+ * @returns {PrintFunction} result
+ */
+const init = (open, close, replace) =>
+	filterEmpty(`\u001B[${open}m`, `\u001B[${close}m`, replace);
+
+/**
+ * Defines the colors type used by this module.
+ * @typedef {{ reset: PrintFunction, bold: PrintFunction, dim: PrintFunction, italic: PrintFunction, underline: PrintFunction, inverse: PrintFunction, hidden: PrintFunction, strikethrough: PrintFunction, black: PrintFunction, red: PrintFunction, green: PrintFunction, yellow: PrintFunction, blue: PrintFunction, magenta: PrintFunction, cyan: PrintFunction, white: PrintFunction, gray: PrintFunction, bgBlack: PrintFunction, bgRed: PrintFunction, bgGreen: PrintFunction, bgYellow: PrintFunction, bgBlue: PrintFunction, bgMagenta: PrintFunction, bgCyan: PrintFunction, bgWhite: PrintFunction, blackBright: PrintFunction, redBright: PrintFunction, greenBright: PrintFunction, yellowBright: PrintFunction, blueBright: PrintFunction, magentaBright: PrintFunction, cyanBright: PrintFunction, whiteBright: PrintFunction, bgBlackBright: PrintFunction, bgRedBright: PrintFunction, bgGreenBright: PrintFunction, bgYellowBright: PrintFunction, bgBlueBright: PrintFunction, bgMagentaBright: PrintFunction, bgCyanBright: PrintFunction, bgWhiteBright: PrintFunction }} Colors
+ */
+
+/**
+ * Defines the colors options type used by this module.
+ * @typedef {object} ColorsOptions
+ * @property {boolean=} useColor force use colors
+ */
+
+/**
+ * Creates a colors from the provided colors option.
+ * @param {ColorsOptions=} options options
+ * @returns {Colors} colors
+ */
+const createColors = ({ useColor = isColorSupported() } = {}) => ({
+	reset: useColor ? init(0, 0) : String,
+	bold: useColor ? init(1, 22, "\u001B[22m\u001B[1m") : String,
+	dim: useColor ? init(2, 22, "\u001B[22m\u001B[2m") : String,
+	italic: useColor ? init(3, 23) : String,
+	underline: useColor ? init(4, 24) : String,
+	inverse: useColor ? init(7, 27) : String,
+	hidden: useColor ? init(8, 28) : String,
+	strikethrough: useColor ? init(9, 29) : String,
+	black: useColor ? init(30, 39) : String,
+	red: useColor ? init(31, 39) : String,
+	green: useColor ? init(32, 39) : String,
+	yellow: useColor ? init(33, 39) : String,
+	blue: useColor ? init(34, 39) : String,
+	magenta: useColor ? init(35, 39) : String,
+	cyan: useColor ? init(36, 39) : String,
+	white: useColor ? init(37, 39) : String,
+	gray: useColor ? init(90, 39) : String,
+	bgBlack: useColor ? init(40, 49) : String,
+	bgRed: useColor ? init(41, 49) : String,
+	bgGreen: useColor ? init(42, 49) : String,
+	bgYellow: useColor ? init(43, 49) : String,
+	bgBlue: useColor ? init(44, 49) : String,
+	bgMagenta: useColor ? init(45, 49) : String,
+	bgCyan: useColor ? init(46, 49) : String,
+	bgWhite: useColor ? init(47, 49) : String,
+	blackBright: useColor ? init(90, 39) : String,
+	redBright: useColor ? init(91, 39) : String,
+	greenBright: useColor ? init(92, 39) : String,
+	yellowBright: useColor ? init(93, 39) : String,
+	blueBright: useColor ? init(94, 39) : String,
+	magentaBright: useColor ? init(95, 39) : String,
+	cyanBright: useColor ? init(96, 39) : String,
+	whiteBright: useColor ? init(97, 39) : String,
+	bgBlackBright: useColor ? init(100, 49) : String,
+	bgRedBright: useColor ? init(101, 49) : String,
+	bgGreenBright: useColor ? init(102, 49) : String,
+	bgYellowBright: useColor ? init(103, 49) : String,
+	bgBlueBright: useColor ? init(104, 49) : String,
+	bgMagentaBright: useColor ? init(105, 49) : String,
+	bgCyanBright: useColor ? init(106, 49) : String,
+	bgWhiteBright: useColor ? init(107, 49) : String
+});
+
+module.exports.createColors = createColors;
+module.exports.getArguments = getArguments;
+module.exports.isColorSupported = isColorSupported;
+module.exports.processArguments = processArguments;
diff --git a/lib/compareLocations.js b/lib/compareLocations.js
deleted file mode 100644
index 7dce42bdb12..00000000000
--- a/lib/compareLocations.js
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
-	MIT License http://www.opensource.org/licenses/mit-license.php
-	Author Tobias Koppers @sokra
-*/
-"use strict";
-
-/**
- * @typedef {Object} LineAndColumn
- * @property {number=} line
- * @property {number=} column
- */
-
-/**
- * @typedef {Object} NodeLocation
- * @property {LineAndColumn=} start
- * @property {LineAndColumn=} end
- * @property {number=} index
- */
-
-/**
- * Compare two locations
- * @param {string|NodeLocation} a A location node
- * @param {string|NodeLocation} b A location node
- * @returns {-1|0|1} sorting comparator value
- */
-module.exports = (a, b) => {
-	if (typeof a === "string") {
-		if (typeof b === "string") {
-			if (a < b) return -1;
-			if (a > b) return 1;
-			return 0;
-		} else if (typeof b === "object") {
-			return 1;
-		} else {
-			return 0;
-		}
-	} else if (typeof a === "object") {
-		if (typeof b === "string") {
-			return -1;
-		} else if (typeof b === "object") {
-			if (a.start && b.start) {
-				const ap = a.start;
-				const bp = b.start;
-				if (ap.line < bp.line) return -1;
-				if (ap.line > bp.line) return 1;
-				if (ap.column < bp.column) return -1;
-				if (ap.column > bp.column) return 1;
-			}
-			if (a.index < b.index) return -1;
-			if (a.index > b.index) return 1;
-			return 0;
-		} else {
-			return 0;
-		}
-	}
-};
diff --git a/lib/config/browserslistTargetHandler.js b/lib/config/browserslistTargetHandler.js
new file mode 100644
index 00000000000..e0111af58d2
--- /dev/null
+++ b/lib/config/browserslistTargetHandler.js
@@ -0,0 +1,662 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Sergey Melyukov @smelukov
+*/
+
+"use strict";
+
+const path = require("path");
+const browserslist = require("browserslist");
+
+/** @typedef {import("./target").ApiTargetProperties} ApiTargetProperties */
+/** @typedef {import("./target").CssTargetProperties} CssTargetProperties */
+/** @typedef {import("./target").EcmaTargetProperties} EcmaTargetProperties */
+/** @typedef {import("./target").PlatformTargetProperties} PlatformTargetProperties */
+
+// [[C:]/path/to/config][:env]
+const inputRx = /^(?:((?:[A-Z]:)?[/\\].*?))?(?::(.+?))?$/i;
+
+// One resolution per `[input, context]` — several callers ask for the same
+// target, and each miss re-reads the config and re-runs the queries. The list is
+// handed out as it stands, so no caller may write to it.
+/** @type {Map | null} */
+const cache = process.env.BROWSERSLIST_DISABLE_CACHE ? null : new Map();
+
+/**
+ * Returns selected browsers.
+ * @param {string | null | undefined} input input string
+ * @param {string} context the context directory
+ * @returns {string[] | undefined} selected browsers
+ */
+const load = (input, context) => {
+	if (cache === null) return loadBrowsers(input, context);
+	// A newline separates the two: an input carries a config path, an env or a
+	// query, and a directory carries none.
+	const key = `${input || ""}\n${context}`;
+	let browsers = cache.get(key);
+	if (browsers === undefined && !cache.has(key)) {
+		browsers = loadBrowsers(input, context);
+		cache.set(key, browsers);
+	}
+	return browsers;
+};
+
+/**
+ * Read the selection out of the browserslist config or query.
+ * @param {string | null | undefined} input input string
+ * @param {string} context the context directory
+ * @returns {string[] | undefined} selected browsers
+ */
+const loadBrowsers = (input, context) => {
+	// browserslist:path-to-config
+	// browserslist:path-to-config:env
+	if (input && path.isAbsolute(input)) {
+		const [, configPath, env] = inputRx.exec(input) || [];
+
+		const config = browserslist.loadConfig({
+			config: configPath,
+			env
+		});
+
+		return browserslist(config, { env });
+	}
+
+	const env = input || undefined;
+
+	const config = browserslist.loadConfig({
+		path: context,
+		env
+	});
+
+	// browserslist
+	// browserslist:env
+	if (config) {
+		try {
+			return browserslist(config, { env, throwOnMissing: true });
+		} catch (_err) {
+			// Nothing, no `env` was found in browserslist, maybe input is `queries`
+		}
+	}
+
+	// browserslist:query
+	if (env) {
+		return browserslist(env);
+	}
+};
+
+/**
+ * Returns target properties.
+ * @param {string[]} browsers supported browsers list
+ * @returns {EcmaTargetProperties & PlatformTargetProperties & ApiTargetProperties & CssTargetProperties} target properties
+ */
+const resolve = (browsers) => {
+	/**
+	 * Checks all against a version number
+	 * @param {Record} versions first supported version
+	 * @returns {boolean} true if supports
+	 */
+	const rawChecker = (versions) => checkAll(browsers, versions);
+	/**
+	 * Checks a subset of the list against a version number
+	 * @param {string[]} list the browsers to check
+	 * @param {Record} versions first supported version
+	 * @returns {boolean} true if all support
+	 */
+	const checkAll = (list, versions) =>
+		list.every((v) => {
+			const [name, parsedVersion] = v.split(" ");
+			if (!name) return false;
+			const requiredVersion = versions[name];
+			if (!requiredVersion) return false;
+			const [parsedMajor, parserMinor] =
+				// safari TP supports all features for normal safari
+				parsedVersion === "TP"
+					? [Infinity, Infinity]
+					: parsedVersion.includes("-")
+						? parsedVersion.split("-")[0].split(".")
+						: parsedVersion.split(".");
+			if (typeof requiredVersion === "number") {
+				return Number(parsedMajor) >= requiredVersion;
+			}
+			return requiredVersion[0] === Number(parsedMajor)
+				? Number(parserMinor) >= requiredVersion[1]
+				: Number(parsedMajor) > requiredVersion[0];
+		});
+	/**
+	 * Checks the rendering engines against a version number. A CSS feature is a
+	 * property of those alone, so node entries say nothing about it — and a list
+	 * with no engine at all renders no stylesheet, which leaves the modern
+	 * spelling available.
+	 * @param {Record} versions first supported version
+	 * @returns {boolean} true if all rendering engines support
+	 */
+	const cssChecker = (versions) => {
+		const engines = browsers.filter((b) => !b.startsWith("node "));
+		return engines.length === 0 || checkAll(engines, versions);
+	};
+	const anyNode = browsers.some((b) => b.startsWith("node "));
+	const anyBrowser = browsers.some((b) => /^(?!node)/.test(b));
+	const browserProperty = !anyBrowser ? false : anyNode ? null : true;
+	const nodeProperty = !anyNode ? false : anyBrowser ? null : true;
+
+	return {
+		/* eslint-disable camelcase */
+		const: rawChecker({
+			chrome: 49,
+			and_chr: 49,
+			edge: 12,
+			// Prior to Firefox 13, const is implemented, but re-assignment is not failing.
+			// Prior to Firefox 46, a TypeError was thrown on redeclaration instead of a SyntaxError.
+			firefox: 36,
+			and_ff: 36,
+			// Not supported in for-in and for-of loops
+			// ie: Not supported
+			opera: 36,
+			op_mob: 36,
+			safari: [10, 0],
+			ios_saf: [10, 0],
+			// Before 5.0 supported correctly in strict mode, otherwise supported without block scope
+			samsung: [5, 0],
+			android: 37,
+			and_qq: [10, 4],
+			// Supported correctly in strict mode, otherwise supported without block scope
+			baidu: [13, 18],
+			and_uc: [12, 12],
+			kaios: [2, 5],
+			node: [6, 0]
+		}),
+		let: rawChecker({
+			chrome: 49,
+			and_chr: 49,
+			edge: 12,
+			firefox: 44,
+			and_ff: 44,
+			// ie: Not supported
+			opera: 36,
+			op_mob: 36,
+			safari: [10, 0],
+			ios_saf: [10, 0],
+			samsung: [5, 0],
+			android: 49,
+			and_qq: [10, 4],
+			baidu: [13, 18],
+			and_uc: [12, 12],
+			kaios: [2, 5],
+			node: [6, 0]
+		}),
+		logicalAssignment: rawChecker({
+			chrome: 85,
+			and_chr: 85,
+			edge: 85,
+			firefox: 79,
+			and_ff: 79,
+			// ie: Not supported,
+			opera: 71,
+			op_mob: 60,
+			safari: 14,
+			ios_saf: 14,
+			samsung: [14, 0],
+			android: 85,
+			and_qq: [14, 9],
+			baidu: [13, 52],
+			and_uc: [15, 5],
+			kaios: [3, 0],
+			node: [15, 0]
+		}),
+		methodShorthand: rawChecker({
+			chrome: 47,
+			and_chr: 47,
+			edge: 12,
+			firefox: 34,
+			and_ff: 34,
+			// ie: Not supported,
+			opera: 34,
+			op_mob: 34,
+			safari: 9,
+			ios_saf: 9,
+			samsung: 5,
+			android: 47,
+			// baidu: Not tracked,
+			and_qq: [14, 9],
+			and_uc: [15, 5],
+			kaios: [2, 5],
+			node: [4, 9]
+		}),
+		arrowFunction: rawChecker({
+			chrome: 45,
+			and_chr: 45,
+			edge: 12,
+			// The initial implementation of arrow functions in Firefox made them automatically strict. This has been changed as of Firefox 24. The use of 'use strict'; is now required.
+			// Prior to Firefox 39, a line terminator (\\n) was incorrectly allowed after arrow function arguments. This has been fixed to conform to the ES2015 specification and code like () \\n => {} will now throw a SyntaxError in this and later versions.
+			firefox: 39,
+			and_ff: 39,
+			// ie: Not supported,
+			opera: 32,
+			op_mob: 32,
+			safari: 10,
+			ios_saf: 10,
+			samsung: [5, 0],
+			android: 45,
+			and_qq: [10, 4],
+			baidu: [7, 12],
+			and_uc: [12, 12],
+			kaios: [2, 5],
+			node: [6, 0]
+		}),
+		forOf: rawChecker({
+			chrome: 38,
+			and_chr: 38,
+			edge: 12,
+			// Prior to Firefox 51, using the for...of loop construct with the const keyword threw a SyntaxError ("missing = in const declaration").
+			firefox: 51,
+			and_ff: 51,
+			// ie: Not supported,
+			opera: 25,
+			op_mob: 25,
+			safari: 7,
+			ios_saf: 7,
+			samsung: [3, 0],
+			android: 38,
+			and_qq: [10, 4],
+			// baidu: Unknown support
+			and_uc: [12, 12],
+			kaios: [3, 0],
+			node: [0, 12]
+		}),
+		destructuring: rawChecker({
+			chrome: 49,
+			and_chr: 49,
+			edge: 14,
+			firefox: 41,
+			and_ff: 41,
+			// ie: Not supported,
+			opera: 36,
+			op_mob: 36,
+			safari: 8,
+			ios_saf: 8,
+			samsung: [5, 0],
+			android: 49,
+			and_qq: [10, 4],
+			// baidu: Unknown support
+			and_uc: [12, 12],
+			kaios: [2, 5],
+			node: [6, 0]
+		}),
+		bigIntLiteral: rawChecker({
+			chrome: 67,
+			and_chr: 67,
+			edge: 79,
+			firefox: 68,
+			and_ff: 68,
+			// ie: Not supported,
+			opera: 54,
+			op_mob: 48,
+			safari: 14,
+			ios_saf: 14,
+			samsung: [9, 2],
+			android: 67,
+			and_qq: [13, 1],
+			baidu: [13, 18],
+			and_uc: [15, 5],
+			kaios: [3, 0],
+			node: [10, 4]
+		}),
+		// Support syntax `import` and `export` and no limitations and bugs on Node.js
+		// Not include `export * as namespace`
+		module: rawChecker({
+			chrome: 61,
+			and_chr: 61,
+			edge: 16,
+			firefox: 60,
+			and_ff: 60,
+			// ie: Not supported,
+			opera: 48,
+			op_mob: 45,
+			safari: [10, 1],
+			ios_saf: [10, 3],
+			samsung: [8, 0],
+			android: 61,
+			and_qq: [10, 4],
+			baidu: [13, 18],
+			and_uc: [15, 5],
+			kaios: [3, 0],
+			node: [12, 17]
+		}),
+		dynamicImport: rawChecker({
+			chrome: 63,
+			and_chr: 63,
+			edge: 79,
+			firefox: 67,
+			and_ff: 67,
+			// ie: Not supported
+			opera: 50,
+			op_mob: 46,
+			safari: [11, 1],
+			ios_saf: [11, 3],
+			samsung: [8, 2],
+			android: 63,
+			and_qq: [10, 4],
+			baidu: [13, 18],
+			and_uc: [15, 5],
+			kaios: [3, 0],
+			node: [12, 17]
+		}),
+		dynamicImportInWorker: rawChecker({
+			chrome: 80,
+			and_chr: 80,
+			edge: 80,
+			firefox: 114,
+			and_ff: 114,
+			// ie: Not supported
+			opera: 67,
+			op_mob: 57,
+			safari: [15, 0],
+			ios_saf: [15, 0],
+			samsung: [13, 0],
+			android: 80,
+			and_qq: [10, 4],
+			baidu: [13, 18],
+			and_uc: [15, 5],
+			kaios: [3, 0],
+			node: [12, 17]
+		}),
+		// browserslist does not have info about globalThis
+		// so this is based on mdn-browser-compat-data
+		globalThis: rawChecker({
+			chrome: 71,
+			and_chr: 71,
+			edge: 79,
+			firefox: 65,
+			and_ff: 65,
+			// ie: Not supported,
+			opera: 58,
+			op_mob: 50,
+			safari: [12, 1],
+			ios_saf: [12, 2],
+			samsung: [10, 1],
+			android: 71,
+			and_qq: [13, 1],
+			// baidu: Unknown support
+			and_uc: [15, 5],
+			kaios: [3, 0],
+			node: 12
+		}),
+		optionalChaining: rawChecker({
+			chrome: 80,
+			and_chr: 80,
+			edge: 80,
+			firefox: 74,
+			and_ff: 79,
+			// ie: Not supported,
+			opera: 67,
+			op_mob: 64,
+			safari: [13, 1],
+			ios_saf: [13, 4],
+			samsung: 13,
+			android: 80,
+			and_qq: [13, 1],
+			// baidu: Not supported
+			and_uc: [15, 5],
+			kaios: [3, 0],
+			node: 14
+		}),
+		symbol: rawChecker({
+			chrome: 38,
+			and_chr: 38,
+			edge: 12,
+			firefox: 36,
+			and_ff: 36,
+			// ie: Not supported,
+			opera: 25,
+			op_mob: 25,
+			safari: 9,
+			ios_saf: 9,
+			samsung: [3, 0],
+			android: 38,
+			and_qq: [10, 4],
+			and_uc: [12, 12],
+			kaios: [2, 5],
+			node: [0, 12]
+		}),
+		hasOwn: rawChecker({
+			chrome: 93,
+			and_chr: 93,
+			edge: 93,
+			firefox: 92,
+			and_ff: 92,
+			// ie: Not supported,
+			opera: 79,
+			op_mob: 66,
+			safari: [15, 4],
+			ios_saf: [15, 4],
+			samsung: 17,
+			android: 93,
+			// and_qq: Not supported
+			// baidu: Not supported
+			// and_uc: Not supported
+			// kaios: Not supported
+			node: [16, 9]
+		}),
+		spread: rawChecker({
+			chrome: 60,
+			and_chr: 60,
+			edge: 79,
+			firefox: 55,
+			and_ff: 55,
+			// ie: Not supported,
+			opera: 47,
+			op_mob: 44,
+			safari: [11, 1],
+			ios_saf: [11, 3],
+			samsung: [8, 2],
+			android: 60,
+			and_qq: [13, 1],
+			// baidu: Not supported
+			and_uc: [15, 5],
+			kaios: [3, 0],
+			node: [8, 3]
+		}),
+		templateLiteral: rawChecker({
+			chrome: 41,
+			and_chr: 41,
+			edge: 13,
+			firefox: 34,
+			and_ff: 34,
+			// ie: Not supported,
+			opera: 29,
+			op_mob: 64,
+			safari: [9, 1],
+			ios_saf: 9,
+			samsung: 4,
+			android: 41,
+			and_qq: [10, 4],
+			baidu: [7, 12],
+			and_uc: [12, 12],
+			kaios: [2, 5],
+			node: 4
+		}),
+		asyncFunction: rawChecker({
+			chrome: 55,
+			and_chr: 55,
+			edge: 15,
+			firefox: 52,
+			and_ff: 52,
+			// ie: Not supported,
+			opera: 42,
+			op_mob: 42,
+			safari: 11,
+			ios_saf: 11,
+			samsung: [6, 2],
+			android: 55,
+			and_qq: [10, 4],
+			baidu: [13, 18],
+			and_uc: [12, 12],
+			kaios: 3,
+			node: [7, 6]
+		}),
+		generator: rawChecker({
+			chrome: 39,
+			and_chr: 39,
+			edge: 13,
+			firefox: 26,
+			and_ff: 26,
+			// ie: Not supported,
+			opera: 26,
+			op_mob: 26,
+			safari: 10,
+			ios_saf: 10,
+			samsung: [3, 4],
+			android: 39,
+			and_qq: [10, 4],
+			baidu: [7, 12],
+			and_uc: [12, 12],
+			kaios: [2, 5],
+			node: 4
+		}),
+		// CSS Color 4's `#rgba` / `#rrggbbaa`.
+		cssColorHexAlpha: cssChecker({
+			chrome: 62,
+			and_chr: 62,
+			edge: 79,
+			firefox: 49,
+			and_ff: 49,
+			// ie: Not supported,
+			opera: 49,
+			op_mob: 46,
+			safari: 10,
+			ios_saf: 10,
+			samsung: [8, 2],
+			android: 62,
+			baidu: [13, 18],
+			and_qq: [13, 1],
+			and_uc: [15, 5],
+			kaios: [3, 0]
+			// node: Renders no CSS
+		}),
+		// Media Queries 4's range syntax (`(width >= 600px)`).
+		cssMediaQueryRange: cssChecker({
+			chrome: 104,
+			and_chr: 104,
+			edge: 104,
+			firefox: 102,
+			and_ff: 102,
+			// ie: Not supported,
+			opera: 90,
+			op_mob: 71,
+			safari: [16, 4],
+			ios_saf: [16, 4],
+			samsung: 20,
+			android: 104
+			// baidu / and_qq / and_uc / kaios: Not tracked
+		}),
+		// CSS Images 4's two-position color stop (`red 0% 50%`), which stands for
+		// the two stops it would otherwise take.
+		cssGradientDoublePosition: cssChecker({
+			chrome: 72,
+			and_chr: 72,
+			edge: 79,
+			firefox: 83,
+			and_ff: 83,
+			// ie: Not supported,
+			opera: 60,
+			op_mob: 51,
+			safari: [12, 1],
+			ios_saf: [12, 2],
+			samsung: [11, 1],
+			android: 72
+			// baidu / and_qq / and_uc / kaios: Not tracked
+		}),
+		// CSS Position 3's `inset` shorthand. Newer than the `top` / `right` /
+		// `bottom` / `left` longhands it merges, which are CSS 1.
+		cssInsetShorthand: cssChecker({
+			chrome: 87,
+			and_chr: 87,
+			edge: 87,
+			firefox: 66,
+			and_ff: 66,
+			// ie: Not supported,
+			opera: 73,
+			op_mob: 62,
+			safari: [14, 1],
+			ios_saf: [14, 5],
+			samsung: 14,
+			android: 87
+			// baidu / and_qq / and_uc / kaios: Not tracked
+		}),
+		// CSS Box Alignment 3's `place-*` shorthands, newer than the `align-*` /
+		// `justify-*` longhands they merge. One flag covers all three, so each
+		// version is the newest of theirs — Safari shipped `place-content` in 9.
+		cssPlaceShorthand: cssChecker({
+			chrome: 59,
+			and_chr: 59,
+			edge: 79,
+			firefox: 45,
+			and_ff: 45,
+			// ie: Not supported,
+			opera: 46,
+			op_mob: 43,
+			safari: 11,
+			ios_saf: 11,
+			samsung: [7, 0],
+			android: 59
+			// baidu / and_qq / and_uc / kaios: Not tracked
+		}),
+		/* eslint-enable camelcase */
+		browser: browserProperty,
+		electron: false,
+		node: nodeProperty,
+		nwjs: false,
+		web: browserProperty,
+		webworker: false,
+
+		document: browserProperty,
+		modulePreload:
+			browserProperty &&
+			/* eslint-disable camelcase */
+			rawChecker({
+				chrome: 66,
+				and_chr: 66,
+				edge: 79,
+				firefox: 115,
+				and_ff: 115,
+				opera: 53,
+				op_mob: 47,
+				safari: 17,
+				ios_saf: 17,
+				samsung: [9, 0],
+				android: 66,
+				and_qq: [13, 1],
+				baidu: [13, 18],
+				and_uc: [15, 5],
+				kaios: [3, 1]
+			}),
+		/* eslint-enable camelcase */
+		fetchWasm: browserProperty,
+		global: nodeProperty,
+		importScripts: false,
+		importScriptsInWorker: Boolean(browserProperty),
+		nodeBuiltins: nodeProperty,
+		nodePrefixForCoreModules:
+			nodeProperty &&
+			!browsers.some((b) => b.startsWith("node 15")) &&
+			rawChecker({
+				node: [14, 18]
+			}),
+		importMetaDirnameAndFilename:
+			nodeProperty &&
+			rawChecker({
+				node: [22, 16]
+			}),
+		nodeBuiltinModuleGetter:
+			nodeProperty &&
+			rawChecker({
+				node: [22, 3]
+			}),
+		require: nodeProperty
+	};
+};
+
+module.exports = {
+	load,
+	resolve
+};
diff --git a/lib/config/defaults.js b/lib/config/defaults.js
new file mode 100644
index 00000000000..5a326b03097
--- /dev/null
+++ b/lib/config/defaults.js
@@ -0,0 +1,2839 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const fs = require("fs");
+const path = require("path");
+const {
+	CSS_TYPE,
+	JAVASCRIPT_TYPE,
+	UNKNOWN_TYPE
+} = require("../ModuleSourceTypeConstants");
+const {
+	ASSET_MODULE_TYPE,
+	ASSET_MODULE_TYPE_BYTES,
+	ASSET_MODULE_TYPE_INLINE,
+	ASSET_MODULE_TYPE_RESOURCE,
+	ASSET_MODULE_TYPE_SOURCE,
+	CSS_MODULE_TYPE,
+	CSS_MODULE_TYPE_AUTO,
+	CSS_MODULE_TYPE_GLOBAL,
+	CSS_MODULE_TYPE_MODULE,
+	HTML_MODULE_TYPE,
+	JAVASCRIPT_MODULE_TYPE_AUTO,
+	JAVASCRIPT_MODULE_TYPE_DYNAMIC,
+	JAVASCRIPT_MODULE_TYPE_ESM,
+	JSON_MODULE_TYPE,
+	WEBASSEMBLY_MODULE_TYPE_ASYNC,
+	WEBASSEMBLY_MODULE_TYPE_SYNC
+} = require("../ModuleTypeConstants");
+const Template = require("../Template");
+const RuleSetCompiler = require("../rules/RuleSetCompiler");
+const { cleverMerge } = require("../util/cleverMerge");
+const {
+	getDefaultTarget,
+	getTargetProperties,
+	getTargetsBrowsers,
+	getTargetsProperties
+} = require("./target");
+
+/** @typedef {import("../../declarations/WebpackOptions").CacheOptionsNormalized} CacheOptionsNormalized */
+/** @typedef {import("../../declarations/WebpackOptions").Context} Context */
+/** @typedef {import("../../declarations/WebpackOptions").DevTool} Devtool */
+/** @typedef {import("../../declarations/WebpackOptions").CssGeneratorOptions} CssGeneratorOptions */
+/** @typedef {import("../../declarations/WebpackOptions").EntryDescription} EntryDescription */
+/** @typedef {import("../../declarations/WebpackOptions").EntryNormalized} Entry */
+/** @typedef {import("../../declarations/WebpackOptions").Environment} Environment */
+/** @typedef {import("../../declarations/WebpackOptions").Experiments} Experiments */
+/** @typedef {import("../../declarations/WebpackOptions").ExperimentsNormalized} ExperimentsNormalized */
+/** @typedef {import("../../declarations/WebpackOptions").ExternalsPresets} ExternalsPresets */
+/** @typedef {import("../../declarations/WebpackOptions").ExternalsType} ExternalsType */
+/** @typedef {import("../../declarations/WebpackOptions").FileCacheOptions} FileCacheOptions */
+/** @typedef {import("../../declarations/WebpackOptions").GeneratorOptionsByModuleTypeKnown} GeneratorOptionsByModuleTypeKnown */
+/** @typedef {import("../../declarations/WebpackOptions").InfrastructureLogging} InfrastructureLogging */
+/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */
+/** @typedef {import("../../declarations/WebpackOptions").JsonGeneratorOptions} JsonGeneratorOptions */
+/** @typedef {import("../../declarations/WebpackOptions").Library} Library */
+/** @typedef {import("../../declarations/WebpackOptions").LibraryName} LibraryName */
+/** @typedef {import("../../declarations/WebpackOptions").LibraryType} LibraryType */
+/** @typedef {import("../../declarations/WebpackOptions").Loader} Loader */
+/** @typedef {import("../../declarations/WebpackOptions").Mode} Mode */
+/** @typedef {import("../../declarations/WebpackOptions").HashFunction} HashFunction */
+/** @typedef {import("../../declarations/WebpackOptions").HashSalt} HashSalt */
+/** @typedef {import("../../declarations/WebpackOptions").HashDigest} HashDigest */
+/** @typedef {import("../../declarations/WebpackOptions").HashDigestLength} HashDigestLength */
+/** @typedef {import("../../declarations/WebpackOptions").ModuleOptionsNormalized} ModuleOptions */
+/** @typedef {import("../../declarations/WebpackOptions").Node} WebpackNode */
+/** @typedef {import("../../declarations/WebpackOptions").OptimizationMinimizeOptions} OptimizationMinimizeOptions */
+/** @typedef {import("../../declarations/WebpackOptions").OptimizationNormalized} Optimization */
+/** @typedef {import("../../declarations/WebpackOptions").OptimizationSplitChunksOptions} OptimizationSplitChunksOptions */
+/** @typedef {import("../../declarations/WebpackOptions").OutputNormalized} Output */
+/** @typedef {import("../../declarations/WebpackOptions").ParserOptionsByModuleTypeKnown} ParserOptionsByModuleTypeKnown */
+/** @typedef {import("../../declarations/WebpackOptions").Performance} Performance */
+/** @typedef {import("../../declarations/WebpackOptions").ResolveOptions} ResolveOptions */
+/** @typedef {import("../../declarations/WebpackOptions").RuleSetRule} RuleSetRule */
+/** @typedef {import("../../declarations/WebpackOptions").RuleSetRules} RuleSetRules */
+/** @typedef {import("../../declarations/WebpackOptions").SnapshotOptions} SnapshotOptions */
+/** @typedef {import("../../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptionsNormalized */
+/** @typedef {import("../Module")} Module */
+/** @typedef {import("../javascript/EnableChunkLoadingPlugin").ChunkLoadingTypes} ChunkLoadingTypes */
+/** @typedef {import("../wasm/EnableWasmLoadingPlugin").WasmLoadingTypes} WasmLoadingTypes */
+/** @typedef {import("./target").PlatformTargetProperties} PlatformTargetProperties */
+/** @typedef {import("./target").TargetProperties} TargetProperties */
+
+/**
+ * Defines the recursive non nullable type used by this module.
+ * @template T
+ * @typedef {{ [P in keyof T]-?: T[P] extends object ? RecursiveNonNullable> : NonNullable }} RecursiveNonNullable
+ */
+
+/**
+ * Defines the shared type used by this module.
+ * @typedef {Output & {
+ * uniqueName: NonNullable,
+ * filename: NonNullable,
+ * cssFilename: NonNullable,
+ * chunkFilename: NonNullable,
+ * cssChunkFilename: NonNullable,
+ * hotUpdateChunkFilename: NonNullable,
+ * hotUpdateGlobal: NonNullable,
+ * assetModuleFilename: NonNullable,
+ * webassemblyModuleFilename: NonNullable,
+ * sourceMapFilename: NonNullable,
+ * hotUpdateMainFilename: NonNullable,
+ * devtoolNamespace: NonNullable,
+ * publicPath: NonNullable,
+ * workerPublicPath: NonNullable,
+ * workerChunkFilename: NonNullable,
+ * workerWasmLoading: NonNullable,
+ * workerChunkLoading: NonNullable,
+ * chunkFormat: NonNullable,
+ * module: NonNullable,
+ * asyncChunks: NonNullable,
+ * charset: NonNullable,
+ * iife: NonNullable,
+ * globalObject: NonNullable,
+ * scriptType: NonNullable,
+ * path: NonNullable,
+ * pathinfo: NonNullable,
+ * hashFunction: NonNullable,
+ * hashDigest: NonNullable,
+ * hashDigestLength: NonNullable,
+ * chunkLoadTimeout: NonNullable,
+ * chunkLoading: NonNullable,
+ * chunkLoadingGlobal: NonNullable,
+ * compareBeforeEmit: NonNullable,
+ * strictModuleErrorHandling: NonNullable,
+ * strictModuleExceptionHandling: NonNullable,
+ * strictModuleResolution: NonNullable,
+ * importFunctionName: NonNullable,
+ * importMetaName: NonNullable,
+ * environment: RecursiveNonNullable,
+ * crossOriginLoading: NonNullable,
+ * wasmLoading: NonNullable,
+ * }} OutputNormalizedWithDefaults
+ */
+
+/**
+ * Defines the shared type used by this module.
+ * @typedef {SnapshotOptions & {
+ * managedPaths: NonNullable,
+ * unmanagedPaths: NonNullable,
+ * immutablePaths: NonNullable,
+ * resolveBuildDependencies: NonNullable,
+ * buildDependencies: NonNullable,
+ * module: NonNullable,
+ * resolve: NonNullable,
+ * }} SnapshotNormalizedWithDefaults
+ */
+
+/**
+ * Defines the shared type used by this module.
+ * @typedef {Optimization & {
+ * runtimeChunk: NonNullable,
+ * splitChunks: NonNullable,
+ * mergeDuplicateChunks: NonNullable,
+ * removeAvailableModules: NonNullable,
+ * removeEmptyChunks: NonNullable,
+ * flagIncludedChunks: NonNullable,
+ * moduleIds: NonNullable,
+ * chunkIds: NonNullable,
+ * sideEffects: NonNullable,
+ * providedExports: NonNullable,
+ * usedExports: NonNullable,
+ * mangleExports: NonNullable,
+ * innerGraph: NonNullable,
+ * inlineExports: NonNullable,
+ * concatenateModules: NonNullable,
+ * avoidEntryIife: NonNullable,
+ * emitOnErrors: NonNullable,
+ * checkWasmTypes: NonNullable,
+ * mangleWasmImports: NonNullable,
+ * portableRecords: NonNullable,
+ * realContentHash: NonNullable,
+ * minimize: NonNullable,
+ * minimizer: NonNullable>,
+ * nodeEnv: NonNullable,
+ * }} OptimizationNormalizedWithDefaults
+ */
+
+/**
+ * Defines the shared type used by this module.
+ * @typedef {ExternalsPresets & {
+ * web: NonNullable,
+ * node: NonNullable,
+ * nwjs: NonNullable,
+ * electron: NonNullable,
+ * electronMain: NonNullable,
+ * electronPreload: NonNullable,
+ * electronRenderer: NonNullable,
+ * }} ExternalsPresetsNormalizedWithDefaults
+ */
+
+/**
+ * Defines the shared type used by this module.
+ * @typedef {InfrastructureLogging & {
+ * stream: NonNullable,
+ * level: NonNullable,
+ * debug: NonNullable,
+ * colors: NonNullable,
+ * appendOnly: NonNullable,
+ * }} InfrastructureLoggingNormalizedWithDefaults
+ */
+
+/**
+ * Defines the webpack options normalized with base defaults type used by this module.
+ * @typedef {WebpackOptionsNormalized & { context: NonNullable } & { infrastructureLogging: InfrastructureLoggingNormalizedWithDefaults }} WebpackOptionsNormalizedWithBaseDefaults
+ */
+
+/**
+ * Defines the webpack options normalized with defaults type used by this module.
+ * @typedef {WebpackOptionsNormalizedWithBaseDefaults & { target: NonNullable } & { output: OutputNormalizedWithDefaults } & { optimization: OptimizationNormalizedWithDefaults } & { devtool: NonNullable } & { stats: NonNullable } & { node: NonNullable } & { profile: NonNullable } & { parallelism: NonNullable } & { snapshot: SnapshotNormalizedWithDefaults } & { externalsPresets: ExternalsPresetsNormalizedWithDefaults } & { externalsType: NonNullable } & { watch: NonNullable } & { performance: NonNullable } & { recordsInputPath: NonNullable } & { recordsOutputPath: NonNullable } & { dotenv: NonNullable }} WebpackOptionsNormalizedWithDefaults
+ */
+
+/**
+ * Defines the resolved options type used by this module.
+ * @typedef {object} ResolvedOptions
+ * @property {PlatformTargetProperties | false} platform - platform target properties
+ */
+
+const NODE_MODULES_REGEXP = /[\\/]node_modules[\\/]/i;
+const DEFAULT_CACHE_NAME = "default";
+const DEFAULTS = {
+	// TODO webpack 6 - use xxhash64
+	HASH_FUNCTION: "md4"
+};
+
+/**
+ * Processes the provided obj.
+ * @template T
+ * @template {keyof T} P
+ * @param {T} obj an object
+ * @param {P} prop a property of this object
+ * @param {T[P]} value a default value of the property
+ * @returns {void}
+ */
+const D = (obj, prop, value) => {
+	if (obj[prop] === undefined) {
+		obj[prop] = value;
+	}
+};
+
+/**
+ * Processes the provided obj.
+ * @template T
+ * @template {keyof T} P
+ * @param {T} obj an object
+ * @param {P} prop a property of this object
+ * @param {() => T[P]} factory a default value factory for the property
+ * @returns {void}
+ */
+const F = (obj, prop, factory) => {
+	if (obj[prop] === undefined) {
+		obj[prop] = factory();
+	}
+};
+
+/**
+ * Sets a dynamic default value when undefined, by calling the factory function.
+ * factory must return an array or undefined
+ * When the current value is already an array an contains "..." it's replaced with
+ * the result of the factory function
+ * @template T
+ * @template {keyof T} P
+ * @param {T} obj an object
+ * @param {P} prop a property of this object
+ * @param {() => T[P]} factory a default value factory for the property
+ * @returns {void}
+ */
+const A = (obj, prop, factory) => {
+	const value = obj[prop];
+	if (value === undefined) {
+		obj[prop] = factory();
+	} else if (Array.isArray(value)) {
+		/** @type {EXPECTED_ANY[] | undefined} */
+		let newArray;
+		for (let i = 0; i < value.length; i++) {
+			const item = value[i];
+			if (item === "...") {
+				if (newArray === undefined) {
+					newArray = value.slice(0, i);
+					obj[prop] = /** @type {T[P]} */ (/** @type {unknown} */ (newArray));
+				}
+				const items =
+					/** @type {EXPECTED_ANY[]} */
+					(/** @type {unknown} */ (factory()));
+				if (items !== undefined) {
+					for (const item of items) {
+						newArray.push(item);
+					}
+				}
+			} else if (newArray !== undefined) {
+				newArray.push(item);
+			}
+		}
+	}
+};
+
+/**
+ * Apply webpack options base defaults.
+ * @param {WebpackOptionsNormalized} options options to be modified
+ * @returns {void}
+ */
+const applyWebpackOptionsBaseDefaults = (options) => {
+	F(options, "context", () => process.cwd());
+	applyInfrastructureLoggingDefaults(options.infrastructureLogging);
+};
+
+/**
+ * Apply webpack options defaults.
+ * @param {WebpackOptionsNormalized} options options to be modified
+ * @param {number=} compilerIndex index of compiler
+ * @returns {ResolvedOptions} Resolved options after apply defaults
+ */
+const applyWebpackOptionsDefaults = (options, compilerIndex) => {
+	F(options, "context", () => process.cwd());
+	F(options, "target", () =>
+		getDefaultTarget(/** @type {string} */ (options.context))
+	);
+
+	const { mode, name, target } = options;
+
+	const targetProperties =
+		target === false
+			? /** @type {false} */ (false)
+			: typeof target === "string"
+				? getTargetProperties(target, /** @type {Context} */ (options.context))
+				: getTargetsProperties(
+						/** @type {string[]} */ (target),
+						/** @type {Context} */ (options.context)
+					);
+
+	const development = mode === "development";
+	const production = mode === "production" || !mode;
+
+	if (typeof options.entry !== "function") {
+		for (const key of Object.keys(options.entry)) {
+			F(
+				options.entry[key],
+				"import",
+				() => /** @type {[string]} */ (["./src"])
+			);
+		}
+	}
+
+	D(options, "watch", false);
+	D(options, "profile", false);
+	D(options, "parallelism", 100);
+	D(options, "recordsInputPath", false);
+	D(options, "recordsOutputPath", false);
+
+	// Capture the raw html intent before "auto" is resolved: only an explicit
+	// `html: true` (or futureDefaults) keeps `.html` ahead of `.js` when resolving.
+	const htmlExplicitlyEnabled = options.experiments.html === true;
+	const htmlWasUnset = options.experiments.html === undefined;
+
+	applyExperimentsDefaults(options.experiments, {
+		production,
+		development,
+		targetProperties,
+		rules: options.module.rules
+	});
+
+	// After experiments are resolved: the default dev source map depends on the
+	// (possibly "auto"-resolved) `experiments.css` value.
+	F(
+		options,
+		"devtool",
+		() =>
+			/** @type {Devtool} */ (
+				development
+					? [
+							options.experiments.css
+								? {
+										type: "css",
+										use: "source-map"
+									}
+								: undefined,
+							{
+								type: "javascript",
+								use: "eval"
+							}
+						].filter(Boolean)
+					: false
+			)
+	);
+
+	const futureDefaults =
+		/** @type {NonNullable} */
+		(options.experiments.futureDefaults);
+
+	// `.html` outranks `.js` only for an explicit opt-in (`html: true`, or the
+	// futureDefaults-forced default), never for the auto-enabled default.
+	const htmlFirst = htmlExplicitlyEnabled || (htmlWasUnset && futureDefaults);
+
+	F(options, "validate", () => !(futureDefaults === true && production));
+
+	// Progress lives on infrastructureLogging; webpack@6 shows the interactive
+	// bar by default (`"auto"` = TTY only), webpack@5 stays opt-in.
+	D(options.infrastructureLogging, "progress", futureDefaults ? "auto" : false);
+
+	F(options, "cache", () =>
+		development ? { type: /** @type {"memory"} */ ("memory") } : false
+	);
+	applyCacheDefaults(options.cache, {
+		name: name || DEFAULT_CACHE_NAME,
+		mode: mode || "production",
+		development,
+		cacheUnaffected: options.experiments.cacheUnaffected,
+		futureDefaults,
+		compilerIndex
+	});
+	const cache = Boolean(options.cache);
+
+	applySnapshotDefaults(options.snapshot, {
+		production,
+		futureDefaults
+	});
+
+	applyOutputDefaults(options.output, {
+		context: /** @type {Context} */ (options.context),
+		targetProperties,
+		isAffectedByBrowserslist:
+			target === undefined ||
+			(typeof target === "string" && target.startsWith("browserslist")) ||
+			(Array.isArray(target) &&
+				target.some((target) => target.startsWith("browserslist"))),
+		outputModule:
+			/** @type {NonNullable} */
+			(options.experiments.outputModule),
+		development,
+		entry: options.entry,
+		futureDefaults,
+		asyncWebAssembly:
+			/** @type {boolean} */
+			(options.experiments.asyncWebAssembly)
+	});
+
+	applyModuleDefaults(options.module, {
+		cache,
+		hashSalt: /** @type {NonNullable} */ (
+			options.output.hashSalt
+		),
+		hashFunction: /** @type {NonNullable} */ (
+			options.output.hashFunction
+		),
+		syncWebAssembly:
+			/** @type {NonNullable} */
+			(options.experiments.syncWebAssembly),
+		asyncWebAssembly:
+			/** @type {boolean} */
+			(options.experiments.asyncWebAssembly),
+		css:
+			/** @type {boolean} */
+			(options.experiments.css),
+		html:
+			/** @type {boolean} */
+			(options.experiments.html),
+		typescript:
+			/** @type {boolean} */
+			(options.experiments.typescript),
+		deferImport:
+			/** @type {NonNullable} */
+			(options.experiments.deferImport),
+		sourceImport:
+			/** @type {NonNullable} */
+			(options.experiments.sourceImport),
+		futureDefaults,
+		isNode: targetProperties && targetProperties.node === true,
+		uniqueName: /** @type {string} */ (options.output.uniqueName),
+		targetProperties,
+		mode: options.mode,
+		outputModule:
+			/** @type {NonNullable} */
+			(options.output.module),
+		library: options.output.library
+	});
+
+	// `output.resourceHints.urlHints` shorthand: fan the project-wide rules out
+	// as the base `urlHints` of every URL-emitting parser (JS `new URL`, CSS
+	// `url()`, HTML ``). They go first so parser-scoped
+	// `parser..urlHints` rules — matched later, last-match-wins — override.
+	const outputUrlHints =
+		options.output.resourceHints && options.output.resourceHints.urlHints;
+	if (outputUrlHints && outputUrlHints.length > 0) {
+		for (const type of [
+			"javascript",
+			CSS_MODULE_TYPE,
+			CSS_MODULE_TYPE_AUTO,
+			CSS_MODULE_TYPE_MODULE,
+			CSS_MODULE_TYPE_GLOBAL,
+			HTML_MODULE_TYPE
+		]) {
+			const parser =
+				/** @type {{ urlHints?: import("../../declarations/WebpackOptions").UrlHintRule[] } | undefined} */
+				(options.module.parser[type]);
+			if (!parser) continue;
+			parser.urlHints = [...outputUrlHints, ...(parser.urlHints || [])];
+		}
+	}
+
+	applyExternalsPresetsDefaults(options.externalsPresets, {
+		targetProperties,
+		buildHttp: Boolean(options.experiments.buildHttp),
+		outputModule:
+			/** @type {NonNullable} */
+			(options.output.module)
+	});
+
+	applyLoaderDefaults(
+		/** @type {NonNullable} */ (
+			options.loader
+		),
+		{
+			targetProperties,
+			environment: options.output.environment,
+			outputModule:
+				/** @type {NonNullable} */
+				(options.output.module)
+		}
+	);
+
+	F(options, "externalsType", () => {
+		const validExternalTypes = require("../../schemas/WebpackOptions.json")
+			.definitions.ExternalsType.enum;
+
+		return options.output.library &&
+			validExternalTypes.includes(options.output.library.type)
+			? /** @type {ExternalsType} */ (options.output.library.type)
+			: options.output.module
+				? "module-import"
+				: "var";
+	});
+
+	applyNodeDefaults(options.node, {
+		futureDefaults:
+			/** @type {NonNullable} */
+			(options.experiments.futureDefaults),
+		outputModule:
+			/** @type {NonNullable} */
+			(options.output.module),
+		targetProperties
+	});
+
+	F(options, "performance", () =>
+		production &&
+		targetProperties &&
+		(targetProperties.browser || targetProperties.browser === null)
+			? {}
+			: false
+	);
+	applyPerformanceDefaults(
+		/** @type {NonNullable} */
+		(options.performance),
+		{
+			production
+		}
+	);
+
+	applyOptimizationDefaults(options.optimization, {
+		development,
+		production,
+		css:
+			/** @type {boolean} */
+			(options.experiments.css),
+		records: Boolean(options.recordsInputPath || options.recordsOutputPath)
+	});
+
+	options.resolve = cleverMerge(
+		getResolveDefaults({
+			cache,
+			context: /** @type {Context} */ (options.context),
+			targetProperties,
+			mode: /** @type {Mode} */ (options.mode),
+			css:
+				/** @type {boolean} */
+				(options.experiments.css),
+			html:
+				/** @type {boolean} */
+				(options.experiments.html),
+			htmlFirst,
+			typescript:
+				/** @type {boolean} */
+				(options.experiments.typescript)
+		}),
+		options.resolve
+	);
+
+	options.resolveLoader = cleverMerge(
+		getResolveLoaderDefaults({ cache }),
+		options.resolveLoader
+	);
+
+	return {
+		platform:
+			targetProperties === false
+				? targetProperties
+				: {
+						web: targetProperties.web,
+						browser: targetProperties.browser,
+						webworker: targetProperties.webworker,
+						node: targetProperties.node,
+						deno: targetProperties.deno,
+						bun: targetProperties.bun,
+						nwjs: targetProperties.nwjs,
+						electron: targetProperties.electron,
+						// spans both web and node (target "universal" or ["web", "node"])
+						universal:
+							targetProperties.node === null && targetProperties.web === null
+					}
+	};
+};
+
+/**
+ * Apply experiments defaults.
+ * @param {ExperimentsNormalized} experiments options
+ * @param {object} options options
+ * @param {boolean} options.production is production
+ * @param {boolean} options.development is development mode
+ * @param {TargetProperties | false} options.targetProperties target properties
+ * @param {RuleSetRules} options.rules user `module.rules`, used to resolve the css/html/asyncWebAssembly/typescript "auto" defaults
+ * @returns {void}
+ */
+const applyExperimentsDefaults = (
+	experiments,
+	{ production, development, targetProperties, rules }
+) => {
+	D(experiments, "futureDefaults", false);
+	D(experiments, "backCompat", !experiments.futureDefaults);
+	// TODO do we need sync web assembly in webpack@6?
+	D(experiments, "syncWebAssembly", false);
+	D(
+		experiments,
+		"asyncWebAssembly",
+		experiments.futureDefaults ? true : "auto"
+	);
+	// the universal target (web + node, neither specific) only works as ESM
+	const universal =
+		Boolean(targetProperties) &&
+		/** @type {TargetProperties} */ (targetProperties).node === null &&
+		/** @type {TargetProperties} */ (targetProperties).web === null;
+	// the deno and bun targets only emit ECMAScript modules
+	const deno =
+		Boolean(targetProperties) &&
+		/** @type {TargetProperties} */ (targetProperties).deno === true;
+	const bun =
+		Boolean(targetProperties) &&
+		/** @type {TargetProperties} */ (targetProperties).bun === true;
+	D(experiments, "outputModule", universal || deno || bun);
+	D(experiments, "lazyCompilation", undefined);
+	D(experiments, "buildHttp", undefined);
+	D(experiments, "cacheUnaffected", experiments.futureDefaults);
+	D(experiments, "deferImport", false);
+	D(experiments, "sourceImport", false);
+	F(experiments, "css", () => (experiments.futureDefaults ? true : "auto"));
+	F(experiments, "html", () => (experiments.futureDefaults ? true : "auto"));
+	F(experiments, "typescript", () =>
+		experiments.futureDefaults ? true : "auto"
+	);
+
+	// Resolve the "auto" default: enable the built-in css/html module type unless
+	// the user already registered a loader (or explicit type) for these files, so
+	// enabling them by default does not break existing css-loader/html-loader setups.
+	// The built-in `/\.css$/i` rule also covers `.module.css`, so a loader scoped to
+	// CSS modules must disable the built-in type too — sample both extensions.
+	// When enabled implicitly the value stays `"auto"` (truthy): Css/HtmlModulesPlugin
+	// then step aside per-module for requests that loaders were applied to
+	// (inline requests or loaders injected via hooks, e.g. html-webpack-plugin).
+	// TODO webpack 6: css/html default to `true`, drop the `"auto"` marker.
+	if (experiments.css === "auto") {
+		experiments.css =
+			!(
+				RuleSetCompiler.hasRuleForResource(rules, "/file.css") ||
+				RuleSetCompiler.hasRuleForResource(rules, "/file.module.css")
+			) && "auto";
+	}
+	if (experiments.html === "auto") {
+		experiments.html =
+			!RuleSetCompiler.hasRuleForResource(rules, "/file.html") && "auto";
+	}
+	// Auto-enable only when the built-in TypeScript support can actually run:
+	// it needs `module.stripTypeScriptTypes` (Node.js >= 22.6) and must step
+	// aside for a loader registered for `.ts`/`.mts`/`.cts` (e.g. ts-loader).
+	if (experiments.typescript === "auto") {
+		experiments.typescript =
+			// eslint-disable-next-line n/no-unsupported-features/node-builtins
+			typeof require("module").stripTypeScriptTypes === "function" &&
+			!(
+				RuleSetCompiler.hasRuleForResource(rules, "/file.ts") ||
+				RuleSetCompiler.hasRuleForResource(rules, "/file.mts") ||
+				RuleSetCompiler.hasRuleForResource(rules, "/file.cts")
+			);
+	}
+	// `syncWebAssembly` already claims `.wasm`, so don't let "auto" override it.
+	if (experiments.asyncWebAssembly === "auto") {
+		experiments.asyncWebAssembly =
+			!experiments.syncWebAssembly &&
+			!RuleSetCompiler.hasRuleForResource(rules, "/file.wasm");
+	}
+
+	if (typeof experiments.buildHttp === "object") {
+		D(experiments.buildHttp, "frozen", production);
+		D(experiments.buildHttp, "upgrade", false);
+	}
+};
+
+/**
+ * Apply cache defaults.
+ * @param {CacheOptionsNormalized} cache options
+ * @param {object} options options
+ * @param {string} options.name name
+ * @param {Mode} options.mode mode
+ * @param {boolean} options.futureDefaults is future defaults enabled
+ * @param {boolean} options.development is development mode
+ * @param {number=} options.compilerIndex index of compiler
+ * @param {Experiments["cacheUnaffected"]} options.cacheUnaffected the cacheUnaffected experiment is enabled
+ * @returns {void}
+ */
+const applyCacheDefaults = (
+	cache,
+	{ name, mode, development, cacheUnaffected, compilerIndex, futureDefaults }
+) => {
+	if (cache === false) return;
+	switch (cache.type) {
+		case "filesystem":
+			F(cache, "name", () =>
+				compilerIndex !== undefined
+					? `${`${name}-${mode}`}__compiler${compilerIndex + 1}__`
+					: `${name}-${mode}`
+			);
+			D(cache, "version", "");
+			F(cache, "cacheDirectory", () => {
+				const cwd = process.cwd();
+				/** @type {string | undefined} */
+				let dir = cwd;
+				for (;;) {
+					try {
+						if (fs.statSync(path.join(dir, "package.json")).isFile()) break;
+						// eslint-disable-next-line no-empty
+					} catch (_err) {}
+					const parent = path.dirname(dir);
+					if (dir === parent) {
+						dir = undefined;
+						break;
+					}
+					dir = parent;
+				}
+				if (!dir) {
+					return path.resolve(cwd, ".cache/webpack");
+				} else if (process.versions.pnp === "1") {
+					return path.resolve(dir, ".pnp/.cache/webpack");
+				} else if (process.versions.pnp === "3") {
+					return path.resolve(dir, ".yarn/.cache/webpack");
+				}
+				return path.resolve(dir, "node_modules/.cache/webpack");
+			});
+			F(cache, "cacheLocation", () =>
+				path.resolve(
+					/** @type {NonNullable} */
+					(cache.cacheDirectory),
+					/** @type {NonNullable} */ (cache.name)
+				)
+			);
+			D(cache, "hashAlgorithm", futureDefaults ? "xxhash64" : "md4");
+			D(cache, "store", "pack");
+			D(cache, "compression", false);
+			D(cache, "profile", false);
+			D(cache, "idleTimeout", 60000);
+			D(cache, "idleTimeoutForInitialStore", 5000);
+			D(cache, "idleTimeoutAfterLargeChanges", 1000);
+			D(cache, "maxMemoryGenerations", development ? 5 : Infinity);
+			D(cache, "maxAge", 1000 * 60 * 60 * 24 * 60); // 1 month
+			D(cache, "allowCollectingMemory", development);
+			D(cache, "memoryCacheUnaffected", development && cacheUnaffected);
+			D(cache, "readonly", false);
+			D(
+				/** @type {NonNullable} */
+				(cache.buildDependencies),
+				"defaultWebpack",
+				[path.resolve(__dirname, "..") + path.sep]
+			);
+			break;
+		case "memory":
+			D(cache, "maxGenerations", Infinity);
+			D(cache, "cacheUnaffected", development && cacheUnaffected);
+			break;
+	}
+};
+
+/**
+ * Apply snapshot defaults.
+ * @param {SnapshotOptions} snapshot options
+ * @param {object} options options
+ * @param {boolean} options.production is production
+ * @param {boolean} options.futureDefaults is future defaults enabled
+ * @returns {void}
+ */
+const applySnapshotDefaults = (snapshot, { production, futureDefaults }) => {
+	if (futureDefaults) {
+		F(snapshot, "managedPaths", () =>
+			process.versions.pnp === "3"
+				? [
+						/^(.+?(?:[\\/]\.yarn[\\/]unplugged[\\/][^\\/]+)?[\\/]node_modules[\\/])/
+					]
+				: [/^(.+?[\\/]node_modules[\\/])/]
+		);
+		F(snapshot, "immutablePaths", () =>
+			process.versions.pnp === "3"
+				? [/^(.+?[\\/]cache[\\/][^\\/]+\.zip[\\/]node_modules[\\/])/]
+				: []
+		);
+	} else {
+		A(snapshot, "managedPaths", () => {
+			if (process.versions.pnp === "3") {
+				const match =
+					/^(.+?)[\\/]cache[\\/]watchpack-npm-[^\\/]+\.zip[\\/]node_modules[\\/]/.exec(
+						require.resolve("watchpack")
+					);
+				if (match) {
+					return [path.resolve(match[1], "unplugged")];
+				}
+			} else {
+				const match = /^(.+?[\\/]node_modules[\\/])/.exec(
+					require.resolve("watchpack")
+				);
+				if (match) {
+					return [match[1]];
+				}
+			}
+			return [];
+		});
+		A(snapshot, "immutablePaths", () => {
+			if (process.versions.pnp === "1") {
+				const match =
+					/^(.+?[\\/]v4)[\\/]npm-watchpack-[^\\/]+-[\da-f]{40}[\\/]node_modules[\\/]/.exec(
+						require.resolve("watchpack")
+					);
+				if (match) {
+					return [match[1]];
+				}
+			} else if (process.versions.pnp === "3") {
+				const match =
+					/^(.+?)[\\/]watchpack-npm-[^\\/]+\.zip[\\/]node_modules[\\/]/.exec(
+						require.resolve("watchpack")
+					);
+				if (match) {
+					return [match[1]];
+				}
+			}
+			return [];
+		});
+	}
+	F(snapshot, "unmanagedPaths", () => []);
+	F(snapshot, "resolveBuildDependencies", () => ({
+		timestamp: true,
+		hash: true
+	}));
+	F(snapshot, "buildDependencies", () => ({ timestamp: true, hash: true }));
+	F(snapshot, "module", () =>
+		production ? { timestamp: true, hash: true } : { timestamp: true }
+	);
+	F(snapshot, "contextModule", () => ({ timestamp: true }));
+	F(snapshot, "resolve", () =>
+		production ? { timestamp: true, hash: true } : { timestamp: true }
+	);
+};
+
+/**
+ * Apply javascript parser options defaults.
+ * @param {JavascriptParserOptions} parserOptions parser options
+ * @param {object} options options
+ * @param {boolean} options.futureDefaults is future defaults enabled
+ * @param {boolean} options.deferImport is defer import enabled
+ * @param {boolean} options.sourceImport is import source enabled
+ * @param {boolean} options.isNode is node target platform
+ * @param {boolean} options.outputModule is output.module enabled
+ * @param {WebpackOptionsNormalized["output"]["library"]} options.library library options
+ * @param {boolean} options.typescript is typescript enabled
+ * @returns {void}
+ */
+const applyJavascriptParserOptionsDefaults = (
+	parserOptions,
+	{
+		futureDefaults,
+		deferImport,
+		sourceImport,
+		isNode,
+		outputModule,
+		library,
+		typescript
+	}
+) => {
+	D(parserOptions, "unknownContextRequest", ".");
+	D(parserOptions, "unknownContextRegExp", false);
+	D(parserOptions, "unknownContextRecursive", true);
+	D(parserOptions, "unknownContextCritical", true);
+	D(parserOptions, "exprContextRequest", ".");
+	D(parserOptions, "exprContextRegExp", false);
+	D(parserOptions, "exprContextRecursive", true);
+	D(parserOptions, "exprContextCritical", true);
+	D(parserOptions, "wrappedContextRegExp", /.*/);
+	D(parserOptions, "wrappedContextRecursive", true);
+	D(parserOptions, "wrappedContextCritical", false);
+	D(parserOptions, "strictThisContextOnImports", false);
+	D(parserOptions, "importMeta", outputModule ? "preserve-unknown" : true);
+	D(parserOptions, "dynamicImportMode", "lazy");
+	D(parserOptions, "dynamicImportPrefetch", false);
+	D(parserOptions, "dynamicImportPreload", false);
+	D(parserOptions, "dynamicImportFetchPriority", false);
+	D(parserOptions, "createRequire", isNode);
+	D(parserOptions, "dynamicUrl", true);
+	D(parserOptions, "deferImport", deferImport);
+	D(parserOptions, "sourceImport", sourceImport);
+	D(parserOptions, "typescript", typescript);
+	if (futureDefaults) D(parserOptions, "exportsPresence", "error");
+	D(parserOptions, "strictModeViolations", futureDefaults ? "error" : "warn");
+	D(parserOptions, "anonymousDefaultExportName", !library);
+};
+
+/**
+ * Apply json generator options defaults.
+ * @param {JsonGeneratorOptions} generatorOptions generator options
+ * @returns {void}
+ */
+const applyJsonGeneratorOptionsDefaults = (generatorOptions) => {
+	D(generatorOptions, "JSONParse", true);
+};
+
+/**
+ * Apply css generator options defaults.
+ * @param {CssGeneratorOptions} generatorOptions generator options
+ * @param {object} options options
+ * @param {TargetProperties | false} options.targetProperties target properties
+ * @returns {void}
+ */
+const applyCssGeneratorOptionsDefaults = (
+	generatorOptions,
+	{ targetProperties }
+) => {
+	D(
+		generatorOptions,
+		"exportsOnly",
+		!targetProperties || targetProperties.document === false
+	);
+	D(generatorOptions, "esModule", true);
+};
+
+/**
+ * Apply module defaults.
+ * @param {ModuleOptions} module options
+ * @param {object} options options
+ * @param {boolean} options.cache is caching enabled
+ * @param {boolean} options.syncWebAssembly is syncWebAssembly enabled
+ * @param {boolean} options.asyncWebAssembly is asyncWebAssembly enabled
+ * @param {boolean} options.typescript is typescript enabled
+ * @param {boolean} options.css is css enabled
+ * @param {boolean} options.html is html enabled
+ * @param {boolean} options.futureDefaults is future defaults enabled
+ * @param {string} options.uniqueName the unique name
+ * @param {boolean} options.isNode is node target platform
+ * @param {boolean} options.deferImport is defer import enabled
+ * @param {boolean} options.sourceImport is import source enabled
+ * @param {TargetProperties | false} options.targetProperties target properties
+ * @param {Mode | undefined} options.mode mode
+ * @param {HashSalt} options.hashSalt hash salt
+ * @param {HashFunction} options.hashFunction hash function
+ * @param {boolean} options.outputModule is output.module enabled
+ * @param {WebpackOptionsNormalized["output"]["library"]} options.library library options
+ * @returns {void}
+ */
+const applyModuleDefaults = (
+	module,
+	{
+		hashSalt,
+		hashFunction,
+		cache,
+		syncWebAssembly,
+		asyncWebAssembly,
+		css,
+		html,
+		typescript,
+		futureDefaults,
+		isNode,
+		uniqueName,
+		targetProperties,
+		mode,
+		deferImport,
+		sourceImport,
+		outputModule,
+		library
+	}
+) => {
+	if (cache) {
+		D(
+			module,
+			"unsafeCache",
+			/**
+			 * Handles the callback logic for this hook.
+			 * @param {Module} module module
+			 * @returns {boolean} true, if we want to cache the module
+			 */
+			(module) => {
+				const name = module.nameForCondition();
+				if (!name) {
+					return false;
+				}
+				return NODE_MODULES_REGEXP.test(name);
+			}
+		);
+	} else {
+		D(module, "unsafeCache", false);
+	}
+
+	F(module.parser, ASSET_MODULE_TYPE, () => ({}));
+	F(
+		/** @type {NonNullable} */
+		(module.parser[ASSET_MODULE_TYPE]),
+		"dataUrlCondition",
+		() => ({})
+	);
+	if (
+		typeof (
+			/** @type {NonNullable} */
+			(module.parser[ASSET_MODULE_TYPE]).dataUrlCondition
+		) === "object"
+	) {
+		D(
+			/** @type {NonNullable} */
+			(module.parser[ASSET_MODULE_TYPE]).dataUrlCondition,
+			"maxSize",
+			8096
+		);
+	}
+
+	F(module.parser, "javascript", () => ({}));
+	F(module.parser, JSON_MODULE_TYPE, () => ({}));
+	D(
+		/** @type {NonNullable} */
+		(module.parser[JSON_MODULE_TYPE]),
+		"exportsDepth",
+		mode === "development" ? 1 : Infinity
+	);
+
+	applyJavascriptParserOptionsDefaults(
+		/** @type {NonNullable} */
+		(module.parser.javascript),
+		{
+			futureDefaults,
+			deferImport,
+			sourceImport,
+			isNode,
+			outputModule,
+			library,
+			typescript
+		}
+	);
+
+	F(module.generator, "json", () => ({}));
+
+	applyJsonGeneratorOptionsDefaults(
+		/** @type {NonNullable} */
+		(module.generator.json)
+	);
+
+	if (css) {
+		F(module.parser, CSS_MODULE_TYPE, () => ({}));
+
+		D(
+			/** @type {NonNullable} */
+			(module.parser[CSS_MODULE_TYPE]),
+			"import",
+			true
+		);
+		D(
+			/** @type {NonNullable} */
+			(module.parser[CSS_MODULE_TYPE]),
+			"url",
+			true
+		);
+		D(
+			/** @type {NonNullable} */
+			(module.parser[CSS_MODULE_TYPE]),
+			"namedExports",
+			true
+		);
+		D(
+			/** @type {NonNullable} */
+			(module.parser[CSS_MODULE_TYPE]),
+			"customMedia",
+			true
+		);
+		D(
+			/** @type {NonNullable} */
+			(module.parser[CSS_MODULE_TYPE]),
+			"customSelectors",
+			true
+		);
+
+		for (const type of [
+			CSS_MODULE_TYPE_AUTO,
+			CSS_MODULE_TYPE_MODULE,
+			CSS_MODULE_TYPE_GLOBAL
+		]) {
+			F(module.parser, type, () => ({}));
+
+			D(
+				/** @type {NonNullable | NonNullable | NonNullable} */
+				(module.parser[type]),
+				"animation",
+				true
+			);
+			D(
+				/** @type {NonNullable | NonNullable | NonNullable} */
+				(module.parser[type]),
+				"container",
+				true
+			);
+			D(
+				/** @type {NonNullable | NonNullable | NonNullable} */
+				(module.parser[type]),
+				"customIdents",
+				true
+			);
+			D(
+				/** @type {NonNullable | NonNullable | NonNullable} */
+				(module.parser[type]),
+				"dashedIdents",
+				true
+			);
+			D(
+				/** @type {NonNullable | NonNullable | NonNullable} */
+				(module.parser[type]),
+				"function",
+				true
+			);
+			D(
+				/** @type {NonNullable | NonNullable | NonNullable} */
+				(module.parser[type]),
+				"grid",
+				true
+			);
+			D(
+				/** @type {NonNullable | NonNullable | NonNullable} */
+				(module.parser[type]),
+				"customMedia",
+				true
+			);
+			D(
+				/** @type {NonNullable | NonNullable | NonNullable} */
+				(module.parser[type]),
+				"customSelectors",
+				true
+			);
+		}
+
+		F(module.generator, CSS_MODULE_TYPE, () => ({}));
+
+		applyCssGeneratorOptionsDefaults(
+			/** @type {NonNullable} */
+			(module.generator[CSS_MODULE_TYPE]),
+			{ targetProperties }
+		);
+
+		const localIdentName =
+			mode === "development"
+				? uniqueName.length > 0
+					? "[uniqueName]-[id]-[local]"
+					: "[id]-[local]"
+				: "[fullhash]";
+		const localIdentHashSalt = hashSalt;
+		const localIdentHashDigest = "base64url";
+		const localIdentHashDigestLength = 6;
+		const exportsConvention = "as-is";
+
+		for (const type of [
+			CSS_MODULE_TYPE_AUTO,
+			CSS_MODULE_TYPE_MODULE,
+			CSS_MODULE_TYPE_GLOBAL
+		]) {
+			F(module.generator, type, () => ({}));
+
+			D(
+				/** @type {NonNullable | NonNullable | NonNullable} */
+				(module.generator[type]),
+				"localIdentName",
+				localIdentName
+			);
+
+			D(
+				/** @type {NonNullable | NonNullable | NonNullable} */
+				(module.generator[type]),
+				"localIdentHashSalt",
+				localIdentHashSalt
+			);
+
+			D(
+				/** @type {NonNullable | NonNullable | NonNullable} */
+				(module.generator[type]),
+				"localIdentHashFunction",
+				hashFunction
+			);
+
+			D(
+				/** @type {NonNullable | NonNullable | NonNullable} */
+				(module.generator[type]),
+				"localIdentHashDigest",
+				localIdentHashDigest
+			);
+
+			D(
+				/** @type {NonNullable | NonNullable | NonNullable} */
+				(module.generator[type]),
+				"localIdentHashDigestLength",
+				localIdentHashDigestLength
+			);
+
+			D(
+				/** @type {NonNullable | NonNullable | NonNullable} */
+				(module.generator[type]),
+				"exportsConvention",
+				exportsConvention
+			);
+		}
+	}
+
+	if (html) {
+		// `module.generator.html.extract` is intentionally left undefined by
+		// default: HtmlGenerator treats undefined as "extract iff this HTML
+		// module is a compilation entry", which is the HTML-entry-point
+		// behaviour. Setting `extract: true` forces extraction for all HTML
+		// modules (including imported ones); `extract: false` disables it
+		// everywhere.
+		F(module.generator, HTML_MODULE_TYPE, () => ({}));
+		// `module.parser.html.sources` defaults to `true` — HtmlParser uses
+		// the built-in source list to extract URL-like attributes (``, ``, `` (as `cloneTagWithUrl` does for a real ``,
+ * so synthesize a real script element instead — copying only the CSP/fetch
+ * attributes (`extra`, captured at parse time), since the custom element's
+ * other attributes have no defined meaning on a ``;
+};
+
+/**
+ * Clone the original `` for script tags)
+ */
+const cloneTagWithUrl = (
+	originalTag,
+	srcStartInTag,
+	srcEndInTag,
+	newUrl,
+	elementKind,
+	crossOrigin,
+	tagNameEndInTag,
+	integrityRange,
+	typeValueRange,
+	integrity
+) => {
+	const isLink =
+		elementKind === "modulepreload" || elementKind === "stylesheet";
+
+	/** @type {{ start: number, end: number, text: string }[]} */
+	const edits = [{ start: srcStartInTag, end: srcEndInTag, text: newUrl }];
+
+	// Drop the author's `integrity` — content-specific, wrong for another
+	// chunk's file. A correct per-chunk one is re-inserted below from
+	// `output.html.integrity`.
+	if (integrityRange) {
+		edits.push({ start: integrityRange[0], end: integrityRange[1], text: "" });
+	}
+
+	// Inserted right after the tag name: `crossorigin` from
+	// `output.crossOriginLoading`, then a forced `type="module"` when the tag
+	// has no `type` value to rewrite in place.
+	let afterName = crossOrigin;
+	if (elementKind === "script-module") {
+		if (typeValueRange) {
+			edits.push({
+				start: typeValueRange[0],
+				end: typeValueRange[1],
+				text: "module"
+			});
+		} else {
+			afterName += ' type="module"';
+		}
+	}
+
+	// Per-chunk `integrity` sentinel goes in alongside `crossorigin`/`type`.
+	afterName += integrity;
+	if (afterName) {
+		edits.push({
+			start: tagNameEndInTag,
+			end: tagNameEndInTag,
+			text: afterName
+		});
+	}
+
+	edits.sort((a, b) => b.start - a.start);
+	let body = originalTag;
+	for (const edit of edits) {
+		body = body.slice(0, edit.start) + edit.text + body.slice(edit.end);
+	}
+
+	// `` is a void element — no closing tag. ``.
+	return isLink ? body : `${body}`;
+};
+
+// ES5 `` polyfill (Safari <17, Firefox <115, …):
+// on browsers without native support, fetch each modulepreload target with the
+// tag's own credentials/integrity so the pending module import reuses it. The
+// `.ep` guard makes it idempotent, and the observer catches any later-inserted
+// links. Emitted only when the target environment lacks native support.
+const MODULEPRELOAD_POLYFILL = [
+	"(function(){",
+	'var s=document.createElement("link").relList;',
+	'if(s&&s.supports&&s.supports("modulepreload"))return;',
+	"function f(e){",
+	"if(e.ep)return;e.ep=1;",
+	"var o={};",
+	"if(e.integrity)o.integrity=e.integrity;",
+	"if(e.referrerPolicy)o.referrerPolicy=e.referrerPolicy;",
+	'o.credentials=e.crossOrigin==="use-credentials"?"include":e.crossOrigin==="anonymous"?"omit":"same-origin";',
+	"fetch(e.href,o)}",
+	"var l=document.querySelectorAll('link[rel=\"modulepreload\"]');",
+	"for(var i=0;i` per rendered page. Keyed by the `ReplaceSource` of a
+// single HTML code-generation pass (shared by all of a page's entry tags, fresh
+// per pass), so multi-``;
+				}
+				const url = getHtmlGenerator().makeChunkUrlSentinel(chunk, kind);
+				const integrityAttr = integrityOn
+					? ` integrity="${getHtmlGenerator().makeChunkIntegritySentinel(chunk, kind)}"`
+					: "";
+				if (kind === "css") {
+					// A CSS chunk is always loaded via ``.
+					// Clone only when the entry tag is itself a native `` (so attributes like `media` carry over);
+					// a ``);
+				}
+				// Insert right after the `` open tag (parser-derived) so the
+				// hints are the document's first fetches; fall back to the first
+				// blocking script (else the entry tag) when the page has no
+				// written `` (e.g. an HTML fragment).
+				const hintAt =
+					dep.headOpenEnd >= 0
+						? dep.headOpenEnd
+						: dep.cssAnchor === -1
+							? dep.tagStart
+							: dep.cssAnchor;
+				source.insert(hintAt, finalHints.join(""));
+			}
+		}
+	}
+};
+
+makeSerializable(
+	HtmlEntryDependency,
+	"webpack/lib/dependencies/HtmlEntryDependency"
+);
+
+module.exports = HtmlEntryDependency;
diff --git a/lib/dependencies/HtmlInlineHtmlDependency.js b/lib/dependencies/HtmlInlineHtmlDependency.js
new file mode 100644
index 00000000000..db657f53e6a
--- /dev/null
+++ b/lib/dependencies/HtmlInlineHtmlDependency.js
@@ -0,0 +1,107 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+*/
+
+"use strict";
+
+const { HTML_TYPE } = require("../ModuleSourceTypeConstants");
+const makeSerializable = require("../util/makeSerializable");
+const ModuleDependency = require("./ModuleDependency");
+
+/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
+/** @typedef {import("../CodeGenerationResults")} CodeGenerationResults */
+/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */
+/** @typedef {import("../Dependency")} Dependency */
+/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
+/** @typedef {import("../Module")} Module */
+/** @typedef {import("../javascript/JavascriptParser").Range} Range */
+/** @typedef {import("../util/Hash")} Hash */
+
+// One pass over the source: each char is replaced once and replacements aren't
+// re-scanned, so this matches the `&`-first sequential escape chain exactly.
+const ATTR_ESCAPE_REGEXP = /[&"']/g;
+/** @type {Record} */
+const ATTR_ESCAPES = { "&": "&", '"': """, "'": "'" };
+/**
+ * @param {string} c matched character
+ * @returns {string} the HTML-attribute-escaped entity
+ */
+const escapeAttrChar = (c) => ATTR_ESCAPES[c];
+
+/**
+ * Represents an `')).toBe(
+			""
+		);
+	});
+
+	it("does it in foreign content too", () => {
+		// The tokenizer's after-attribute-name state reads the `/` as the tag's,
+		// not as part of the name, so a self-closing tag needs nothing extra.
+		expect(minify('')).toBe("");
+		expect(minify('')).toBe(
+			""
+		);
+	});
+
+	it("leaves a value a reference only decodes to empty", () => {
+		// Read raw: what a reference decodes to is not the printer's business.
+		expect(minify('
x
')).toContain("title= "); + }); + + it("prints a boolean attribute repeating its own name as the name", () => { + expect(minify('')).toBe( + "" + ); + }); + + it("keeps a same-length value that is not the name", () => { + // `_isBooleanAttribute` is asked before the fold, so a value only as long + // as the name still has to be compared against it. + expect(minify('')).toBe(""); + }); +}); + +describe("SourceProcessor — removeEmptyElements reads the output", () => { + const { SourceProcessor } = require("../lib/html/syntax"); + + /** + * @param {string} html input markup + * @param {object=} options extra print options + * @returns {string} the minified serialization + */ + const minify = (html, options) => + new SourceProcessor().process(html, { + mode: "minify", + removeEmptyElements: true, + ...options + }).code; + + it("drops a run of nested empties together", () => { + expect(minify("
")).toBe(""); + expect(minify("

")).toBe(""); + }); + + it("drops one left empty by a comment going", () => { + expect(minify("
")).toBe(""); + }); + + it("keeps one that spells an attribute, whatever it holds", () => { + expect(minify("
")).toBe( + "
" + ); + expect(minify("
")).toBe("
"); + }); + + it("keeps one whose child still prints", () => { + expect(minify("
")).toBe( + "
" + ); + expect(minify("
x
")).toBe( + "
x
" + ); + }); + + it("follows the collapse tier for a whitespace child", () => { + expect(minify("
")).toBe("
"); + expect(minify("
", { collapseWhitespace: "all" })).toBe(""); + }); + + it("keeps one whose whitespace is data rather than layout", () => { + // A literal-text body is written as it is meant to be read, so whitespace + // there leaves the element non-empty at every tier. + for (const name of ["style", "script", "iframe", "noframes"]) { + const html = `
<${name}>
`; + expect(minify(html, { collapseWhitespace: "all" })).toBe(html); + } + expect( + minify("
  
", { collapseWhitespace: "all" }) + ).toBe("
  
"); + }); + + it("keeps foreign content, whose whitespace it cannot read", () => { + expect(minify("
")).toBe("
"); + }); + + it("drops a head holding nothing but whitespace", () => { + // Nothing renders what sits directly in ``, whatever the tier says. + expect(minify(" x")).toBe("x"); + }); +}); + +describe("SourceProcessor — removeImpliedTags", () => { + const { SourceProcessor } = require("../lib/html/syntax"); + + const PAGE = + "t
  • a
"; + + /** + * @param {(boolean | "smart" | "all")=} removeImpliedTags the mode + * @returns {string} the minified serialization + */ + const minify = (removeImpliedTags) => + new SourceProcessor().process(PAGE, { mode: "minify", removeImpliedTags }) + .code; + + it("leaves out only the start tag by default", () => { + // `` stays with it: a reader checking the page downloaded whole + // looks for one, and only the start tag is what nothing matches on. + expect(minify()).toBe( + "t
  • a
" + ); + expect(minify("smart")).toBe(minify()); + }); + + it("leaves out all six when asked", () => { + expect(minify(true)).toBe("t
  • a
"); + expect(minify("all")).toBe(minify(true)); + }); + + it("keeps all three when off, and the other optional tags still go", () => { + // Every optional tag but these three is dropped whatever the option says: + // nothing can observe that, and only these three are what a consumer + // reading the page with a regexp looks for. + expect(minify(false)).toBe( + "t
  • a
" + ); + }); + + it("keeps a that carries an attribute", () => { + expect( + new SourceProcessor().process("x", { mode: "minify" }) + .code + ).toBe("x"); + }); + + it("invents no for a source that spelled no ", () => { + expect(new SourceProcessor().process("

a", { mode: "minify" }).code).toBe( + "

a" + ); + }); + + /** + * @param {string} html markup + * @returns {string} the minified serialization, every optional tag dropped + */ + const minifyAll = (html) => + new SourceProcessor().process(html, { + mode: "minify", + removeImpliedTags: "all" + }).code; + + it("keeps the tag whitespace behind it would re-parse into", () => { + // A space opening the body lands in the head without ``. + expect( + minifyAll("t text") + ).toBe("t text"); + }); + + it("drops a shell tag over whitespace that prints nothing", () => { + // Whitespace inside ``, or behind ``, sits outside every + // block formatting context, so no tier prints it and no tag guards it. + expect( + minifyAll("\nt\nx") + ).toBe("tx"); + expect( + minifyAll("t x") + ).toBe("tx"); + }); + + it("looks past a comment it is about to drop", () => { + expect( + minifyAll("tx") + ).toBe("tx"); + // One it keeps stays behind the tag, so the tag stays too. + expect( + minifyAll("tx") + ).toBe("tx"); + }); +}); + +describe("SourceProcessor — attribute quote spelling", () => { + const { SourceProcessor } = require("../lib/html/syntax"); + + /** + * @param {string} html a document + * @returns {string} its minified form + */ + const minify = (html) => + new SourceProcessor().process(html, { mode: "minify" }).code; + + it("swaps the delimiter so escaped quotes can stand literal", () => { + expect(minify('say "hi" to "them"')).toBe( + '\'say' + ); + expect(minify('

')).toBe( + "
" + ); + }); + + it("keeps every other reference the value spells", () => { + // `&quot;` is the text `"`, not a quote, so it stays escaped — + // and ` ` / ` ` still need a reference under either delimiter. + expect(minify('&quot; literal "q"')).toBe( + "&quot; literal \"q\"" + ); + expect(minify('nb sp "q"')).toBe( + "nb sp \"q\"" + ); + expect(minify('line
br "q"')).toBe( + "line
br \"q\"" + ); + }); + + it("escapes only the delimiter when a value carries both kinds", () => { + // One of them has to be escaped, so the cheaper delimiter is the one that + // leaves the more frequent quote standing literal. + expect(minify('it's "x"')).toBe( + "it's \"x\"" + ); + // Two of each: escaping the apostrophes is the shorter of the two. + expect(minify(''a' "b"')).toBe( + "'a' \"b\"" + ); + }); + + it("writes a reference the parser cannot read back as one", () => { + // `<` and `>` mean nothing inside a quoted value, and an `&` no reference + // can start after it is data — but one that could start a reference stays. + expect(minify('

t

')).toBe( + '

t' + ); + expect(minify('

t

')).toBe('

t'); + expect(minify('t')).toBe( + 't' + ); + // A bare value ends at `>`, so there the reference has to stay. + expect(minify("

t

")).toBe("

t"); + }); +}); + +describe("SourceProcessor — token list values", () => { + const { SourceProcessor } = require("../lib/html/syntax"); + + /** + * @param {string} html input markup + * @returns {string} the minified serialization + */ + const minify = (html) => + new SourceProcessor().process(html, { mode: "minify" }).code; + + it("collapses the separators of every list", () => { + expect(minify('

x
')).toContain('class="a b"'); + expect(minify('l')).toContain( + 'ping="/p1 /p2"' + ); + }); + + it("trims a list whose separators are otherwise already collapsed", () => { + // Padding at either end is all the rewriting these need — the scan that + // decides whether a list is already collapsed has to see it. + expect(minify('
x
')).toContain('class="a b"'); + expect(minify('
x
')).toContain('class="a b"'); + expect(minify('
x
')).toContain("class=ab"); + }); + + it("drops a repeated token only where the DOM folds it away", () => { + // `class` / `rel` reflect a `DOMTokenList`, which is an ordered set. + expect(minify('
x
')).toContain('class="a b"'); + expect(minify('')).toContain( + "rel=preload" + ); + // `ping` is read back as written and sends one request per token; the + // other two reflect no token list either. + expect(minify('l')).toContain('ping="/p /p"'); + expect(minify('
c
')).toContain( + 'headers="h h"' + ); + expect(minify('
x
')).toContain('accesskey="k k"'); + }); +}); + +describe("SourceProcessor — sortAttributes / sortTokenLists", () => { + const { SourceProcessor } = require("../lib/html/syntax"); + + /** + * @param {string} html input markup + * @param {object=} options extra print options + * @returns {string} the minified serialization + */ + const minify = (html, options) => + new SourceProcessor().process(html, { mode: "minify", ...options }).code; + + // `serializeHtmlTree` sorts attributes, so attribute order cannot hide in it; + // `class` is a set the option reorders on purpose, so sort it here too. + /** + * @param {string} html markup + * @returns {string} its DOM, spelled so neither order can show through + */ + const canonical = (html) => + serializeHtmlTree(parseHtmlRefs(html)).replace( + /^(\|\s*class=")([^"]*)(")$/gm, + (_m, open, value, close) => + open + value.split(" ").sort().join(" ") + close + ); + + // Attribute and class order vary per element: that is the repetition the + // options exist to create. + const PAGE = `t${[ + '

x

"); + expect(/** @type {MatElement} */ (nodes[0]).tagName).toBe("p"); + }); + + it("handles
", + "noscript" + ); + expect(noscript.children[0].type).toBe(NodeType.Comment); + expect( + /** @type {MatElement} */ ( + noscript.children.find((c) => c.type === NodeType.Element) + ).tagName + ).toBe("link"); + }); + + it("pops