diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 00000000000..e5b6d8d6a67 --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,8 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works +with multi-package repos, or single-package repos to help you version and publish your code. You can +find the full documentation for it [in our repository](https://github.com/changesets/changesets) + +We have a quick list of common questions to get you started engaging with this project in +[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/.changeset/auto-detect-esm-tla-import-meta.md b/.changeset/auto-detect-esm-tla-import-meta.md new file mode 100644 index 00000000000..68086179286 --- /dev/null +++ b/.changeset/auto-detect-esm-tla-import-meta.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Treat top-level await and `import.meta` as ES module markers, matching Node.js syntax detection so no explicit module type is needed. diff --git a/.changeset/avoid-entry-iife-fast-path.md b/.changeset/avoid-entry-iife-fast-path.md new file mode 100644 index 00000000000..c8793bacf02 --- /dev/null +++ b/.changeset/avoid-entry-iife-fast-path.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Skip re-parsing the inlined entry module when no renaming is needed. diff --git a/.changeset/avoid-entry-iife-multi-entry-fast-path.md b/.changeset/avoid-entry-iife-multi-entry-fast-path.md new file mode 100644 index 00000000000..dd1aa004344 --- /dev/null +++ b/.changeset/avoid-entry-iife-multi-entry-fast-path.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Extend the avoidEntryIife no-parse fast path to multi-entry chunks. diff --git a/.changeset/binary-middleware-dispatch-table.md b/.changeset/binary-middleware-dispatch-table.md new file mode 100644 index 00000000000..030eaf3b4fb --- /dev/null +++ b/.changeset/binary-middleware-dispatch-table.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Reuse the binary deserialize dispatch table to speed up cache restore. diff --git a/.changeset/buildinfo-buildmeta-per-module-types.md b/.changeset/buildinfo-buildmeta-per-module-types.md new file mode 100644 index 00000000000..0591cb7ce14 --- /dev/null +++ b/.changeset/buildinfo-buildmeta-per-module-types.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Type `buildInfo` and `buildMeta` per module type with shared common properties. diff --git a/.changeset/bun-target.md b/.changeset/bun-target.md new file mode 100644 index 00000000000..764fccc6b65 --- /dev/null +++ b/.changeset/bun-target.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Add a `bun` target that emits ESM and externalizes `bun:*` and node.js built-in modules. diff --git a/.changeset/changelog-generator.mjs b/.changeset/changelog-generator.mjs new file mode 100644 index 00000000000..547a3aabb0e --- /dev/null +++ b/.changeset/changelog-generator.mjs @@ -0,0 +1,137 @@ +import { getInfo, getInfoFromPullRequest } from "@changesets/get-github-info"; + +/** @typedef {import("@changesets/types").ChangelogFunctions} ChangelogFunctions */ + +/** + * @returns {{ GITHUB_SERVER_URL: string }} value + */ +function readEnv() { + const GITHUB_SERVER_URL = + process.env.GITHUB_SERVER_URL || "https://github.com"; + return { GITHUB_SERVER_URL }; +} + +/** @type {ChangelogFunctions} */ +const changelogFunctions = { + getDependencyReleaseLine: async ( + changesets, + dependenciesUpdated, + options + ) => { + if (!options.repo) { + throw new Error( + 'Please provide a repo to this changelog generator like this:\n"changelog": ["@changesets/changelog-github", { "repo": "org/repo" }]' + ); + } + if (dependenciesUpdated.length === 0) return ""; + + const changesetLink = `- Updated dependencies [${( + await Promise.all( + changesets.map(async (cs) => { + if (cs.commit) { + const { links } = await getInfo({ + repo: options.repo, + commit: cs.commit + }); + return links.commit; + } + }) + ) + ) + .filter(Boolean) + .join(", ")}]:`; + + const updatedDependenciesList = dependenciesUpdated.map( + (dependency) => ` - ${dependency.name}@${dependency.newVersion}` + ); + + return [changesetLink, ...updatedDependenciesList].join("\n"); + }, + getReleaseLine: async (changeset, type, options) => { + const { GITHUB_SERVER_URL } = readEnv(); + if (!options || !options.repo) { + throw new Error( + 'Please provide a repo to this changelog generator like this:\n"changelog": ["@changesets/changelog-github", { "repo": "org/repo" }]' + ); + } + + /** @type {number | undefined} */ + let prFromSummary; + /** @type {string | undefined} */ + let commitFromSummary; + /** @type {string[]} */ + const usersFromSummary = []; + + const replacedChangelog = changeset.summary + .replace(/^\s*(?:pr|pull|pull\s+request):\s*#?(\d+)/im, (_, pr) => { + const num = Number(pr); + if (!Number.isNaN(num)) prFromSummary = num; + return ""; + }) + .replace(/^\s*commit:\s*([^\s]+)/im, (_, commit) => { + commitFromSummary = commit; + return ""; + }) + .replace(/^\s*(?:author|user):\s*@?([^\s]+)/gim, (_, user) => { + usersFromSummary.push(user); + return ""; + }) + .trim(); + + const [firstLine, ...futureLines] = replacedChangelog + .split("\n") + .map((l) => l.trimEnd()); + + const links = await (async () => { + if (prFromSummary !== undefined) { + let { links } = await getInfoFromPullRequest({ + repo: options.repo, + pull: prFromSummary + }); + if (commitFromSummary) { + const shortCommitId = commitFromSummary.slice(0, 7); + links = { + ...links, + commit: `[\`${shortCommitId}\`](${GITHUB_SERVER_URL}/${options.repo}/commit/${commitFromSummary})` + }; + } + return links; + } + const commitToFetchFrom = commitFromSummary || changeset.commit; + if (commitToFetchFrom) { + const { links } = await getInfo({ + repo: options.repo, + commit: commitToFetchFrom + }); + return links; + } + return { + commit: null, + pull: null, + user: null + }; + })(); + + const users = usersFromSummary.length + ? usersFromSummary + .map( + (userFromSummary) => + `[@${userFromSummary}](${GITHUB_SERVER_URL}/${userFromSummary})` + ) + .join(", ") + : links.user; + + let suffix = ""; + if (links.pull || links.commit || users) { + suffix = `(${users ? `by ${users} ` : ""}in ${ + links.pull || links.commit + })`; + } + + return `\n\n- ${firstLine} ${suffix}\n${futureLines + .map((l) => ` ${l}`) + .join("\n")}`; + } +}; + +export default changelogFunctions; diff --git a/.changeset/changeset-validate.mjs b/.changeset/changeset-validate.mjs new file mode 100644 index 00000000000..2e2073148c5 --- /dev/null +++ b/.changeset/changeset-validate.mjs @@ -0,0 +1,141 @@ +import fs from "fs/promises"; +import path from "path"; +import { fileURLToPath } from "url"; +import { simpleGit } from "simple-git"; +import pkgJson from "../package.json" with { type: "json" }; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const rootPath = path.join(__dirname, ".."); +const git = simpleGit(rootPath); + +const VALID_BUMPS = new Set(["major", "minor", "patch"]); +const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/; +const ENTRY_RE = /^"([^"]+)"\s*:\s*([a-zA-Z]+)\s*$/; + +const toLines = (output) => + output + .split(/\r?\n/) + .map((line) => line.trim()) + .filter(Boolean); + +const isChangeset = (filePath) => { + const normalized = filePath.replace(/\\/g, "/"); + return ( + normalized.startsWith(".changeset/") && + normalized.endsWith(".md") && + normalized !== ".changeset/README.md" + ); +}; + +const gitDiff = async (more = []) => { + const args = [ + "diff", + "--name-only", + // cspell:ignore ACMR + "--diff-filter=ACMR", + ...more, + "--", + ".changeset/*.md" + ].filter(Boolean); + + return toLines(await git.raw(args)); +}; + +const getChangedFiles = async () => { + const files = new Set(); + const baseRef = process.env.GITHUB_BASE_REF; + + // GitHub Actions base diff + if (baseRef) { + for (const file of await gitDiff([`origin/${baseRef}...HEAD`])) { + if (isChangeset(file)) files.add(file); + } + } + // Local working tree changes + else { + const _files = [ + // Unstaged changes + ...(await gitDiff()), + // Staged but uncommitted changes + ...(await gitDiff(["--cached"])), + // Untracked files + ...(await git.status()).not_added + ]; + for (const file of _files) { + if (isChangeset(file)) files.add(file); + } + } + return files; +}; + +const validate = async (filePath) => { + const absoluteFilePath = path.join(rootPath, filePath); + const content = await fs.readFile(absoluteFilePath, "utf8"); + const frontmatterMatch = content.match(FRONTMATTER_RE); + const errors = []; + + if (!frontmatterMatch) { + errors.push("missing YAML frontmatter block"); + return errors; + } + + const entries = frontmatterMatch[1] + .split(/\r?\n/) + .map((line) => line.trim()) + .filter(Boolean); + + if (entries.length === 0) { + errors.push("frontmatter does not contain package bump entries"); + return errors; + } + + for (const entry of entries) { + const match = entry.match(ENTRY_RE); + if (!match) { + errors.push(`invalid frontmatter entry: ${entry}`); + continue; + } + + const [, pkgName, bumpType] = match; + if (pkgName !== pkgJson.name) { + errors.push( + `invalid package name "${pkgName}", expected "${pkgJson.name}"` + ); + } + + if (!VALID_BUMPS.has(bumpType)) { + errors.push( + `invalid bump type "${bumpType}", expected one of: major, minor, patch` + ); + } + } + + return errors; +}; + +const main = async () => { + const changedFiles = await getChangedFiles(); + + if (changedFiles.length === 0) { + console.log("No changed changeset files found."); + return; + } + + const failures = []; + for (const filePath of changedFiles) { + const errors = await validate(filePath); + for (const error of errors) { + failures.push(`${filePath}: ${error}`); + } + } + + if (failures.length > 0) { + console.error("Changeset validation failed:"); + for (const failure of failures) { + console.error(`- ${failure}`); + } + process.exitCode = 1; + } +}; + +main(); diff --git a/.changeset/chunk-graph-runtime-requirements-ownership.md b/.changeset/chunk-graph-runtime-requirements-ownership.md new file mode 100644 index 00000000000..1c14d5ffbdc --- /dev/null +++ b/.changeset/chunk-graph-runtime-requirements-ownership.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Avoid copying module runtime requirements when ownership is not transferred. diff --git a/.changeset/cjs-define-property-reexport.md b/.changeset/cjs-define-property-reexport.md new file mode 100644 index 00000000000..c5527d1b9f2 --- /dev/null +++ b/.changeset/cjs-define-property-reexport.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Support CommonJS reexports via `Object.defineProperty` value and getter descriptors. diff --git a/.changeset/cjs-this-in-exported-function.md b/.changeset/cjs-this-in-exported-function.md new file mode 100644 index 00000000000..df01519bff0 --- /dev/null +++ b/.changeset/cjs-this-in-exported-function.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Keep all CommonJS exports when an exported function accesses them via `this`. diff --git a/.changeset/cli-conflict-origin-path.md b/.changeset/cli-conflict-origin-path.md new file mode 100644 index 00000000000..ffe40a667ee --- /dev/null +++ b/.changeset/cli-conflict-origin-path.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Include the schema origin path in conflicting-schema CLI argument errors. diff --git a/.changeset/cli-const-support.md b/.changeset/cli-const-support.md new file mode 100644 index 00000000000..f45deaa4c43 --- /dev/null +++ b/.changeset/cli-const-support.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Support JSON Schema `const` when generating CLI flags from a schema. diff --git a/.changeset/cli-if-then-else.md b/.changeset/cli-if-then-else.md new file mode 100644 index 00000000000..51898a9fabd --- /dev/null +++ b/.changeset/cli-if-then-else.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Support JSON Schema `if`/`then`/`else` when generating CLI flags from a schema. diff --git a/.changeset/cli-prototype-pollution.md b/.changeset/cli-prototype-pollution.md new file mode 100644 index 00000000000..65e1803bc4b --- /dev/null +++ b/.changeset/cli-prototype-pollution.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Reject `__proto__`, `constructor` and `prototype` path segments in `cli.processArguments` to prevent prototype pollution. diff --git a/.changeset/compilation-asset-chunk-reverse-index.md b/.changeset/compilation-asset-chunk-reverse-index.md new file mode 100644 index 00000000000..d2ad9c52d3f --- /dev/null +++ b/.changeset/compilation-asset-chunk-reverse-index.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Speed up `Compilation.deleteAsset` and `Compilation.renameAsset` via a lazy reverse index from asset file name to containing chunks. diff --git a/.changeset/concat-top-level-declarations.md b/.changeset/concat-top-level-declarations.md new file mode 100644 index 00000000000..95c980d3d36 --- /dev/null +++ b/.changeset/concat-top-level-declarations.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Fix merging of inner modules' top-level declarations in concatenated modules. diff --git a/.changeset/concatenate-hash-micro-opts.md b/.changeset/concatenate-hash-micro-opts.md new file mode 100644 index 00000000000..f04bd1da2ec --- /dev/null +++ b/.changeset/concatenate-hash-micro-opts.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Reduce allocations in export hashing and concatenation name lookups. diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 00000000000..5fcacb00918 --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.1.2/schema.json", + "changelog": ["./changelog-generator.mjs", { "repo": "webpack/webpack" }], + "fixed": [], + "linked": [], + "access": "public", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": [] +} diff --git a/.changeset/cross-module-dead-branch-skipping.md b/.changeset/cross-module-dead-branch-skipping.md new file mode 100644 index 00000000000..356f11abdb6 --- /dev/null +++ b/.changeset/cross-module-dead-branch-skipping.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Skip import specifiers, `require()` and `import()` calls in dead conditional branches gated by inlined imported constants (`isDEV ? A : B`), evaluated via `getCondition`. diff --git a/.changeset/css-ascii-case-insensitive-keyword-match.md b/.changeset/css-ascii-case-insensitive-keyword-match.md new file mode 100644 index 00000000000..ac2f9a98613 --- /dev/null +++ b/.changeset/css-ascii-case-insensitive-keyword-match.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Avoid toLowerCase allocations in CSS keyword comparisons. diff --git a/.changeset/css-escape-identifier-table.md b/.changeset/css-escape-identifier-table.md new file mode 100644 index 00000000000..26a54466440 --- /dev/null +++ b/.changeset/css-escape-identifier-table.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Speed up CSS identifier escaping with a char-class lookup table. diff --git a/.changeset/css-export-type-fullhash-publicpath.md b/.changeset/css-export-type-fullhash-publicpath.md new file mode 100644 index 00000000000..60965917f3d --- /dev/null +++ b/.changeset/css-export-type-fullhash-publicpath.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Resolve `[fullhash]` in `url()` public paths for inlined CSS export types (`style`/`text`/`css-style-sheet`) at runtime. diff --git a/.changeset/css-exports-sourcemap-linear-scan.md b/.changeset/css-exports-sourcemap-linear-scan.md new file mode 100644 index 00000000000..a44f8df4ed7 --- /dev/null +++ b/.changeset/css-exports-sourcemap-linear-scan.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Avoid quadratic line scan when building CSS module exports source maps. diff --git a/.changeset/css-lazy-comment-loc.md b/.changeset/css-lazy-comment-loc.md new file mode 100644 index 00000000000..0a15a3eeda0 --- /dev/null +++ b/.changeset/css-lazy-comment-loc.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Compute CSS comment source locations lazily. diff --git a/.changeset/css-parser-as-block-contents.md b/.changeset/css-parser-as-block-contents.md new file mode 100644 index 00000000000..6218d470a8a --- /dev/null +++ b/.changeset/css-parser-as-block-contents.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Add CSS parser `as` option and resolve `url()` inside HTML `style` attributes. diff --git a/.changeset/css-parser-decomposition.md b/.changeset/css-parser-decomposition.md new file mode 100644 index 00000000000..16999a31c9b --- /dev/null +++ b/.changeset/css-parser-decomposition.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Resolve full CSS escapes (including hex) in CSS-Modules names, so e.g. `\75 rl()` matches `url()`. diff --git a/.changeset/css-parser-hoist-regex-release-comments.md b/.changeset/css-parser-hoist-regex-release-comments.md new file mode 100644 index 00000000000..e69988acc51 --- /dev/null +++ b/.changeset/css-parser-hoist-regex-release-comments.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Reduce CSS parser CPU (hoisted per-call regexes, byte-compared `@container` pure-mode keywords) and stop retaining parsed comments on the reused parser instance between modules. diff --git a/.changeset/css-perf.md b/.changeset/css-perf.md new file mode 100644 index 00000000000..4df0baea841 --- /dev/null +++ b/.changeset/css-perf.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Reduce CSS build time and memory usage. Per-export CSS dependencies are consolidated into one dependency per module, and hot-path allocations and lookups in CSS code generation and the module-graph cache are trimmed. diff --git a/.changeset/css-public-path-placeholder-plan.md b/.changeset/css-public-path-placeholder-plan.md new file mode 100644 index 00000000000..ff4b661538a --- /dev/null +++ b/.changeset/css-public-path-placeholder-plan.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Cache CSS public-path placeholder offsets per module source to avoid re-materializing and re-scanning the source on every render. diff --git a/.changeset/css-tokenizer-eof-fixes.md b/.changeset/css-tokenizer-eof-fixes.md new file mode 100644 index 00000000000..3124ff831dd --- /dev/null +++ b/.changeset/css-tokenizer-eof-fixes.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Fix CSS tokenizer infinite loops and dropped tokens on malformed input. diff --git a/.changeset/css-unescape-identifier-bulk-flush.md b/.changeset/css-unescape-identifier-bulk-flush.md new file mode 100644 index 00000000000..9ebcfbd9e3a --- /dev/null +++ b/.changeset/css-unescape-identifier-bulk-flush.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Speed up CSS identifier unescaping with bulk run flushing. diff --git a/.changeset/dedicated-module-classes.md b/.changeset/dedicated-module-classes.md new file mode 100644 index 00000000000..8f1257aa131 --- /dev/null +++ b/.changeset/dedicated-module-classes.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Add dedicated module classes for all built-in module types. diff --git a/.changeset/dedup-context-symlink-walk.md b/.changeset/dedup-context-symlink-walk.md new file mode 100644 index 00000000000..b41eeccb35c --- /dev/null +++ b/.changeset/dedup-context-symlink-walk.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Skip already-visited symlink targets when resolving context hashes so cyclic symlink graphs no longer overflow the queue. diff --git a/.changeset/default-entry-html-css.md b/.changeset/default-entry-html-css.md new file mode 100644 index 00000000000..853fe604a8f --- /dev/null +++ b/.changeset/default-entry-html-css.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Support `.html`/`.css` for the default `./src` entry under the html/css experiments. diff --git a/.changeset/define-config-helper.md b/.changeset/define-config-helper.md new file mode 100644 index 00000000000..ecc55ff2ccb --- /dev/null +++ b/.changeset/define-config-helper.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Add `defineConfig` helper for typed configuration files. diff --git a/.changeset/define-plugin-undefined-member.md b/.changeset/define-plugin-undefined-member.md new file mode 100644 index 00000000000..d9a570d929e --- /dev/null +++ b/.changeset/define-plugin-undefined-member.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Resolve `DefinePlugin` access to an undefined object member as `undefined`. diff --git a/.changeset/deno-target.md b/.changeset/deno-target.md new file mode 100644 index 00000000000..c036ec4e052 --- /dev/null +++ b/.changeset/deno-target.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Add a `deno` target (with versions, e.g. `deno`, `deno2`, `deno1.40`) that emits ESM, resolves node.js built-ins via the required `node:` specifier, and keeps Deno's own import protocols (`npm:`, `jsr:`, `node:`, `http(s)://`) external. diff --git a/.changeset/dependency-loc-lazy-sort.md b/.changeset/dependency-loc-lazy-sort.md new file mode 100644 index 00000000000..881e286610f --- /dev/null +++ b/.changeset/dependency-loc-lazy-sort.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Avoid materializing dependency source locations when sorting, keeping them lazy to reduce build time and memory. diff --git a/.changeset/deserialize-buffer-check.md b/.changeset/deserialize-buffer-check.md new file mode 100644 index 00000000000..98ce60dbc17 --- /dev/null +++ b/.changeset/deserialize-buffer-check.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Speed up serialization deserialize by replacing a Buffer.isBuffer call with a typeof check. diff --git a/.changeset/electron-externals-universal-target.md b/.changeset/electron-externals-universal-target.md new file mode 100644 index 00000000000..6afd98ad14b --- /dev/null +++ b/.changeset/electron-externals-universal-target.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Use `module-import` for electron externals when the target supports ESM. diff --git a/.changeset/emit-absolute-asset-path.md b/.changeset/emit-absolute-asset-path.md new file mode 100644 index 00000000000..ada86ba23e6 --- /dev/null +++ b/.changeset/emit-absolute-asset-path.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Emit assets with absolute target paths as-is to avoid invalid Windows paths. diff --git a/.changeset/env-code-reduction.md b/.changeset/env-code-reduction.md new file mode 100644 index 00000000000..42a3ce6f641 --- /dev/null +++ b/.changeset/env-code-reduction.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Add `output.environment.spread`, `output.environment.hasOwn`, and `output.environment.symbol`, and use method shorthand, spread, `Object.hasOwn`, and an unguarded `Symbol` in generated runtime code where the environment supports it. diff --git a/.changeset/environment-logical-assignment.md b/.changeset/environment-logical-assignment.md new file mode 100644 index 00000000000..26f3b4a6a57 --- /dev/null +++ b/.changeset/environment-logical-assignment.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Add `output.environment.logicalAssignment` to emit `||=` in runtime code when the target supports logical assignment operators. diff --git a/.changeset/esm-hmr-drop-loadscript.md b/.changeset/esm-hmr-drop-loadscript.md new file mode 100644 index 00000000000..95cab9a46ea --- /dev/null +++ b/.changeset/esm-hmr-drop-loadscript.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Drop the unused loadScript runtime from ESM hot-update bundles. diff --git a/.changeset/export-default-value-binding.md b/.changeset/export-default-value-binding.md new file mode 100644 index 00000000000..9e453d12fbe --- /dev/null +++ b/.changeset/export-default-value-binding.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Extend value binding optimization to export default expressions. diff --git a/.changeset/export-info-inline-flags.md b/.changeset/export-info-inline-flags.md new file mode 100644 index 00000000000..291ec83d561 --- /dev/null +++ b/.changeset/export-info-inline-flags.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Reduce ExportInfo memory and cache size for inline-exports metadata. diff --git a/.changeset/exports-info-used-name-walk.md b/.changeset/exports-info-used-name-walk.md new file mode 100644 index 00000000000..6deaa01da53 --- /dev/null +++ b/.changeset/exports-info-used-name-walk.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Resolve nested exports info paths iteratively to cut per-level array allocations. diff --git a/.changeset/extend-incremental-cache-source-types.md b/.changeset/extend-incremental-cache-source-types.md new file mode 100644 index 00000000000..d442f243b74 --- /dev/null +++ b/.changeset/extend-incremental-cache-source-types.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Fix stale incremental cache for css, html and asset/source/inline modules. diff --git a/.changeset/fix-cjs-deferred-require-tree-shaking.md b/.changeset/fix-cjs-deferred-require-tree-shaking.md new file mode 100644 index 00000000000..b2edf982fb3 --- /dev/null +++ b/.changeset/fix-cjs-deferred-require-tree-shaking.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +CommonJS tree-shaking no longer drops exports accessed before a deferred require binding. diff --git a/.changeset/fix-css-lazy-asset-incremental-cache.md b/.changeset/fix-css-lazy-asset-incremental-cache.md new file mode 100644 index 00000000000..100149807a6 --- /dev/null +++ b/.changeset/fix-css-lazy-asset-incremental-cache.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Make CSS-referenced asset available in lazy JS chunk during incremental rebuilds. diff --git a/.changeset/fix-specifier-parsing.md b/.changeset/fix-specifier-parsing.md new file mode 100644 index 00000000000..9144e999f6e --- /dev/null +++ b/.changeset/fix-specifier-parsing.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Correct string/template import specifier parsing for filesystem cache build dependencies and fix module-sharing hostname validation. diff --git a/.changeset/good-flies-say.md b/.changeset/good-flies-say.md new file mode 100644 index 00000000000..a70bbc90f2a --- /dev/null +++ b/.changeset/good-flies-say.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +perf: guard isDeferred() behind experiments.deferImport in ConcatenatedModule diff --git a/.changeset/grouping-comparator-hot-path.md b/.changeset/grouping-comparator-hot-path.md new file mode 100644 index 00000000000..2e81ef08cba --- /dev/null +++ b/.changeset/grouping-comparator-hot-path.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Speed up deterministicGrouping and cached comparators on large builds. diff --git a/.changeset/harmony-commonjs-dependency-allocations.md b/.changeset/harmony-commonjs-dependency-allocations.md new file mode 100644 index 00000000000..de7dea19290 --- /dev/null +++ b/.changeset/harmony-commonjs-dependency-allocations.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Reduce allocations on harmony/commonjs dependency hot paths. diff --git a/.changeset/hmr-force-load-migrated-chunk.md b/.changeset/hmr-force-load-migrated-chunk.md new file mode 100644 index 00000000000..63cbc0f2412 --- /dev/null +++ b/.changeset/hmr-force-load-migrated-chunk.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Force-load a module's new owning chunk during HMR when its only loaded chunk is removed from a runtime, so it keeps receiving updates. diff --git a/.changeset/html-module-hmr.md b/.changeset/html-module-hmr.md new file mode 100644 index 00000000000..1dc68d48091 --- /dev/null +++ b/.changeset/html-module-hmr.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Add HMR support for HTML modules with body/title DOM patching on update. diff --git a/.changeset/html-parser-coverage-and-entities.md b/.changeset/html-parser-coverage-and-entities.md new file mode 100644 index 00000000000..3c066d43c09 --- /dev/null +++ b/.changeset/html-parser-coverage-and-entities.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Expand HTML parser tag/attribute coverage and decode character references. diff --git a/.changeset/html-parser-css-url-type.md b/.changeset/html-parser-css-url-type.md new file mode 100644 index 00000000000..d12eb936b0e --- /dev/null +++ b/.changeset/html-parser-css-url-type.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Add `css-url` html source type extracting `url()` references from CSS-valued attributes. diff --git a/.changeset/html-parser-end-tag-allocations.md b/.changeset/html-parser-end-tag-allocations.md new file mode 100644 index 00000000000..bd4ec86f29b --- /dev/null +++ b/.changeset/html-parser-end-tag-allocations.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Speed up and reduce allocations in the experimental HTML parser's tokenizer, tree builder, and entity decoder. diff --git a/.changeset/html-parser-perf.md b/.changeset/html-parser-perf.md new file mode 100644 index 00000000000..21985e5acde --- /dev/null +++ b/.changeset/html-parser-perf.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Speed up the experimental HTML parser and reduce its memory usage. diff --git a/.changeset/html-parser-sources-option.md b/.changeset/html-parser-sources-option.md new file mode 100644 index 00000000000..43b0a997698 --- /dev/null +++ b/.changeset/html-parser-sources-option.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Add `module.parser.html.sources` option to disable or customize URL-attribute extraction for HTML modules, with `script` / `script-module` / `stylesheet` / `stylesheet-inline` types for custom attributes diff --git a/.changeset/html-parser-template-option.md b/.changeset/html-parser-template-option.md new file mode 100644 index 00000000000..b01ddd81088 --- /dev/null +++ b/.changeset/html-parser-template-option.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Add `module.parser.html.template` option to transform HTML module source before parsing. diff --git a/.changeset/html-render-dedup.md b/.changeset/html-render-dedup.md new file mode 100644 index 00000000000..ae40f1b6803 --- /dev/null +++ b/.changeset/html-render-dedup.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Avoid redundant HTML module work: reuse the dependency-template render across the JS and HTML code-generation passes, and memoize sentinel resolution/content hashing per source. diff --git a/.changeset/html-svg-and-legacy-background-sources.md b/.changeset/html-svg-and-legacy-background-sources.md new file mode 100644 index 00000000000..839e9cdee1b --- /dev/null +++ b/.changeset/html-svg-and-legacy-background-sources.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Extract more source URLs in HTML modules (SVG, legacy and obsolete attributes). diff --git a/.changeset/inline-exports.md b/.changeset/inline-exports.md new file mode 100644 index 00000000000..2b1eac841d6 --- /dev/null +++ b/.changeset/inline-exports.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Support `optimization.inlineExports` for better tree-shaking. diff --git a/.changeset/inner-graph-cross-module-pure.md b/.changeset/inner-graph-cross-module-pure.md new file mode 100644 index 00000000000..99f96484138 --- /dev/null +++ b/.changeset/inner-graph-cross-module-pure.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Allow tree-shaking unused calls to `/*#__NO_SIDE_EFFECTS__*/`-annotated (pure) exports across module boundaries. diff --git a/.changeset/inner-graph-release-and-inline-fast-paths.md b/.changeset/inner-graph-release-and-inline-fast-paths.md new file mode 100644 index 00000000000..30d1feb7664 --- /dev/null +++ b/.changeset/inner-graph-release-and-inline-fast-paths.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Release inner-graph state after use and speed up inlined-export checks. diff --git a/.changeset/javascript-parser-walk-allocations.md b/.changeset/javascript-parser-walk-allocations.md new file mode 100644 index 00000000000..50f7f3f1395 --- /dev/null +++ b/.changeset/javascript-parser-walk-allocations.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Reduce JavascriptParser allocations on the walk hot path to speed up parsing and lower memory usage. diff --git a/.changeset/lazy-barrel-overhead.md b/.changeset/lazy-barrel-overhead.md new file mode 100644 index 00000000000..30d99333c24 --- /dev/null +++ b/.changeset/lazy-barrel-overhead.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Reduce CPU and memory overhead of the lazy barrel optimization. diff --git a/.changeset/lazy-barrel.md b/.changeset/lazy-barrel.md new file mode 100644 index 00000000000..77a2ad56634 --- /dev/null +++ b/.changeset/lazy-barrel.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Defer building unused re-export targets of side-effect-free barrel modules. diff --git a/.changeset/module-build-error-message-jsc.md b/.changeset/module-build-error-message-jsc.md new file mode 100644 index 00000000000..c6a1b670f8d --- /dev/null +++ b/.changeset/module-build-error-message-jsc.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Keep the error message in module build errors on engines whose `Error.stack` omits it. diff --git a/.changeset/module-concatenation-runtime-cache.md b/.changeset/module-concatenation-runtime-cache.md new file mode 100644 index 00000000000..1de7fcf34be --- /dev/null +++ b/.changeset/module-concatenation-runtime-cache.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Speed up module concatenation by caching repeated per-module computations. diff --git a/.changeset/move-hot-flag-to-normal-module.md b/.changeset/move-hot-flag-to-normal-module.md new file mode 100644 index 00000000000..04c0b42cdf3 --- /dev/null +++ b/.changeset/move-hot-flag-to-normal-module.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Move the `hot` flag from `Module` to `NormalModule`, where it's actually read and written. diff --git a/.changeset/move-weak-flag-to-module-dependency.md b/.changeset/move-weak-flag-to-module-dependency.md new file mode 100644 index 00000000000..64ef92d974b --- /dev/null +++ b/.changeset/move-weak-flag-to-module-dependency.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Move the `weak` flag from `Dependency` to `ModuleDependency`, where it's actually set. diff --git a/.changeset/multiple-inlined-entries-no-iife.md b/.changeset/multiple-inlined-entries-no-iife.md new file mode 100644 index 00000000000..d248664a450 --- /dev/null +++ b/.changeset/multiple-inlined-entries-no-iife.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Avoid the entry IIFE for multiple inlined entry modules by renaming collisions. diff --git a/.changeset/new-import-call-member-access.md b/.changeset/new-import-call-member-access.md new file mode 100644 index 00000000000..578c38ea803 --- /dev/null +++ b/.changeset/new-import-call-member-access.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Reject `new import.defer(...)`/`new import.source(...)` with member access as a SyntaxError. diff --git a/.changeset/nice-peaches-smell.md b/.changeset/nice-peaches-smell.md new file mode 100644 index 00000000000..a3c738717ed --- /dev/null +++ b/.changeset/nice-peaches-smell.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Avoid `ProvidePlugin` injection for local CommonJS require bindings that use the same variable name. diff --git a/.changeset/node-global-worker.md b/.changeset/node-global-worker.md new file mode 100644 index 00000000000..71654f7a085 --- /dev/null +++ b/.changeset/node-global-worker.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Resolve the global `new Worker(new URL(...))` to `worker_threads` on the `node` target. diff --git a/.changeset/optional-chaining-runtime.md b/.changeset/optional-chaining-runtime.md new file mode 100644 index 00000000000..ac614378feb --- /dev/null +++ b/.changeset/optional-chaining-runtime.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Use optional chaining in generated runtime code where the environment supports it. diff --git a/.changeset/output-environment-let.md b/.changeset/output-environment-let.md new file mode 100644 index 00000000000..de7264fcc64 --- /dev/null +++ b/.changeset/output-environment-let.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Add `output.environment.let` option (paired with target's `let` capability) and emit `let`/`const` instead of `var` in generated runtime code wherever it is safe. Bindings that may be wrapped in runtime-condition `if` blocks (harmony imports, ConcatenatedModule external imports) continue to use `var` to preserve function scoping. diff --git a/.changeset/output-html-entrypoints.md b/.changeset/output-html-entrypoints.md new file mode 100644 index 00000000000..8f39918c68f --- /dev/null +++ b/.changeset/output-html-entrypoints.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Add `output.html` to emit an HTML file per entrypoint, injecting its JS/CSS chunks (including `dependOn` shared chunks). diff --git a/.changeset/output-path-root-eisdir.md b/.changeset/output-path-root-eisdir.md new file mode 100644 index 00000000000..ce9d085e5cc --- /dev/null +++ b/.changeset/output-path-root-eisdir.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Allow output.path to be the filesystem root by treating EISDIR like EEXIST in mkdirp. diff --git a/.changeset/parser-pure-functions.md b/.changeset/parser-pure-functions.md new file mode 100644 index 00000000000..faf1b6d133d --- /dev/null +++ b/.changeset/parser-pure-functions.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Add `module.parser.javascript.pureFunctions` to mark top-level names as side-effect-free for tree shaking. diff --git a/.changeset/platform-universal.md b/.changeset/platform-universal.md new file mode 100644 index 00000000000..3a0a2df19c3 --- /dev/null +++ b/.changeset/platform-universal.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Add `universal` to `compiler.platform`, true for universal targets (`"universal"` or `["web", "node"]`). diff --git a/.changeset/reduce-dependency-memory.md b/.changeset/reduce-dependency-memory.md new file mode 100644 index 00000000000..2f8356c0498 --- /dev/null +++ b/.changeset/reduce-dependency-memory.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Reduce memory by not retaining the source location object on every dependency. diff --git a/.changeset/reexport-require-binding.md b/.changeset/reexport-require-binding.md new file mode 100644 index 00000000000..3628d8816fd --- /dev/null +++ b/.changeset/reexport-require-binding.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Keep the full exports object when a `require()` binding is re-exported. diff --git a/.changeset/remove-glob-to-regexp.md b/.changeset/remove-glob-to-regexp.md new file mode 100644 index 00000000000..0b04acda15a --- /dev/null +++ b/.changeset/remove-glob-to-regexp.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Replace glob-to-regexp dependency with watchpack's globToRegExp utility. diff --git a/.changeset/serialize-shrink-cache.md b/.changeset/serialize-shrink-cache.md new file mode 100644 index 00000000000..9238cde2319 --- /dev/null +++ b/.changeset/serialize-shrink-cache.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Shrink the persistent cache: add a NULL_AND_I16 binary tier and inline tiny strings instead of larger far back-references. diff --git a/.changeset/serializer-positional-tuple-typing.md b/.changeset/serializer-positional-tuple-typing.md new file mode 100644 index 00000000000..e46400ba735 --- /dev/null +++ b/.changeset/serializer-positional-tuple-typing.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Type serializer read/write contexts with positional tuples and fix a ProvideSharedDependency version/request swap. diff --git a/.changeset/share-block-modules-across-runtimes.md b/.changeset/share-block-modules-across-runtimes.md new file mode 100644 index 00000000000..87c35e5d70b --- /dev/null +++ b/.changeset/share-block-modules-across-runtimes.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Speed up buildChunkGraph by deriving block modules from the first runtime. diff --git a/.changeset/side-effects-cache-reexport-resolution.md b/.changeset/side-effects-cache-reexport-resolution.md new file mode 100644 index 00000000000..f1559b2c2be --- /dev/null +++ b/.changeset/side-effects-cache-reexport-resolution.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Cache re-export target resolution in SideEffectsFlagPlugin for faster builds. diff --git a/.changeset/side-effects-star-reexport-passthrough.md b/.changeset/side-effects-star-reexport-passthrough.md new file mode 100644 index 00000000000..fd816727ce8 --- /dev/null +++ b/.changeset/side-effects-star-reexport-passthrough.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Skip pure single-star passthrough modules for `export *` re-exports. diff --git a/.changeset/skip-dependency-report-unchanged-modules.md b/.changeset/skip-dependency-report-unchanged-modules.md new file mode 100644 index 00000000000..5dca0b8cfaa --- /dev/null +++ b/.changeset/skip-dependency-report-unchanged-modules.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Skip dependency error/warning reporting for unchanged modules on rebuilds. diff --git a/.changeset/static-export-bindings.md b/.changeset/static-export-bindings.md new file mode 100644 index 00000000000..d79a23a612b --- /dev/null +++ b/.changeset/static-export-bindings.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Use value descriptors instead of getters for const export bindings. diff --git a/.changeset/strict-module-resolution.md b/.changeset/strict-module-resolution.md new file mode 100644 index 00000000000..42feb4aff7e --- /dev/null +++ b/.changeset/strict-module-resolution.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Add `output.strictModuleResolution` to gate the runtime `MODULE_NOT_FOUND` guard. diff --git a/.changeset/uniquename-placeholder.md b/.changeset/uniquename-placeholder.md new file mode 100644 index 00000000000..7cb8978f819 --- /dev/null +++ b/.changeset/uniquename-placeholder.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Support `[uniqueName]` and its `[uniquename]` alias in template paths. diff --git a/.changeset/universal-css-hmr-node.md b/.changeset/universal-css-hmr-node.md new file mode 100644 index 00000000000..2059e037d54 --- /dev/null +++ b/.changeset/universal-css-hmr-node.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Apply CSS hot updates on the Node side of a universal target. diff --git a/.changeset/universal-css-node.md b/.changeset/universal-css-node.md new file mode 100644 index 00000000000..eb0e3115c44 --- /dev/null +++ b/.changeset/universal-css-node.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Support CSS in Node for universal targets, collecting styles for SSR. diff --git a/.changeset/universal-css-style-inject-guard.md b/.changeset/universal-css-style-inject-guard.md new file mode 100644 index 00000000000..c171ef85300 --- /dev/null +++ b/.changeset/universal-css-style-inject-guard.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Guard CSS `style` export-type injection so it no-ops when there is no `document`. diff --git a/.changeset/universal-external-loading.md b/.changeset/universal-external-loading.md new file mode 100644 index 00000000000..623140e925a --- /dev/null +++ b/.changeset/universal-external-loading.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Improve commonjs, node-commonjs and global externals for universal targets. diff --git a/.changeset/universal-target-preset.md b/.changeset/universal-target-preset.md new file mode 100644 index 00000000000..1e8680bc99b --- /dev/null +++ b/.changeset/universal-target-preset.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Add a `universal` target preset (browser + web worker + Node.js + Electron + NW.js) that always outputs ECMAScript modules. diff --git a/.changeset/universal-worker-instantiation.md b/.changeset/universal-worker-instantiation.md new file mode 100644 index 00000000000..c57b8a5ff33 --- /dev/null +++ b/.changeset/universal-worker-instantiation.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Support `new Worker(new URL(...))` in universal (node + web) targets by resolving the Worker constructor from `worker_threads` when no global `Worker` exists. diff --git a/.changeset/warm-pots-cheer.md b/.changeset/warm-pots-cheer.md new file mode 100644 index 00000000000..b6399ed7c08 --- /dev/null +++ b/.changeset/warm-pots-cheer.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Avoid building warning stats objects when counting warnings without a filter. diff --git a/.changeset/windows-absolute-path-forward-slash.md b/.changeset/windows-absolute-path-forward-slash.md new file mode 100644 index 00000000000..85c758f4781 --- /dev/null +++ b/.changeset/windows-absolute-path-forward-slash.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Recognize forward-slash Windows absolute paths (e.g. C:/dir) consistently. diff --git a/.changeset/worker-chunk-filename.md b/.changeset/worker-chunk-filename.md new file mode 100644 index 00000000000..739817ffaf3 --- /dev/null +++ b/.changeset/worker-chunk-filename.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Add `output.workerChunkFilename` and `entry.worker` for worker chunk filenames. diff --git a/.editorconfig b/.editorconfig index 45137161298..d725dd33227 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,16 +8,27 @@ trim_trailing_whitespace = true insert_final_newline = true max_line_length = 80 -[.prettierrc] -indent_style = space -indent_size = 2 - [*.{yml,yaml,json}] indent_style = space indent_size = 2 +[*.md] +trim_trailing_whitespace = false + +[*.snap] +trim_trailing_whitespace = false + [test/cases/parsing/bom/bomfile.{css,js}] charset = utf-8-bom -[*.md] -trim_trailing_whitespace = false +[test/configCases/asset-modules/bytes/file.text] +insert_final_newline = false + +[test/configCases/asset-modules/bytes/file.svg] +insert_final_newline = false + +[test/configCases/asset-modules/concatenation/file.text] +insert_final_newline = false + +[test/configCases/css/no-extra-runtime-in-js/source.text] +insert_final_newline = false diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index a70d08eb0fb..00000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,75 +0,0 @@ -module.exports = { - root: true, - plugins: ["prettier", "node", "jest"], - extends: ["eslint:recommended", "plugin:node/recommended", "plugin:prettier/recommended"], - env: { - node: true, - es6: true - }, - parserOptions: { - ecmaVersion: 2017 - }, - rules: { - "prettier/prettier": "error", - "no-undef": "error", - "no-extra-semi": "error", - "no-template-curly-in-string": "error", - "no-caller": "error", - "no-control-regex": "off", - "yoda": "error", - "eqeqeq": "error", - "global-require": "off", - "brace-style": "error", - "eol-last": "error", - "no-extra-bind": "warn", - "no-process-exit": "warn", - "no-use-before-define": "off", - "no-unused-vars": ["error", { args: "none" }], - "no-unsafe-negation": "error", - "no-loop-func": "warn", - "indent": "off", - "no-console": "off", - "valid-jsdoc": ["error", { - "prefer": { - "return": "returns", - "memberof": "DONTUSE", - "class": "DONTUSE", - "inheritdoc": "DONTUSE", - "description": "DONTUSE", - "readonly": "DONTUSE" - }, - "preferType": { - "*": "any" - }, - "requireReturnType": true - }], - "node/no-unsupported-features": "error", - "node/no-deprecated-api": "error", - "node/no-missing-import": "error", - "node/no-missing-require": ["error", { allowModules: ["webpack"] }], - "node/no-unpublished-bin": "error", - "node/no-unpublished-require": "error", - "node/process-exit-as-throw": "error" - }, - overrides: [ - { - files: ["lib/**/*.runtime.js", "buildin/*.js", "hot/*.js"], - env: { - es6: false, - browser: true - }, - globals: { - Promise: false, - }, - parserOptions: { - ecmaVersion: 5 - } - }, - { - files: ["test/**/*.js"], - env: { - "jest/globals": true - } - } - ] -}; diff --git a/.gitattributes b/.gitattributes index ac579eb7bc0..0336cb2fb13 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,25 @@ * text=auto -test/statsCases/* eol=lf +test/statsCases/** eol=lf +test/hotCases/** eol=lf +test/configCases/css/public-path/*.js eol=lf +test/configCases/defer-import/** eol=lf +test/configCases/html/sources/** eol=lf +test/configCases/html/link/** eol=lf +test/configCases/html/modulepreload-esm/** eol=lf +test/configCases/html/inline-script/** eol=lf +test/configCases/html/inline-script-classic/** eol=lf +test/configCases/html/script-src/** eol=lf +test/configCases/html/script-src-classic/** eol=lf +test/configCases/html/script-src-mixed/** eol=lf +test/configCases/html/style-tag/** eol=lf +test/configCases/html/style-tag-context/** eol=lf +test/configCases/html/style-tag-no-css/** eol=lf +test/configCases/html/svg-paint-server-references/** eol=lf +test/configCases/html/svg-presentation-url/** eol=lf +test/configCases/html/legacy-background/** eol=lf +test/configCases/html/legacy-and-obsolete-sources/** eol=lf examples/* eol=lf -bin/* eol=lf \ No newline at end of file +bin/* eol=lf +*.svg eol=lf +*.css eol=lf +**/*webpack.lock.data/** -text diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 1deb4adab1a..00000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,22 +0,0 @@ - - - - -**Do you want to request a *feature* or report a *bug*?** - - - - -**What is the current behavior?** - -**If the current behavior is a bug, please provide the steps to reproduce.** - - - - - -**What is the expected behavior?** - -**If this is a feature request, what is motivation or use case for changing the behavior?** - -**Please mention other relevant information such as the browser version, Node.js version, webpack version, and Operating System.** diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md deleted file mode 100644 index 657747d6289..00000000000 --- a/.github/ISSUE_TEMPLATE/Bug_report.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve ---- - - - - -# Bug report - - - - - - -**What is the current behavior?** - - -**If the current behavior is a bug, please provide the steps to reproduce.** - - - - - - - - - -**What is the expected behavior?** - - - - - -**Other relevant information:** -webpack version: -Node.js version: -Operating System: -Additional tools: diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md deleted file mode 100644 index e23f20d97cf..00000000000 --- a/.github/ISSUE_TEMPLATE/Feature_request.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project - ---- - - - -## Feature request - - - - - - - -**What is the expected behavior?** - - -**What is motivation or use case for adding/changing the behavior?** - - -**How should this be implemented in your opinion?** - - -**Are you willing to work on this yourself?** -yes diff --git a/.github/ISSUE_TEMPLATE/Other.md b/.github/ISSUE_TEMPLATE/Other.md deleted file mode 100644 index 5e5b62bd605..00000000000 --- a/.github/ISSUE_TEMPLATE/Other.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -name: Other -about: Something else - ---- - - - - diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 8967c8f0169..00000000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - -**What kind of change does this PR introduce?** - - - -**Did you add tests for your changes?** - - - -**Does this PR introduce a breaking change?** - - - -**What needs to be documented once your changes are merged?** - - - diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000000..93d3d2018ae --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,47 @@ +version: 2 +updates: + - package-ecosystem: npm + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 20 + labels: + - dependencies + versioning-strategy: widen + groups: + dependencies: + patterns: + - "*" + update-types: + - "minor" + - "patch" + dependencies-major: + patterns: + - "*" + update-types: + - "major" + exclude-patterns: + - "eslint-scope" + - "rimraf" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 20 + labels: + - dependencies + groups: + dependencies: + patterns: + - "*" + - package-ecosystem: "gitsubmodule" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 20 + labels: + - dependencies + groups: + dependencies: + patterns: + - "*" diff --git a/.github/scripts/publish-to-pkg-pr-new.mjs b/.github/scripts/publish-to-pkg-pr-new.mjs new file mode 100644 index 00000000000..f84aae84a6e --- /dev/null +++ b/.github/scripts/publish-to-pkg-pr-new.mjs @@ -0,0 +1,129 @@ +/* eslint-disable no-console */ +/* eslint-disable camelcase */ + +import fs from "fs"; + +/** + * @param {{ github: EXPECTED_ANY, context: EXPECTED_ANY }} params params + */ +export async function run({ github, context }) { + const output = JSON.parse(fs.readFileSync("output.json", "utf8")); + + const sha = + context.eventName === "pull_request" + ? context.payload.pull_request.head.sha + : context.sha; + + const commitUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/commit/${sha}`; + + const botCommentIdentifier = ""; + const body = `${botCommentIdentifier} +This PR is packaged and the instant preview is available (${commitUrl}). + +Install it locally: + +- npm + +\`\`\`shell +npm i -D webpack@${output.packages.map((p) => p.url).join(" ")} +\`\`\` + +- yarn + +\`\`\`shell +yarn add -D webpack@${output.packages.map((p) => p.url).join(" ")} +\`\`\` + +- pnpm + +\`\`\`shell +pnpm add -D webpack@${output.packages.map((p) => p.url).join(" ")} +\`\`\` +`; + + /** + * @param {number=} issueNumber PR number + * @returns {Promise} comments + */ + async function findBotComment(issueNumber) { + if (!issueNumber) return null; + const comments = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber + }); + return comments.data.find( + (comment) => + // Prevent unintentional overwriting + comment.user && + comment.user.login === "github-actions[bot]" && + comment.body.includes(botCommentIdentifier) + ); + } + + /** + * @param {number=} issueNumber issue number + * @returns {Promise} + */ + async function createOrUpdateComment(issueNumber) { + if (!issueNumber) { + console.log("No issue number provided. Cannot post or update comment."); + return; + } + + const existingComment = await findBotComment(issueNumber); + + if (existingComment) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existingComment.id, + body + }); + } else { + await github.rest.issues.createComment({ + issue_number: issueNumber, + owner: context.repo.owner, + repo: context.repo.repo, + body + }); + } + } + + /** + * @returns {void} + */ + function logPublishInfo() { + console.log(`\n${"=".repeat(50)}`); + console.log("Publish Information"); + console.log("=".repeat(50)); + console.log("\nPublished Packages:"); + console.log(output.packages); + console.log("\nTemplates:"); + console.log(output.templates); + console.log(`\nCommit URL: ${commitUrl}`); + console.log(`\n${"=".repeat(50)}`); + } + + if (context.eventName === "pull_request") { + if (context.issue.number) { + await createOrUpdateComment(context.issue.number); + } + } else if (context.eventName === "push") { + const { data: prs } = + await github.rest.repos.listPullRequestsAssociatedWithCommit({ + owner: context.repo.owner, + repo: context.repo.repo, + commit_sha: sha + }); + + if (prs.length > 0) { + await createOrUpdateComment(prs[0].number); + } else { + console.log( + "No open pull request found for this push. Logging publish information to console:" + ); + logPublishInfo(); + } + } +} diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml new file mode 100644 index 00000000000..cbf17930860 --- /dev/null +++ b/.github/workflows/benchmarks.yml @@ -0,0 +1,103 @@ +name: Benchmarks + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + id-token: write # Required for OIDC authentication with CodSpeed + +jobs: + benchmark: + strategy: + fail-fast: false + matrix: + shard: [1/4, 2/4, 3/4, 4/4] + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + fetch-tags: true + fetch-depth: 0 + + - name: Use Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: lts/* + cache: yarn + + - run: yarn --frozen-lockfile + + - run: yarn link --frozen-lockfile || true + + - run: yarn link webpack --frozen-lockfile + + - name: Check Memory Status + run: > + node -e 'const os=require("os"); + const toGb=b=>(b/1024**3).toFixed(2)+" GB"; + console.log(`--- Memory Status ---\nTotal: ${toGb(os.totalmem())}\nFree: ${toGb(os.freemem())}\nUsed: ${toGb(os.totalmem()-os.freemem())}`)' + + - name: Run benchmarks + uses: CodSpeedHQ/action@c145068895e045cc725ee76fcd2307624b65c3af # v4.17.5 + with: + run: yarn benchmark --ci + mode: "simulation" + env: + LAST_COMMIT: 1 + NEGATIVE_FILTER: on-schedule + SHARD: ${{ matrix.shard }} + benchmark-memory: + strategy: + fail-fast: false + matrix: + shard: [1/4, 2/4, 3/4, 4/4] + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + fetch-tags: true + fetch-depth: 0 + + - name: Use Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: lts/* + cache: yarn + + - run: yarn --frozen-lockfile + + - run: yarn link --frozen-lockfile || true + + - run: yarn link webpack --frozen-lockfile + + - name: Check Memory Status + run: > + node -e 'const os=require("os"); + const toGb=b=>(b/1024**3).toFixed(2)+" GB"; + console.log(`--- Memory Status ---\nTotal: ${toGb(os.totalmem())}\nFree: ${toGb(os.freemem())}\nUsed: ${toGb(os.totalmem()-os.freemem())}`)' + + - name: Run memory benchmarks + uses: CodSpeedHQ/action@c145068895e045cc725ee76fcd2307624b65c3af # v4.17.5 + with: + run: yarn benchmark --ci + mode: "memory" + token: ${{ secrets.CODSPEED_TOKEN }} + env: + LAST_COMMIT: 1 + NEGATIVE_FILTER: on-schedule + SHARD: ${{ matrix.shard }} diff --git a/.github/workflows/dependabot.yml b/.github/workflows/dependabot.yml new file mode 100644 index 00000000000..2b7803c8d63 --- /dev/null +++ b/.github/workflows/dependabot.yml @@ -0,0 +1,38 @@ +name: Dependabot + +on: pull_request + +permissions: + contents: write + pull-requests: write + +jobs: + dependabot-auto-merge: + runs-on: ubuntu-latest + if: github.actor == 'dependabot[bot]' + steps: + - name: Generate Token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + id: app-token + with: + app-id: ${{ secrets.BOT_APP_ID }} + private-key: ${{ secrets.BOT_PRIVATE_KEY }} + + - name: Dependabot metadata + id: dependabot-metadata + uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0 + with: + github-token: "${{ steps.app-token.outputs.token }}" + + - name: Enable auto-merge for Dependabot PRs + if: steps.dependabot-metadata.outputs.update-type != 'version-update:semver-major' + run: | + if [ "$(gh pr status --json reviewDecision -q .currentBranch.reviewDecision)" != "APPROVED" ]; + then gh pr review --approve "$PR_URL" + else echo "PR already approved, skipping additional approvals to minimize emails/notification noise."; + fi + + gh pr merge --auto --squash "$PR_URL" + env: + PR_URL: ${{ github.event.pull_request.html_url }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 00000000000..9a67f0703e9 --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,75 @@ +name: Dependency Review + +on: + pull_request: + +permissions: + contents: read + +jobs: + dependency-review: + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Dependency Review + uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 + with: + allow-dependencies-licenses: | + pkg:npm/@cspell/dict-django, + pkg:npm/@cspell/dict-en-common-misspellings, + pkg:npm/flatted, + pkg:npm/parse-imports, + pkg:npm/prettier, + pkg:npm/type-fest, + pkg:npm/abbrev, + pkg:npm/@pkgjs/parseargs, + pkg:npm/@apidevtools/json-schema-ref-parser, + pkg:npm/cookie-signature, + pkg:npm/ansis, + pkg:npm/strtok3 + allow-licenses: | + 0BSD, + AFL-1.1, + AFL-1.2, + AFL-2.0, + AFL-2.1, + AFL-3.0, + AGPL-3.0-only, + AGPL-3.0-or-later, + Apache-1.1, + Apache-2.0, + APSL-2.0, + Artistic-2.0, + BlueOak-1.0.0, + BSD-2-Clause, + BSD-3-Clause-Clear, + BSD-3-Clause, + BSL-1.0, + CAL-1.0, + CC-BY-3.0, + CC-BY-4.0, + CC-BY-SA-4.0, + CDDL-1.0, + CC0-1.0, + EPL-2.0, + GPL-2.0-only, + GPL-2.0-or-later, + GPL-2.0, + GPL-3.0-or-later, + ISC, + LGPL-2.0-only, + LGPL-2.1-only, + LGPL-2.1-or-later, + LGPL-2.1, + LGPL-3.0-only, + LGPL-3.0, + MIT, + MPL-2.0, + OFL-1.1, + PSF-2.0, + Python-2.0, + Python-2.0.1, + Unicode-DFS-2016, + Unlicense diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml new file mode 100644 index 00000000000..5f93422cd50 --- /dev/null +++ b/.github/workflows/examples.yml @@ -0,0 +1,48 @@ +name: Update examples + +on: + workflow_dispatch: + schedule: + - cron: "0 0 * * 0" + +jobs: + examples: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + fetch-tags: true + fetch-depth: 0 + + - name: Use Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: lts/* + cache: yarn + + - run: yarn --frozen-lockfile + + - run: yarn link --frozen-lockfile || true + + - run: yarn link webpack --frozen-lockfile + + - run: yarn build:examples + + - name: Create Pull Request + uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + delete-branch: true + commit-message: | + docs: update examples + title: | + docs: update examples + body: | + Update examples. + + This PR was autogenerated. + branch: update-examples diff --git a/.github/workflows/pr-quality.yml b/.github/workflows/pr-quality.yml new file mode 100644 index 00000000000..5fc6579b8f2 --- /dev/null +++ b/.github/workflows/pr-quality.yml @@ -0,0 +1,14 @@ +name: PR Quality + +permissions: + contents: read + issues: read + pull-requests: write + +on: + pull_request_target: + types: [opened, reopened] + +jobs: + anti-slop: + uses: webpack/.github/.github/workflows/pr-quality.yml@a03552c758d8c244b3cfc2985aff7020469e0473 diff --git a/.github/workflows/publish-to-pkg-pr-new.yml b/.github/workflows/publish-to-pkg-pr-new.yml new file mode 100644 index 00000000000..e9652880910 --- /dev/null +++ b/.github/workflows/publish-to-pkg-pr-new.yml @@ -0,0 +1,41 @@ +name: Publish to pkg.pr.new + +on: + pull_request: + branches: [main] + push: + branches: [main] + tags: ["!**"] + +permissions: + issues: write + pull-requests: write + +jobs: + publish: + if: | + github.repository == 'webpack/webpack' && + (github.event_name != 'pull_request' || + github.event.pull_request.head.repo.full_name == github.repository) + name: Publish to pkg.pr.new + runs-on: ubuntu-latest + outputs: + sha: ${{ steps.publish.outputs.sha }} + urls: ${{ steps.publish.outputs.urls }} + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - run: corepack enable + - name: Use Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: lts/* + cache: yarn + - run: yarn --frozen-lockfile + - run: npx pkg-pr-new publish --compact --json output.json --comment=off + - name: Add metadata to output + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { run } = await import('${{ github.workspace }}/.github/scripts/publish-to-pkg-pr-new.mjs'); + await run({ github, context }); diff --git a/.github/workflows/release-announcement.yml b/.github/workflows/release-announcement.yml new file mode 100644 index 00000000000..d4460bdc373 --- /dev/null +++ b/.github/workflows/release-announcement.yml @@ -0,0 +1,57 @@ +name: Release Announcement + +on: + release: + types: [published] + workflow_dispatch: + workflow_call: + +permissions: + contents: read + +jobs: + github-releases-to-discord: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: GitHub Releases to Discord + shell: bash + run: | + RELEASE_JSON=$(gh release view --json body,tagName,url,name) + + RAW_NAME=$(echo "$RELEASE_JSON" | jq -r '.name // .tagName // ""') + RAW_BODY=$(echo "$RELEASE_JSON" | jq -r '.body // ""') + HTML_URL=$(echo "$RELEASE_JSON" | jq -r '.url // ""') + RAW_AVATAR_URL="https://github.com/webpack/media/blob/90b54d02fa1cfc8aa864a8322202f74ac000f5d2/logo/icon.png" + + PAYLOAD=$(jq -n \ + --arg name "$RAW_NAME" \ + --arg avatar_url "$RAW_AVATAR_URL" \ + --arg url "$HTML_URL" \ + --arg body "$RAW_BODY" \ + --arg color "2105893" \ + --arg footer_text "Changelog" \ + --arg timestamp "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \ + '{ + username: "webpack Release Changelog", + avatar_url: $avatar_url, + content: "||<@&1450591255485743204>||", + embeds: [{ + title: ($name | .[0:256]), + url: $url, + color: ($color | tonumber), + description: ($body | .[0:4096]), + footer: { + text: ($footer_text | .[0:2048]) + }, + timestamp: $timestamp + }] + }') + + curl -X POST -H "Content-Type: application/json" \ + -d "$PAYLOAD" \ + "${DISCORD_WEBHOOK}" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000000..32eeea3c9d0 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,81 @@ +name: Release + +on: + push: + branches: + - main + +concurrency: ${{ github.workflow }}-${{ github.ref }} + +permissions: + id-token: write # Required for OIDC + contents: write + pull-requests: write + +jobs: + release: + if: github.repository == 'webpack/webpack' + name: Release + runs-on: ubuntu-latest + outputs: + published: ${{ steps.changesets.outputs.published }} + publishedPackages: ${{ steps.changesets.outputs.publishedPackages }} + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Use Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: lts/* + cache: yarn + + - run: yarn --frozen-lockfile + + - name: Create Release Pull Request or Publish to npm + id: changesets + uses: changesets/action@a45c4d594aa4e2c509dc14a9f2b3b67ba3780d0d # v1.9.0 + with: + publish: node ./node_modules/.bin/changeset publish + commit: "chore(release): new release" + title: "chore(release): new release" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: "" # https://github.com/changesets/changesets/issues/1152#issuecomment-3190884868 + + announce-release: + needs: release + if: needs.release.outputs.published == 'true' + uses: ./.github/workflows/release-announcement.yml + secrets: inherit + + trigger-documentation-update: + needs: release + if: needs.release.outputs.published == 'true' + runs-on: ubuntu-latest + steps: + - name: Generate Token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + id: app-token + with: + app-id: ${{ secrets.BOT_APP_ID }} + private-key: ${{ secrets.BOT_PRIVATE_KEY }} + - name: Dispatch workflow + uses: actions/github-script@d746ffe35508b1917358783b479e04febd2b8f71 # v9.0.0 + env: + PUBLISHED_PACKAGES: ${{ needs.release.outputs.publishedPackages }} + with: + github-token: ${{ steps.app-token.outputs.token }} + script: | + const packages = JSON.parse(process.env.PUBLISHED_PACKAGES); + const pkg = packages.find(p => p.name === "webpack"); + if (!pkg) throw new Error("webpack package not found in published packages"); + + await github.rest.actions.createWorkflowDispatch({ + owner: "webpack", + repo: "webpack-doc-kit", + workflow_id: "release.yml", + ref: "main", + inputs: { + tag: "v" + pkg.version, + }, + }); diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000000..599eeeee40c --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,467 @@ +name: Github Actions + +on: + push: + branches: [main] + pull_request: + branches: [main] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + fetch-depth: 0 + + - name: Use Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: lts/* + cache: yarn + + - run: yarn --frozen-lockfile + + - name: Cache prettier result + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: ./node_modules/.cache/prettier/.prettier-cache + key: lint-prettier-${{ runner.os }}-node-${{ hashFiles('**/yarn.lock', '**/.prettierrc.js') }} + restore-keys: lint-prettier- + + - name: Cache eslint result + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: .eslintcache + key: lint-eslint-${{ runner.os }}-node-${{ hashFiles('**/yarn.lock', '**/eslint.config.mjs') }} + restore-keys: lint-eslint- + + - name: Cache cspell result + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: .cspellcache + key: lint-cspell-${{ runner.os }}-node-${{ hashFiles('**/yarn.lock', '**/cspell.json') }} + restore-keys: lint-cspell- + + - run: yarn lint + + - name: Validate types using old typescript version + run: | + yarn upgrade typescript@5.0 @types/node@20 + yarn --frozen-lockfile + yarn validate:types + + - name: Validate changeset format + if: github.event_name == 'pull_request' + run: | + yarn validate:changeset + + types-coverage: + if: github.repository == 'webpack/webpack' && github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Use Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: lts/* + cache: yarn + + - run: yarn --frozen-lockfile + + - run: yarn link --frozen-lockfile || true + + - run: yarn link webpack --frozen-lockfile + + - name: Collect types coverage + run: yarn types:cover:report + + - uses: jwalton/gh-find-current-pr@f3d61b485d2801773f7a07b2aaa3306bd8f8e653 # v1.3.5 + id: findPr + + - uses: Nef10/lcov-reporter-action@797ec1c1e78e3a9a7890c1a104d58b9e05fcef76 # v0.3.0 + with: + pr-number: ${{ steps.findPr.outputs.number }} + lcov-file: ./coverage/lcov.info + title: Types Coverage + delete-old-comments: true + + validate-legacy-node: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Use Node.js 10.x + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 10.x + cache: yarn + + # Remove `devDependencies` from `package.json` to avoid `yarn install` compatibility error + - run: node -e "const content = require('./package.json');delete content.devDependencies;require('fs').writeFileSync('package.json', JSON.stringify(content, null, 2));" + + - run: yarn install --production --frozen-lockfile + + basic: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Use Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: lts/* + cache: yarn + + - run: yarn --frozen-lockfile + + - run: yarn link --frozen-lockfile || true + + - run: yarn link webpack --frozen-lockfile + + - run: yarn test:basic --ci + + unit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Use Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: lts/* + cache: yarn + + - run: yarn --frozen-lockfile + + - run: yarn link --frozen-lockfile || true + + - run: yarn link webpack --frozen-lockfile + + - name: Cache jest result + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: .jest-cache + key: jest-unit-${{ runner.os }}-${{ hashFiles('**/yarn.lock', '**/jest.config.js') }}-${{ github.sha }} + restore-keys: | + jest-unit-${{ runner.os }}-${{ hashFiles('**/yarn.lock', '**/jest.config.js') }}- + jest-unit-${{ runner.os }}- + + - run: yarn cover:unit --ci --cacheDirectory .jest-cache + + - name: Codecov + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 + with: + flags: unit + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + test262: + needs: basic + strategy: + fail-fast: false + matrix: + shard: [1/2, 2/2] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + submodules: true + + - name: Use Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + # Pinned; revert to "latest" once https://github.com/nodejs/node/issues/63715 is resolved + node-version: "26.2.0" + cache: yarn + + - run: yarn --frozen-lockfile + + - run: yarn link --frozen-lockfile || true + + - run: yarn link webpack --frozen-lockfile + + - name: Cache jest result + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: .jest-cache + key: jest-test262-${{ matrix.shard }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock', '**/jest.config.js') }}-${{ github.sha }} + restore-keys: | + jest-test262-${{ matrix.shard }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock', '**/jest.config.js') }}- + jest-test262-${{ matrix.shard }}-${{ runner.os }}- + + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + repository: tc39/test262 + path: test/js/test262 + ref: f2d59ea6e4b2f6f87de9f0d18a41d73782b6a9bc + + - run: yarn cover:test262 --ci --cacheDirectory .jest-cache + env: + SHARD: ${{ matrix.shard }} + + - name: Codecov + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 + with: + flags: test262 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + html5lib: + needs: basic + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + submodules: true + + - name: Use Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: lts/* + cache: yarn + + - run: yarn --frozen-lockfile + + - run: yarn link --frozen-lockfile || true + + - run: yarn link webpack --frozen-lockfile + + - name: Cache jest result + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: .jest-cache + key: jest-html5lib-${{ runner.os }}-${{ hashFiles('**/yarn.lock', '**/jest.config.js') }}-${{ github.sha }} + restore-keys: | + jest-html5lib-${{ runner.os }}-${{ hashFiles('**/yarn.lock', '**/jest.config.js') }}- + jest-html5lib-${{ runner.os }}- + + - run: yarn cover:html5lib --ci --cacheDirectory .jest-cache + + - name: Codecov + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 + with: + flags: html5lib + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + css-parsing: + needs: basic + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + submodules: true + + - name: Use Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: lts/* + cache: yarn + + - run: yarn --frozen-lockfile + + - run: yarn link --frozen-lockfile || true + + - run: yarn link webpack --frozen-lockfile + + - name: Cache jest result + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: .jest-cache + key: jest-css-parsing-${{ runner.os }}-${{ hashFiles('**/yarn.lock', '**/jest.config.js') }}-${{ github.sha }} + restore-keys: | + jest-css-parsing-${{ runner.os }}-${{ hashFiles('**/yarn.lock', '**/jest.config.js') }}- + jest-css-parsing-${{ runner.os }}- + + - run: yarn cover:css-parsing --ci --cacheDirectory .jest-cache + + - name: Codecov + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 + with: + flags: css-parsing + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + # Run webpack's test suite under Deno and Bun (spec validation suites excluded). + runtimes: + needs: basic + strategy: + fail-fast: false + matrix: + runtime: [deno, bun] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Use Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: lts/* + cache: yarn + + - name: Use Deno + if: matrix.runtime == 'deno' + uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2.0.4 + with: + deno-version: v2.x + + - name: Use Bun + if: matrix.runtime == 'bun' + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 + with: + bun-version: latest + + - run: yarn --frozen-lockfile + + - run: yarn link --frozen-lockfile || true + + - run: yarn link webpack --frozen-lockfile + + # Bun runs with worker_threads (see test:base:bun): in-band's single heap + # OOMs the runner on the full suite and Bun's child_process workers hang, but + # threads work once jest-worker loads the Bun preload per thread. Apply that + # patch here (Bun only) rather than via a repo-wide postinstall. + - if: matrix.runtime == 'bun' + run: git apply test/patches/jest-worker+30.4.1.patch + + # Deno and Bun both run with jest's parallel workers. The HotTestCases + # suites are dropped for Deno only (see test:deno): under Deno's event-loop + # timing webpack's async cache/compilation tails outlive a suite's teardown + # and require a module after the Jest environment is gone. Bun runs them with + # only a few per-case test.filter.js skips. + - run: yarn test:${{ matrix.runtime }} --ci + + integration: + needs: basic + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + node-version: [10.x, 20.x, 22.x, 24.x, 26.x] + part: [a, b] + include: + # Test with main branches of webpack dependencies + - os: ubuntu-latest + node-version: lts/* + part: a + use_main_branches: 1 + - os: ubuntu-latest + node-version: lts/* + part: b + use_main_branches: 1 + # Test on old Node.js versions + - os: ubuntu-latest + node-version: 18.x + part: a + - os: ubuntu-latest + node-version: 16.x + part: a + - os: ubuntu-latest + node-version: 14.x + part: a + - os: ubuntu-latest + node-version: 12.x + part: a + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + id: calculate_architecture + with: + result-encoding: string + script: | + if ('${{ matrix.os }}' === 'macos-latest' && '${{ matrix['node-version'] }}' === '10.x') { + return "x64" + } else { + return '' + } + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: ${{ matrix.node-version }} + architecture: ${{ steps.calculate_architecture.outputs.result }} + cache: yarn + + # Install old `jest` version and deps for legacy node versions + - run: | + yarn upgrade jest@^27.5.0 jest-circus@^27.5.0 jest-cli@^27.5.0 jest-diff@^27.5.0 jest-environment-node@^27.5.0 jest-snapshot@^27.5.0 jest-junit@^13.0.0 @types/jest@^27.4.0 pretty-format@^27.0.2 husky@^8.0.3 lint-staged@^13.2.1 cspell@^6.31.1 open-cli@^7.2.0 coffee-loader@^1.0.0 babel-loader@^8.1.0 style-loader@^2.0.0 css-loader@^5.0.1 less-loader@^8.1.1 less@4.5.1 mini-css-extract-plugin@^1.6.1 nyc@^15.1.0 memfs@4.14.0 --ignore-engines + yarn --frozen-lockfile --ignore-engines + if: matrix.node-version == '10.x' || matrix.node-version == '12.x' || matrix.node-version == '14.x' + + - run: | + yarn upgrade jest@^27.5.0 jest-circus@^27.5.0 jest-cli@^27.5.0 jest-diff@^27.5.0 jest-environment-node@^27.5.0 jest-snapshot@^27.5.0 jest-junit@^13.0.0 @types/jest@^27.4.0 pretty-format@^27.0.2 husky@^8.0.3 lint-staged@^13.2.1 nyc@^15.1.0 coffee-loader@1.0.0 babel-loader@^8.1.0 style-loader@^2.0.0 css-loader@^5.0.1 less-loader@^8.1.1 mini-css-extract-plugin@^1.6.1 --ignore-engines + yarn --frozen-lockfile + if: matrix.node-version == '16.x' + + - run: | + yarn upgrade cspell@^8.8.4 lint-staged@^15.2.5 --ignore-engines + yarn --frozen-lockfile + if: matrix.node-version == '18.x' + + - run: | + yarn upgrade cspell@^9 --ignore-engines + yarn --frozen-lockfile + if: matrix.node-version == '20.x' + + - run: | + yarn upgrade pkg-pr-new@0.0.66 + yarn --frozen-lockfile + if: matrix.node-version == '22.x' + + # Install main version of our deps + - run: yarn upgrade enhanced-resolve@webpack/enhanced-resolve#main loader-runner@webpack/loader-runner#main webpack-sources@webpack/webpack-sources#main watchpack@webpack/watchpack#main tapable@webpack/tapable#main + if: matrix.use_main_branches == '1' + + # Install dependencies for LTS node versions + - run: yarn --frozen-lockfile + if: matrix.node-version != '10.x' && matrix.node-version != '12.x' && matrix.node-version != '14.x' && matrix.node-version != '16.x' && matrix.node-version != '18.x' && matrix.node-version != '20.x' && matrix.node-version != '22.x' + + - run: yarn link --frozen-lockfile || true + + - run: yarn link webpack --frozen-lockfile + + - name: Cache jest result + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: .jest-cache + key: jest-integration-${{ runner.os }}-node-${{ matrix.node-version }}-${{ matrix.part }}-${{ hashFiles('**/yarn.lock', '**/jest.config.js') }}-${{ github.sha }} + restore-keys: | + jest-integration-${{ runner.os }}-node-${{ matrix.node-version }}-${{ matrix.part }}-${{ hashFiles('**/yarn.lock', '**/jest.config.js') }}- + jest-integration-${{ runner.os }}-node-${{ matrix.node-version }}-${{ matrix.part }}- + + - run: yarn cover:integration:${{ matrix.part }} --ci --cacheDirectory .jest-cache || yarn cover:integration:${{ matrix.part }} --ci --cacheDirectory .jest-cache -f + env: + MAIN_BRANCHES: ${{ matrix.use_main_branches }} + if: matrix.node-version != '10.x' && matrix.node-version != '12.x' && matrix.node-version != '14.x' && matrix.node-version != '16.x' && matrix.node-version != '18.x' && matrix.node-version != '20.x' + + # Don't run code coverage analysis on older versions of NodeJS, this will speed up our CI + - run: yarn test:integration:${{ matrix.part }} --ci --cacheDirectory .jest-cache || yarn test:integration:${{ matrix.part }} --ci --cacheDirectory .jest-cache -f + if: matrix.node-version == '10.x' || matrix.node-version == '12.x' || matrix.node-version == '14.x' || matrix.node-version == '16.x' || matrix.node-version == '18.x' || matrix.node-version == '20.x' + + - run: yarn report:cover:merge + if: matrix.node-version != '10.x' && matrix.node-version != '12.x' && matrix.node-version != '14.x' && matrix.node-version != '16.x' && matrix.node-version != '18.x' && matrix.node-version != '20.x' + + - name: Codecov + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 + with: + files: ./coverage/coverage-nyc.json,./coverage/coverage-final.json + directory: ./coverage/ + disable_search: true + flags: integration + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + if: matrix.node-version != '10.x' && matrix.node-version != '12.x' && matrix.node-version != '14.x' && matrix.node-version != '16.x' && matrix.node-version != '18.x' && matrix.node-version != '20.x' diff --git a/.gitignore b/.gitignore index 32814bb493a..a2042c75c70 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,24 @@ /node_modules /test/js /test/browsertest/js -/test/fixtures/temp-cache-fixture -/benchmark/js -/benchmark/fixtures +/test/fixtures/temp-* +/test/temp +/test/ChangesAndRemovals +/test/ChangesAndRemovalsTemp +/test/**/dev-defaults.webpack.lock +/test/**/generated/** /examples/**/dist +/examples/nodejs-addons/build/** +/assembly/**/*.wat +/assembly/**/*.wasm /coverage +/.nyc_output +/.jest-cache .DS_Store *.log .idea .vscode +.cache .eslintcache +.cspellcache package-lock.json diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000000..27660ffe529 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "test/test262-cases"] + path = test/test262-cases + url = https://github.com/tc39/test262 +[submodule "test/html5lib-tests"] + path = test/html5lib-tests + url = https://github.com/html5lib/html5lib-tests.git +[submodule "test/css-parsing-tests"] + path = test/css-parsing-tests + url = https://github.com/CourtBouillon/css-parsing-tests.git diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 00000000000..041c660c92b --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npx --no-install lint-staged diff --git a/.prettierignore b/.prettierignore index c99674c1822..b19d9bf01ba 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,15 +1,51 @@ -# Ignore all paths. -**/*.* - -# Enable prettier for the following paths. -!setup/**/*.js -!lib/**/*.js -!bin/*.js -!hot/*.js -!buildin/*.js -!benchmark/**/*.js +package.json + +# Ignore some test files +test/**/*.* !test/*.js +!test/*.cjs +!test/*.mjs !test/**/webpack.config.js +!test/**/webpack.config.cjs +!test/**/webpack.config.mjs +!test/**/test.config.js +!test/**/test.filter.js +!test/**/errors.js +!test/**/warnings.js +!test/**/deprecations.js +!test/**/infrastructure-log.js +!test/*.md +!test/helpers/*.* +!test/harness/**/*.* +!test/benchmarkCases/**/*.mjs +test/js/**/*.* +test/test262-cases/**/*.* + +# Ignore some folders +benchmark/ +coverage/ + +# Ignore generated files +*.check.js +*.check.d.ts +types.d.ts +declarations/WebpackOptions.d.ts + +# Ignore precompiled schemas +schemas/**/*.check.js + +# Ignore example fixtures +examples/** +!examples/*/ +!examples/**/internals/ +!examples/**/internals/** !examples/**/webpack.config.js -!schemas/**/*.js -!declarations.d.ts +!examples/**/webpack.config.cjs +!examples/**/webpack.config.mjs +!examples/**/test.filter.js + +.vscode/**/*.* + +# Ignore local working files +pr.md +issue.md diff --git a/.prettierrc.js b/.prettierrc.js index 03b9da1bac3..04b93b8c6a7 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -1,5 +1,24 @@ +"use strict"; + module.exports = { printWidth: 80, useTabs: true, - tabWidth: 2 + tabWidth: 2, + trailingComma: "none", + arrowParens: "always", + overrides: [ + { + files: "*.json", + options: { + parser: "json", + useTabs: false + } + }, + { + files: "*.{cts,mts,ts}", + options: { + parser: "typescript" + } + } + ] }; diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 184cf5ee220..00000000000 --- a/.travis.yml +++ /dev/null @@ -1,65 +0,0 @@ -sudo: false -dist: trusty -language: node_js - -branches: - only: - - master - - next - -cache: - yarn: true - directories: - - ".jest-cache" - - ".eslintcache" - -stages: - - basic - - advanced - - versions - -matrix: - include: - - os: linux - node_js: "10" - env: NO_WATCH_TESTS=1 JEST="--maxWorkers=2 --cacheDirectory .jest-cache" JOB_PART=basic - stage: basic - - os: linux - node_js: "10" - env: NO_WATCH_TESTS=1 JEST="--maxWorkers=2 --cacheDirectory .jest-cache" JOB_PART=lint-unit - stage: advanced - - os: linux - node_js: "10" - env: NO_WATCH_TESTS=1 JEST="--maxWorkers=2 --cacheDirectory .jest-cache" JOB_PART=integration - stage: advanced - - os: osx - node_js: "10" - env: NO_WATCH_TESTS=1 JEST="--maxWorkers=2 --cacheDirectory .jest-cache" JOB_PART=integration - stage: versions - - os: linux - node_js: "8" - env: NO_WATCH_TESTS=1 JEST="--maxWorkers=2 --cacheDirectory .jest-cache" JOB_PART=integration - stage: versions - - os: linux - node_js: "6" - env: NO_WATCH_TESTS=1 JEST="--maxWorkers=2 --cacheDirectory .jest-cache" JOB_PART=integration - stage: versions - fast_finish: true - allow_failures: - - os: osx - -install: - - yarn --frozen-lockfile - - yarn link --frozen-lockfile || true - - yarn link webpack --frozen-lockfile - -script: yarn travis:$JOB_PART - -after_success: - - cat ./coverage/lcov.info | node_modules/.bin/coveralls --verbose - - bash <(curl -s https://codecov.io/bash) -F $JOB_PART -X gcov - - rm -f .jest-cache/haste-map* .jest-cache/perf-cache* - -notifications: - slack: - secure: JduSdKWwbnLCwo7Z4E59SGE+Uw832UwnXzQiKEpg1BV45MYDPRiGltly1tRHmPh9OGjvGx3XSkC2tNGOBLtL4UL2SCkf012x0t7jDutKRfcv/njynl8jk8l+UhPmaWiHXDQAgGiiKdL4RfzPLW3HeVHCOWm0LKMzcarTa8tw+rE= diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000000..01e58fefc42 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,292 @@ +# Webpack Development Guide + +> Note: CLAUDE.md is a symlink to AGENTS.md. They are the same file. + +## Conventions in this guide + +A `> [!REQUIRED]` callout placed immediately under a heading marks that whole section as **mandatory and not optional**: follow it exactly, do not paraphrase, do not skip, do not substitute a similar-looking convention from other tooling. Reviewers have repeatedly flagged that REQUIRED sections (especially the [Pull request body](#pull-request-body)) are being skipped or partially filled in — doing so blocks the PR every time. Read each REQUIRED section in full whenever it applies; do not rely on memory or on a previous task's output. Sections without the callout are normal guidance — apply judgement. + +## Project Overview + +> [!REQUIRED] + +The directory listings below are the canonical map of the repository. **Whenever you add, rename, or remove a top-level directory** (under the repo root, under `lib/`, under `test/`, or under `schemas/`) you must update the matching bullet here in the same commit. CI does not check this — drift is only caught by humans, which is why it must be part of the change itself. If a new directory does not fit any existing group, add a new group rather than dropping the entry. + +webpack is a JavaScript module bundler. Package manager: **yarn**. + +**Source** + +- `lib/` — Main source code (CommonJS only; types declared via JSDoc `@typedef`). + - `lib/asset/` — Asset modules (images, fonts, raw files). + - `lib/async-modules/` — Top-level await. + - `lib/bun/` — Bun target externals preset (`bun:*` and node.js built-in modules). + - `lib/cache/` — Filesystem and memory caches. + - `lib/config/` — Config defaults, normalization, target presets. + - `lib/container/` — Module Federation. + - `lib/css/` — CSS Modules, CSS parsing and generation. + - `lib/debug/` — Debug helpers. + - `lib/dependencies/` — `Dependency` classes and their templates (HarmonyImport, CommonJsRequire, RequireContext, …). + - `lib/dll/` — DllPlugin / DllReferencePlugin. + - `lib/deno/`, `lib/electron/`, `lib/node/`, `lib/web/`, `lib/webworker/` — Target-specific runtime templates and externals presets. + - `lib/errors/` — Error class hierarchy. + - `lib/esm/` — ESM-specific output (e.g. `import.meta`). + - `lib/hmr/` — Hot Module Replacement plugins. + - `lib/html/` — Experimental HTML support. + - `lib/ids/` — Module/chunk id assignment plugins. + - `lib/javascript/` — JavaScript parsing (acorn), generation, exports analysis. + - `lib/json/` — JSON modules. + - `lib/library/` — UMD/AMD/ESM/CommonJS library output formats. + - `lib/logging/` — Logger API and console formatting. + - `lib/optimize/` — Optimization plugins (`SplitChunksPlugin`, `ConcatenatedModule`, …). + - `lib/performance/` — Asset/entrypoint size hints. + - `lib/prefetch/` — Prefetch/preload plugins. + - `lib/rules/` — `module.rules` matching engine. + - `lib/runtime/` — Runtime modules emitted into bundles (chunk loaders, public-path, …). + - `lib/schemes/` — Custom URL scheme handlers (`data:`, `http:`, …). + - `lib/serialization/` — Persistent cache serialization. + - `lib/sharing/` — Shared modules / Module Federation runtime. + - `lib/stats/` — Stats output (default printer, JSON factories). + - `lib/typescript/` — Experimental TypeScript module support (strip types via the Node.js TypeScript API). + - `lib/url/` — `new URL(asset, import.meta.url)` references. + - `lib/util/` — Utility helpers. + - `lib/wasm/`, `lib/wasm-async/`, `lib/wasm-sync/` — WebAssembly module support. +- `hot/` — Runtime code shipped to browsers for HMR (browser-side, not Node tooling). +- `bin/` — `webpack` CLI entry point. +- `tooling/` — Repo-internal build scripts (runtime/wasm code generators, hash-debug tool); invoked by `yarn fix:special`. +- `assembly/` — WebAssembly source for the hash function. +- `setup/` — One-time setup scripts. + +**Schemas (the source of truth for webpack's config API)** + +- `schemas/WebpackOptions.json` — top-level webpack options schema. +- `schemas/plugins/*.json` — per-plugin option schemas (`BannerPlugin`, `IgnorePlugin`, `ProgressPlugin`, `SourceMapDevToolPlugin`, …). +- `schemas/_container.json`, `schemas/_sharing.json` — Module Federation sub-schemas. + +**Tests** — see [TESTING_DOCS.md](TESTING_DOCS.md) for directory structure, naming, and how to run a single case. + +- `test/` — All test suites (`cases/`, `configCases/`, `watchCases/`, `hotCases/`, `statsCases/`, `typesCases/`, `test262-cases/`, `html5lib-tests/`, `css-parsing-tests/`, `benchmarkCases/`, `memoryLimitCases/`, etc.). + +**Examples & changesets** + +- `examples/` — Usage examples (build with `yarn build:examples`). +- `.changeset/` — Pending changeset files for the next release. + +**Auto-generated — do not edit by hand; regenerate via `yarn fix:special`** + +- `types.d.ts`, `declarations/**/*.d.ts`, `schemas/**/*.check.{js,d.ts}`, generated runtime code under `lib/`. + +**Hand-maintained type declarations (these _are_ editable)** + +- `declarations.d.ts`, `declarations.test.d.ts`, `module.d.ts`. + +**Configuration** + +- `package.json` — All commands (defined in `scripts`). +- `tsconfig*.json` — TypeScript configs (one per surface: `lib`, `hot`, types tests, validation, benchmarks). +- `eslint.config.mjs`, `cspell.json`, `jest.config.js`, `generate-types-config.js` — Lint/spell/test/type-gen configs. +- `.github/workflows/`, `.github/scripts/` — CI. +- `test/patches/` — test-only dependency patches (e.g. jest-worker) applied via `git apply` in the CI Bun test job. + +## Coding Standards + +### Source language: CommonJS + JSDoc + +`lib/` is CommonJS only. Use `module.exports` / `require()`, never `import`/`export` syntax. Types are declared via JSDoc — `@typedef {import("./Other")} Other` and friends — never TypeScript syntax inside `.js` files. The JSDoc annotations are compiled into `types.d.ts` by `yarn fix:special`. + +### Source file headers + +Every source file under `lib/` (and `hot/`, `tooling/`) opens with the MIT license header. When adding a **new** file, set the `Author` line to its actual author (`Author @`) — don't copy another file's author line. + +### Code comments + +> [!REQUIRED] + +Comments inside `lib/`, `hot/`, `tooling/`, and `test/` must be **as short as possible** — ideally one line, at most two short lines. Every line must add information a careful reader can't get from the code itself: a hidden invariant, a non-obvious ordering constraint, a workaround, or the name of the higher-level concept the block implements. **Never** write multi-paragraph essays, restate what the next line obviously does, narrate the diff, restate the PR description, or quote the user/task framing. + +JSDoc on exported symbols stays as-is — that's the type contract, not commentary. + +## Performance and memory + +webpack is a bundler — users measure it by build time and peak heap usage. Many changes in `lib/` end up on per-module hot paths (sometimes per module × runtime, or per chunk × module) on user builds, so constant factors compound. Always weigh the time and memory cost of a change, including bug fixes and refactors: less allocation, smaller `Map`/`Set` footprints, and fewer closures retained on hot paths are wins worth pursuing — less is better. When introducing or holding any per-`Compilation` state, ask whether it can be released after seal/emit so large compilation data structures are not retained longer than necessary. See #15521 for an example of how this class of memory issue can surface. + +## Auto-generated files + +> [!REQUIRED] + +These files are produced by `yarn fix:special` and must not be edited by hand: + +- `types.d.ts` — compiled from JSDoc + schemas. +- `declarations/**/*.d.ts` — per-schema/plugin declarations emitted from `schemas/**/*.json`. +- `schemas/**/*.check.{js,d.ts}` — precompiled schema validators. +- Generated runtime code under `lib/` (driven by `tooling/generate-runtime-code.js`). + +The hand-maintained type declarations (`declarations.d.ts`, `declarations.test.d.ts`, `module.d.ts`) _are_ editable. + +Re-run `yarn fix:special` **before the next commit** whenever you touch: + +- `schemas/**/*.json` — reshapes validators, declarations, and `types.d.ts`. +- `lib/**/*.js` JSDoc on anything reachable from a public export — regenerates `types.d.ts`. +- `tooling/generate-runtime-code.js`, `tooling/generate-wasm-code.js`, or any file they consume. + +CI's `lint` job verifies these outputs are up to date. The combined `yarn fix` script runs `fix:code` + `fix:special` + `fmt` in one go; prefer it as the final step. + +## Development Workflow + +### 1. Making Changes + +Modify source code in `lib/` as needed. + +**Adding or renaming a webpack option** requires edits in every layer, in this order: + +1. **Schema** — `schemas/WebpackOptions.json` (or `schemas/plugins/.json`). +2. **Defaults** — `lib/config/defaults.js`. +3. **Normalization** — `lib/config/normalization.js`. +4. **Implementation** — the site that consumes the option. + +Skipping any layer silently breaks the option. After editing schemas, run `yarn fix:special` so `lib/` code can reference the updated types. + +### 2. Writing and Running Tests + +**For bug fixes, always write the test case first.** Run the test to confirm it fails, then make the code change and re-run. For new features, tests can be written alongside or after. + +**Prefer integration tests over unit tests.** Cover behavior with an integration case (`configCases/`, `watchCases/`, `hotCases/`, `statsCases/`, …) that drives a real `webpack()` build whenever the behavior can be exercised that way — they catch real-world regressions a mocked unit test misses. Reach for a `*.unittest.js` only for pure helpers/utilities that a build can't naturally reach. + +Run targeted tests — `yarn jest test/` or `yarn jest -t ""`. Don't run `yarn test` unless asked. When updating snapshots (`yarn jest -u`), eyeball the diff first. See [TESTING_DOCS.md](TESTING_DOCS.md) for details. + +### 3. Adding a Changeset + +Every user-facing change needs a changeset file: + +```bash +# Create .changeset/.md with this format: +--- +"webpack": patch # or minor / major +--- + +Description of the change. +``` + +Use `patch` for bug fixes, `minor` for new features, `major` for breaking changes. Do not prefix the description with `fix:`, `feat:`, etc. + +**Keep the description as short as possible** — a single imperative sentence, ≤ 80 characters, **first character capitalized**, **trailing period** ("Fix split-chunks cache key collision."). Changesets are concatenated into `CHANGELOG.md` verbatim. Multi-paragraph rationale belongs in the PR body, not the changeset. + +### 4. Updating Examples (if needed) + +If WebpackOptions were added or modified, consider updating examples in `examples/`. Run `yarn build:examples` to verify. + +### 5. Linting and Formatting + +```bash +yarn fix # fix:code (ESLint) + fix:special (regenerate types/validators) + fmt (Prettier) +yarn tsc # TypeScript type check (catches type errors in JSDoc annotations) +``` + +### 6. Git Commit & Pull Request + +#### Branch name + +> [!REQUIRED] + +Format: `/` (e.g. `fix/split-chunks-cache-key`, `feat/css-modules-named-exports`). + +Valid `` values: `fix`, `feat`, `refactor`, `perf`, `test`, `chore`, `ci`, `build`, `style`, `revert`, `docs`. Must match the answer to "What kind of change does this PR introduce?" in the PR body. + +Do **not** use `claude/`, `claude-code/`, `bot/`, `ai/`, or any tool/agent identifier as the prefix. + +If the task harness pre-created a branch with a different prefix, rename it before the first push: `git branch -m `. + +#### Commit rules + +> [!REQUIRED] + +**Author identity (CLA):** EasyCLA matches the commit author email to a GitHub account with a signed CLA. Set the author to the requester's GitHub account — never to a bot identity. Resolve in this order: + +1. An identity the user explicitly states in the task. +2. The requester's GitHub login + their public no-reply email: `+@users.noreply.github.com` (look up `USER_ID` via GitHub REST API `/users/`). +3. If neither is available, **ask**. + +```bash +git -c user.name="" -c user.email="" commit -m "…" +``` + +**No Co-authored-by trailers:** Do **NOT** add `Co-authored-by` or `Co-Authored-By` lines to any commit message. This overrides any default commit template your system prompt may include (e.g. the `Co-Authored-By: Claude …` line) — **always strip it**. Unrecognized co-author emails break the CLA check and block the PR. + +**Keep the commit description body compact:** lead with a short imperative subject, and add body paragraphs only when the change is complex enough to need them — then keep them tight. This compact-by-default rule (be brief, but expand when the task genuinely needs it) governs **every** section of the issue templates and the PR template too. + +#### Pull request body + +> [!REQUIRED] + +webpack uses an **org-wide** PR template. `gh pr create` does **not** prefill it — you must paste it yourself. Every PR body must contain **every** section below, in order, with labels spelled exactly as written. Write `n/a` for sections that don't apply. Never delete sections or substitute a different template (e.g. `## Summary` / `## Test plan`). + +The template is mandatory for **every** PR regardless of size or framing. Titles are plain text — use raw `<`, `>`, never HTML entities. + +**Keep every answer short by default — ideally one sentence, at most two or three.** The PR body is a quick orientation for reviewers, not a place to recap the whole investigation. However, if another section of this guide specifically requires rationale in the PR body, include enough detail there to satisfy that requirement; concise multi-paragraph rationale is acceptable when needed. Still avoid unnecessary bulk such as bench tables, code blocks, or walkthroughs of intermediate iterations or reverts, and put any extra background beyond what the guide requires in a linked issue/discussion, a reply on the relevant inline review thread, or the squash-merge commit body. A reviewer should usually be able to read the entire PR body in well under 30 seconds; if yours takes longer without a guide-required reason, trim it. + +Common mistakes that block PRs: + +- Using `## Summary` headings instead of `**Summary**` bold labels. +- Omitting **Use of AI** (mandatory per [webpack AI policy](https://github.com/webpack/governance/blob/main/AI_POLICY.md)). +- Omitting or mis-answering **What kind of change does this PR introduce?** (must match branch prefix). +- Dropping HTML comment hints or leaving sections blank instead of `n/a`. + +Paste the body from the fenced block below (do **not** include the fence lines themselves): + +```markdown + + +**Summary** + + + + + + + +**What kind of change does this PR introduce?** + + + +**Did you add tests for your changes?** + + + +**Does this PR introduce a breaking change?** + + + +**If relevant, what needs to be documented once your changes are merged or what have you already documented?** + + + +**Use of AI** + + +``` + +Required answer per section — **one sentence each is the target, two or three the absolute maximum**: + +- **Summary** — motivation and what problem is solved; link the related issue (`Closes #…` / `Fixes #…`). +- **What kind of change does this PR introduce?** — one of: fix, feat, refactor, perf, test, chore, ci, build, style, revert, docs. +- **Did you add tests for your changes?** — yes/no + which test files. +- **Does this PR introduce a breaking change?** — yes/no + migration path if yes. +- **If relevant, what needs to be documented…** — list doc updates or write `n/a`. +- **Use of AI** — state that AI was used and how. Per the [webpack AI policy](https://github.com/webpack/governance/blob/main/AI_POLICY.md), omitting or misrepresenting this can get the PR closed. + +#### After push — verify PR body + +After every `git push` of a new branch, check whether a PR was auto-created (webpack has this webhook). If so, `update_pull_request` to install the full template — the auto-created body never matches. + +#### After opening the PR — wait for Copilot review + +> [!REQUIRED] + +Every webpack PR gets an automated **GitHub Copilot code review** on the initial commit and on every subsequent push. You must always wait for it and address every comment. + +1. After `create_pull_request`, subscribe to the PR (`subscribe_pr_activity`) so Copilot's review wakes the session. Do **not** poll. +2. When the review arrives, read every comment: + - If correct, push a fix in a new commit. + - If wrong, reply on the thread with a short reason — never ignore silently. +3. After every push, Copilot re-reviews. Repeat step 2. The loop ends when Copilot's latest review has zero outstanding threads. +4. Only `unsubscribe_pr_activity` once all comments are handled and CI is green, or when the user tells you to stop. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000000..db7bbef9ac9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,392 @@ +# webpack + +## 5.107.2 + +### Patch Changes + +- Reduce per-file overhead in `ContextModuleFactory.resolveDependencies` by batching `alternativeRequests` hook calls. Previously the hook was invoked once per file in the context (with a single-item array), paying per-call overhead (closure allocation, `resolverFactory.get`, intermediate arrays in `RequireContextPlugin`) for every file. The hook is now invoked once per directory with all matched files in one batch — `RequireContextPlugin`'s tap already iterates the items array, so the output is unchanged. Steady-state rebuild on a 4000-file `require.context` drops a further ~15 ms (after the watch-mode purge fix in the same release). (by [@alexander-akait](https://github.com/alexander-akait) in [#21020](https://github.com/webpack/webpack/pull/21020)) + +- Include each external info's `runtimeCondition` in `ConcatenatedModule#updateHash` so changes to a concatenated external's runtime condition invalidate persistent caches instead of slipping through with the module id alone. (by [@alexander-akait](https://github.com/alexander-akait) in [#21023](https://github.com/webpack/webpack/pull/21023)) + +- Fix HTML `[contenthash]` for referenced asset and inline-style URL changes. (by [@alexander-akait](https://github.com/alexander-akait) in [#21018](https://github.com/webpack/webpack/pull/21018)) + +- Resolve chunk-hash placeholders in chunk URLs embedded into extracted HTML. (by [@alexander-akait](https://github.com/alexander-akait) in [#21018](https://github.com/webpack/webpack/pull/21018)) + +- Remove unnecessary `__webpack_require__` runtime helpers in ESM library output with multi-module chunks. (by [@xiaoxiaojx](https://github.com/xiaoxiaojx) in [#21032](https://github.com/webpack/webpack/pull/21032)) + +- Rewrite `NormalModule#getSideEffectsConnectionState` walk as an allocation-light iterative loop instead of a generator trampoline, restoring rebuild performance lost in #20993 while keeping deep import chains stack-safe. (by [@alexander-akait](https://github.com/alexander-akait) in [#21014](https://github.com/webpack/webpack/pull/21014)) + +- Fix runtime `ReferenceError` on the first activation of a lazy-compiled module when `output.library.type` produces a closure-wrapped bundle (`umd`, `umd2`, `amd`, `amd-require`, `system`). (by [@alexander-akait](https://github.com/alexander-akait) in [#21013](https://github.com/webpack/webpack/pull/21013)) + + External modules of these types reference closure-bound identifiers like `__WEBPACK_EXTERNAL_MODULE_react__`, supplied by the library wrapper that is generated once per chunk. When `lazyCompilation` activates an entry or import for the first time, any external dependency the lazily-built module pulls in arrives in a hot-update chunk that lives outside the original wrapper closure, so its factory body cannot resolve the closure identifier and only a manual page refresh recovers. + + The inactive `LazyCompilationProxyModule` now declares statically-enumerable externals (string and object forms of `externals`) as its own dependencies, so the initial entry chunk's library wrapper already exposes their closure identifiers. When activation later pulls in those externals through the lazily-compiled module, they resolve to the already-installed factories instead of throwing. Function and RegExp externals are not pre-populated because their effective request set isn't knowable up front. + +- Fill in missing `entryOptions` when an async block joins an existing entrypoint. (by [@alexander-akait](https://github.com/alexander-akait) in [#21026](https://github.com/webpack/webpack/pull/21026)) + +- Release per-child `codeGenerationResults` in `MultiCompiler` and at `Compiler.close` to reduce memory retention. (by [@alexander-akait](https://github.com/alexander-akait) in [#21015](https://github.com/webpack/webpack/pull/21015)) + +- Reduce peak memory of `SourceMapDevToolPlugin` on large builds (closes #20961). (by [@alexander-akait](https://github.com/alexander-akait) in [#20963](https://github.com/webpack/webpack/pull/20963)) + +- Fix slow `require.context()` / dynamic `import()` rebuilds in watch mode (#13636). When a file inside a watched context directory changed, `NodeWatchFileSystem` would call `inputFileSystem.purge(contextDir)`. The enhanced-resolve `purge` implementation matches cache keys with `key.startsWith(contextDir)`, so the stat cache of every file under the directory was discarded on every rebuild — `ContextModuleFactory.resolveDependencies` then re-`stat`-ed the whole tree on each rebuild. Single-file rebuilds on a 4000-file context now reuse the warm stat cache, dropping median rebuild from ~1260 ms to ~650 ms in a local reproduction (≈49%). For directory items that are explicitly watched contexts, `purge` is now called with `{ exact: true }` (added in `enhanced-resolve@5.22.0`) so only the directory's own entry is invalidated; file-level changes in the same aggregated event continue to purge file stats and the parent `readdir` as before. (by [@alexander-akait](https://github.com/alexander-akait) in [#21020](https://github.com/webpack/webpack/pull/21020)) + +## 5.107.1 + +### Patch Changes + +- Align the experimental HTML tokenizer with the WHATWG spec: fix offset-range bugs in the script-data, content-mode end-tag, attribute-value, and EOF states; surface tokenizer parse errors to consumers via a new `parseError` callback (`"warning"` when the tokenizer recovers and the emitted token is still well-formed, `"error"` when the offset range is incomplete — e.g. `eof-in-tag`); and add the full WHATWG named character references table so `decodeHtmlEntities` handles all named entities (including legacy bare forms like `&` and multi-code-point entities like `≂̸`) with proper longest-prefix backtracking. (by [@alexander-akait](https://github.com/alexander-akait) in [#21000](https://github.com/webpack/webpack/pull/21000)) + +- Tree-shake CommonJS modules imported through a `const NAME = require(LITERAL)` binding when only static members of `NAME` are read. Previously webpack treated every export of such modules as referenced (because the bare `require()` dependency reports `EXPORTS_OBJECT_REFERENCED`), so unused `exports.x = ...` assignments remained in the bundle even with `usedExports` enabled. The parser now forwards `NAME.x` / `NAME.x()` / `NAME["x"]` accesses to the underlying `CommonJsRequireDependency` as referenced exports, falling back to the full exports object the moment `NAME` is read in any other context (passed by value, destructured later, accessed with a dynamic key, …). This brings the binding form to parity with the existing destructuring form (`const { x } = require(...)`). (by [@alexander-akait](https://github.com/alexander-akait) in [#21003](https://github.com/webpack/webpack/pull/21003)) + +- Fix `RangeError: Maximum call stack size exceeded` thrown from `HarmonyImportSideEffectDependency.getModuleEvaluationSideEffectsState` on long linear chains of side-effect-free imports. `NormalModule.getSideEffectsConnectionState` previously descended through `HarmonyImportSideEffectDependency.getModuleEvaluationSideEffectsState` recursively, adding two stack frames per module, which overflowed V8's stack at a few thousand modules deep. The traversal is now iterative. (by [@alexander-akait](https://github.com/alexander-akait) in [#20993](https://github.com/webpack/webpack/pull/20993)) + +- Fix `NormalModuleFactory` parser/generator types: (by [@alexander-akait](https://github.com/alexander-akait) in [#20999](https://github.com/webpack/webpack/pull/20999)) + - `module.generator.html` now uses `HtmlGeneratorOptions` instead of `EmptyGeneratorOptions` (the `extract` option was hidden from the `createGenerator` / `generator` hook types). + - WebAssembly (`webassembly/async`, `webassembly/sync`) generator hooks now use `EmptyGeneratorOptions` instead of `EmptyParserOptions`. + - `NormalModuleFactory#getParser` / `createParser` / `getGenerator` / `createGenerator` are now generic over the module-type string, returning the specific parser/generator class for known types (e.g. `JavascriptParser` for `"javascript/auto"`, `CssGenerator` for `"css"`, etc.) instead of always returning the base `Parser` / `Generator`. + - `NormalModuleCreateData` is now generic over the module type so `parser`, `parserOptions`, `generator`, and `generatorOptions` are narrowed to the specific class / options for the given `type`. + +- Link import bindings used inside `define(...)` callbacks in ES modules. Previously, `HarmonyDetectionParserPlugin` skipped walking the arguments of `define` calls in harmony modules, so references to imported bindings inside an inline AMD `define` factory (e.g. `define(function () { console.log(foo); })`) were not rewritten to their imported references and could cause `ReferenceError` at runtime. Inner graph usage analysis is also fixed for the related pattern `const fn = function () { foo; }; define(fn);`. (by [@alexander-akait](https://github.com/alexander-akait) in [#20990](https://github.com/webpack/webpack/pull/20990)) + +- HTML-entry pipeline (`experiments.html` + `experiments.css`): emit `` tags for CSS chunks reachable from 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..5f175f0a27a --- /dev/null +++ b/examples/asset/README.md @@ -0,0 +1,500 @@ +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 */ +/*! 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 */ +/*! 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 */ +/*! 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 */ +/*! exports [not provided] [no usage info] */ +/*! runtime requirements: module */ +/***/ ((module) => { + +module.exports = "a Ä€ ð€€ æ–‡ 🦄 Text\n"; + +/***/ }), +/* 6 */ +/*!***************************!*\ + !*** ./content/bytes.svg ***! + \***************************/ +/*! default exports */ +/*! 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; +/******/ } +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = __webpack_modules__; +/******/ +/************************************************************************/ +/******/ /* 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/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; +/******/ +/******/ // 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 +/******/ }; +/******/ +/******/ // 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.8 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.82 KiB (runtime) [entry] [rendered] + > ./example.js main + dependent modules 9.59 KiB (javascript) 15.2 KiB (asset) [dependent] 6 modules + runtime modules 1.82 KiB 5 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..a0c593f2611 --- /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 83 KiB [emitted] (name: main) +runtime modules 1.07 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..f40da05e7ad 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,369 @@ 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; /******/ } -/******/ -/******/ // 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]); +/******/ +/******/ // 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 { -/******/ // 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'); -/******/ +/******/ 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 + ".[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; +/******/ +/******/ +/******/ 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 = { +/******/ 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; +/******/ installedChunkData[1](error); +/******/ } +/******/ } +/******/ }; +/******/ __webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId); +/******/ } else installedChunks[chunkId] = 0; /******/ } -/******/ }; -/******/ 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; -/******/ -/******/ -/******/ // run deferred modules from other chunks -/******/ checkDeferredModules(); -/******/ }) +/******/ +/******/ 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 +407,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.8 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.6 KiB = runtime~main.[chunkhash].js 12.8 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.03 KiB [entry] [rendered] + > ./example main + runtime modules 8.03 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.14 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.04 KiB [entry] [rendered] + > ./example main + runtime modules 8.04 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..1202d0af4fa 100644 --- a/examples/code-splitted-require.context-amd/README.md +++ b/examples/code-splitted-require.context-amd/README.md @@ -16,207 +16,225 @@ 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; /******/ } -/******/ -/******/ // 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'); -/******/ +/******/ +/******/ // 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 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; +/******/ 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 +242,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 +292,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 +308,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 +320,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 +340,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 9 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.92 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 4.92 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.88 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.92 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 4.92 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..949706fb231 100644 --- a/examples/code-splitted-require.context/README.md +++ b/examples/code-splitted-require.context/README.md @@ -16,207 +16,225 @@ 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; /******/ } -/******/ -/******/ // 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'); -/******/ +/******/ +/******/ // 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 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; +/******/ 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 +242,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 +292,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 +308,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 +320,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 +340,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.91 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.92 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 4.92 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.85 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.92 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 4.92 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..aed36e65b68 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,283 @@ 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; /******/ } -/******/ -/******/ // 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'); -/******/ +/******/ +/******/ // 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 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; +/******/ 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 +298,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.62 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.92 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 4.92 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.87 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.92 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 4.92 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..63914813fdd --- /dev/null +++ b/examples/code-splitting-depend-on-advanced/README.md @@ -0,0 +1,707 @@ +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; +/******/ 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.7 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 13 KiB = runtime.js 11.7 KiB react-vendors.js 1.33 KiB +Entrypoint other-vendors 13.8 KiB = runtime.js 11.7 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.15 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.15 KiB 10 modules +webpack X.X.X compiled successfully +``` + +## Production mode + +``` +asset runtime.js 2.59 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.79 KiB = runtime.js 2.59 KiB react-vendors.js 201 bytes +Entrypoint other-vendors 2.82 KiB = runtime.js 2.59 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.15 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.15 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..f9f3acf687e --- /dev/null +++ b/examples/code-splitting-depend-on-simple/README.md @@ -0,0 +1,395 @@ +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.21 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..f51da67659a 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,383 @@ 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: module, __webpack_require__.o, __webpack_require__, __webpack_require__.e, __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; /******/ } -/******/ -/******/ // 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]); +/******/ +/******/ // 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 { -/******/ // 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'); -/******/ +/******/ 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 + ".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/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 = { +/******/ 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; +/******/ 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 = 4); -/******/ }) +/******/ +/******/ 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 +412,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.3 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.32 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 7.32 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.16 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.05 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 7.05 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..f5e6f89d541 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,335 @@ 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: module, __webpack_require__.o, __webpack_require__, __webpack_require__.e, __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; /******/ } -/******/ -/******/ // 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]); +/******/ +/******/ // 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 { -/******/ // 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'); -/******/ +/******/ 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 + ".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/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 = { +/******/ 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; +/******/ 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 = 4); -/******/ }) +/******/ +/******/ 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 +389,10 @@ getTemplate("bar.noimport"); getTemplate("baz.noimport"); +})(); -/***/ }) -/******/ ]); +/******/ })() +; ``` # Info @@ -327,79 +400,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.9 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.9 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 5.9 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.7 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.9 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 5.9 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..f1af1f633ba 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,335 @@ 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: module, __webpack_require__.o, __webpack_require__, __webpack_require__.e, __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; /******/ } -/******/ -/******/ // 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]); +/******/ +/******/ // 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 { -/******/ // 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'); -/******/ +/******/ 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 + ".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/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 = { +/******/ 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; +/******/ 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 = 4); -/******/ }) +/******/ +/******/ 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 +376,10 @@ getTemplate("baz"); +})(); -/***/ }) -/******/ ]); +/******/ })() +; ``` # Info @@ -314,79 +387,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.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 ^\.\/.*$ 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.9 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 5.9 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.66 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.9 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 5.9 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..885b97eab2f 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,340 @@ 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: module, __webpack_require__.o, __webpack_require__, __webpack_require__.e, __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; /******/ } -/******/ -/******/ // 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]); +/******/ +/******/ // 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 { -/******/ // 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'); -/******/ +/******/ 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 + ".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/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 = { +/******/ 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; +/******/ 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 = 4); -/******/ }) +/******/ +/******/ 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 +375,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 12 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.9 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 5.9 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.68 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.9 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 5.9 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..2b68905239b 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,288 @@ 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; /******/ } -/******/ -/******/ // 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'); -/******/ +/******/ +/******/ // 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 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; +/******/ 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 +323,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 +332,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.43 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.92 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 4.92 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.79 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.92 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 4.92 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..a9e40340805 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,18 @@ 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__) { +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: module, __webpack_require__ */ +/*! CommonJS bailout: module.exports is used directly at 1:0-14 */ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { module.exports = { cool: "stuff", @@ -135,8 +50,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 +61,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 +117,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.25 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..4e9adce393b 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,170 @@ 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 +350,312 @@ 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 +663,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.09 KiB [emitted] (name: pageA) +asset pageB.js 5.8 KiB [emitted] (name: pageB) +asset pageC.js 5.74 KiB [emitted] (name: pageC) +asset vendor.js 713 bytes [emitted] (name: vendor) (id hint: vendor) +Entrypoint pageA 7.15 KiB = vendor.js 713 bytes commons-utility2_js.js 372 bytes pageA.js 6.09 KiB +Entrypoint pageB 7.23 KiB = vendor.js 713 bytes commons-utility2_js.js 372 bytes commons-utility3_js.js 372 bytes pageB.js 5.8 KiB +Entrypoint pageC 6.47 KiB = commons-utility2_js.js 372 bytes commons-utility3_js.js 372 bytes pageC.js 5.74 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..51614eba2f6 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,353 @@ 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; /******/ } -/******/ -/******/ // 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'); -/******/ +/******/ +/******/ // 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 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; +/******/ 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 - -``` javascript -(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[3],{ +# dist/reusableComponent_js.output.js -/***/ 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 +452,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 9.05 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.92 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 4.92 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.85 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.92 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 4.92 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..68bf4d66dc8 --- /dev/null +++ b/examples/css/README.md @@ -0,0 +1,582 @@ +# example.js + +```javascript +import "./style.css"; +import "./style2.css"; +import { main } from "./style.module.css"; +import("./lazy-style.css"); + +document.getElementsByTagName("main")[0].className = main; +``` + +# style.css + +```javascript +@import "style-imported.css"; +@import "https://fonts.googleapis.com/css?family=Open+Sans"; + +body { + background: green; + font-family: "Open Sans"; +} +``` + +# dist/output.js + +```javascript +/******/ (() => { // webpackBootstrap +/******/ "use strict"; +/******/ var __webpack_modules__ = ({ + +/***/ 6 +/*!******************************!*\ + !*** css ./style.module.css ***! + \******************************/ +/*! namespace exports */ +/*! 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; +/******/ 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; +/******/ 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: darkblue; +} + +@media (min-width: 1024px) { + .zI6JBT { + color: green; + } +} + +@supports (display: grid) { + .zI6JBT { + display: grid + } +} +``` + +# dist/1.output.css + +```javascript +/*!****************************!*\ + !*** css ./lazy-style.css ***! + \****************************/ +body { + color: blue; +} +``` + +# Info + +## Unoptimized + +``` +assets by path *.js 15.5 KiB + asset output.js 15.2 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.2 KiB output.css 1.04 KiB 1 auxiliary asset +chunk (runtime: main) output.js, output.css (main) 254 bytes (javascript) 454 bytes (css) 14.6 KiB (asset) 42 bytes (asset-url) 42 bytes (css-import) 8.81 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 8.81 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 176 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.24 KiB + asset output.js 3.16 KiB [emitted] [minimized] (name: main) + asset 822.output.js 85 bytes [emitted] [minimized] +assets by path *.css 475 bytes + asset output.css 451 bytes [emitted] (name: main) + asset 822.output.css 24 bytes [emitted] +asset 89a353e9c515885abd8e.png 14.6 KiB [emitted] [immutable] [from: images/file.png] (auxiliary name: main) +Entrypoint main 3.6 KiB (14.6 KiB) = output.js 3.16 KiB output.css 451 bytes 1 auxiliary asset +chunk (runtime: main) output.js, output.css (main) 454 bytes (css) 14.6 KiB (asset) 568 bytes (javascript) 42 bytes (asset-url) 42 bytes (css-import) 8.58 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 8.58 KiB 8 modules + dependent modules 14.6 KiB (asset) 42 bytes (javascript) 42 bytes (asset-url) 79 bytes (css) 42 bytes (css-import) [dependent] 3 modules + built modules 526 bytes (javascript) 375 bytes (css) [built] + ./example.js + 5 modules 435 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 91 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..b44731310a8 --- /dev/null +++ b/examples/css/example.js @@ -0,0 +1,6 @@ +import "./style.css"; +import "./style2.css"; +import { main } from "./style.module.css"; +import("./lazy-style.css"); + +document.getElementsByTagName("main")[0].className = 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/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..6dea2beb4e5 --- /dev/null +++ b/examples/css/template.md @@ -0,0 +1,49 @@ +# example.js + +```javascript +_{{example.js}}_ +``` + +# style.css + +```javascript +_{{style.css}}_ +``` + +# 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}}_ +``` + +# 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..107b4bd11d3 --- /dev/null +++ b/examples/css/webpack.config.js @@ -0,0 +1,13 @@ +"use strict"; + +/** @type {import("webpack").Configuration} */ +const config = { + output: { + uniqueName: "app" + }, + experiments: { + css: true + } +}; + +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..eb381d348d1 --- /dev/null +++ b/examples/custom-javascript-parser/README.md @@ -0,0 +1,590 @@ +# 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 */ +/** @typedef {Set} Semicolons */ + +/** + * @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 = []; + /** @type {Semicolons} */ + const semicolons = new Set(); + + const ast = + /** @type {import("estree").Program} */ + ( + acorn.parse(sourceCode, { + ...options, + onComment: options.comments ? comments : undefined, + onInsertedSemicolon: options.semicolons + ? // Set semicolons + /** + * @param {number} pos a position of semicolon + * @returns {Semicolons} set with semicolon positions + */ + (pos) => semicolons.add(pos) + : undefined + }) + ); + + return { ast, comments, semicolons }; +}; + +module.exports = acornParse; +``` + +### oxc + +Implementation example: + +```javascript +"use strict"; + +const oxc = require("oxc-parser"); + +/** @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 */ +/** @typedef {Set} Semicolons */ + +class LocationResolver { + /** + * @param {string} sourceCode source code + */ + constructor(sourceCode) { + const len = sourceCode.length; + let ls = new Uint32Array(Math.ceil(len / 40) + 10); + ls[0] = 0; + let count = 1; + + for (let i = 0; i < len; i++) { + if (sourceCode.charCodeAt(i) === 10) { + // '\n' + if (count >= ls.length) { + const newLs = new Uint32Array(ls.length * 2); + newLs.set(ls); + ls = newLs; + } + ls[count++] = i + 1; + } + } + + this.lineStarts = ls.subarray(0, count); + this.lsLen = count; + /** @type {[number, number, number, number]} */ + this.coords = [0, 0, 0, 0]; + } + + /** + * @param {number} start start + * @param {number} end end + * @returns {[number, number, number, number]} location + */ + getLocation(start, end) { + const ls = this.lineStarts; + + // Search Start + let sLow = 0; + let sHigh = this.lsLen - 1; + while (sLow <= sHigh) { + const mid = (sLow + sHigh) >>> 1; + if (ls[mid] <= start) sLow = mid + 1; + else sHigh = mid - 1; + } + + // Search End + let eLow = 0; + let eHigh = this.lsLen - 1; + while (eLow <= eHigh) { + const mid = (eLow + eHigh) >>> 1; + if (ls[mid] <= end) eLow = mid + 1; + else eHigh = mid - 1; + } + + const res = this.coords; + res[0] = sHigh + 1; // Line + res[1] = start - ls[sHigh]; // Col + res[2] = eHigh + 1; // Line + res[3] = end - ls[eHigh]; // Col + + return res; + } +} + +/** + * @param {string} sourceCode source code + * @returns {Semicolons} semicolons + */ +const collectSemicolons = (sourceCode) => { + const semiSet = new Set(); + let pos = sourceCode.indexOf(";"); + + while (pos !== -1) { + semiSet.add(pos); + pos = sourceCode.indexOf(";", pos + 1); + } + + return semiSet; +}; + +/** + * @template {Node} T + * @typedef {T & { start: number, end: number, range: [number, number] }} NodeWithRange + */ + +/** + * @template {Node} T + * @typedef {T & { start: number, end: number, range: [number, number], loc: SourceLocation }} NodeWithRangeAndLocation + */ + +/** + * @template {Node} T + * @param {NodeWithRange} node node + * @param {LocationResolver} locationResolver location resolver + * @param {WeakMap, NodeWithRangeAndLocation>} cache cache + * @returns {NodeWithRangeAndLocation} proxy node + */ +const createLazyProxy = (node, locationResolver, cache) => { + if (cache.has(node)) { + return /** @type {NodeWithRangeAndLocation} */ (cache.get(node)); + } + + const proxy = + /** @type {NodeWithRangeAndLocation} */ + ( + new Proxy(node, { + has(target, prop) { + return prop === "loc" || prop in target; + }, + get(target, prop, receiver) { + if (prop === "loc") { + const loc = locationResolver.getLocation(target.start, target.end); + return { + start: { + line: loc[0], + offset: loc[1] + }, + end: { + line: loc[2], + offset: loc[3] + } + }; + } + + const value = Reflect.get(target, prop, receiver); + + if (Array.isArray(value)) { + return value.map((node) => { + if ( + !node || + typeof node !== "object" || + Array.isArray(node) || + !node.type + ) { + return node; + } + + return createLazyProxy(node, locationResolver, cache); + }); + } else if ( + value && + typeof value === "object" && + /** @type {T} */ (value).type + ) { + return createLazyProxy( + /** @type {NodeWithRange} */ + ( + /** @type {unknown} */ + (value) + ), + locationResolver, + cache + ); + } + + return value; + } + }) + ); + + cache.set(node, proxy); + + return proxy; +}; + +/** + * @param {string} sourceCode the source code + * @param {ParseOptions} options options + * @returns {ParseResult} the parsed result + */ +const oxcParse = (sourceCode, options) => { + const locationResolver = new LocationResolver(sourceCode); + + // We need only automatic semicolon insertion position, but there is no API, so let's collect all semicolons + // But there are rooms to improve it + /** @type {Semicolons} */ + const semicolons = options.semicolons + ? collectSemicolons(sourceCode) + : new Set(); + + const result = oxc.parseSync("file.js", sourceCode, { + ...options, + 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, loc: SourceLocation })[]} */ + (result.comments); + + for (const comment of comments) { + Object.defineProperty(comment, "loc", { + get() { + const loc = locationResolver.getLocation(comment.start, comment.end); + return { + start: { + line: loc[0], + column: loc[1] + }, + end: { + line: loc[2], + column: loc[3] + } + }; + }, + configurable: true, + enumerable: true + }); + Object.defineProperty(comment, "range", { + get() { + return [comment.start, comment.end]; + }, + configurable: true, + enumerable: true + }); + } + + const ast = createLazyProxy( + /** @type {NodeWithRange} */ + (result.program), + locationResolver, + new WeakMap() + ); + return { ast, comments, semicolons }; +}; + +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 */ +/** @typedef {Set} Semicolons */ + +/** + * @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 = []; + /** @type {Semicolons} */ + const semicolons = new Set(); + + 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, + onInsertedSemicolon: options.semicolons + ? // Set semicolons + /** + * @param {number} pos a position of semicolon + * @returns {Semicolons} set with semicolon positions + */ + (pos) => semicolons.add(pos) + : undefined + }) + ); + + return { ast, comments, semicolons }; +}; + +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.4 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-6 + ./async-loaded.js 24 bytes [built] [code generated] + [exports: answer] + [used exports unknown] + import() ./async-loaded ./example.js 6:0-0 +chunk (runtime: main) output.js (main) 457 bytes (javascript) 5.9 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 5.9 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.4 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.9 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 5.9 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.4 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.9 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 5.9 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.23 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-6 + ./async-loaded.js 24 bytes [built] [code generated] + [exports: answer] + import() ./async-loaded ./example.js + 2 modules ./example.js 6:0-0 +chunk (runtime: main) output.js (main) 457 bytes (javascript) 5.9 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 5.9 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.23 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.9 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 5.9 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.23 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.9 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 5.9 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..6984cf3759c --- /dev/null +++ b/examples/custom-javascript-parser/internals/acorn-parse.js @@ -0,0 +1,43 @@ +"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 */ +/** @typedef {Set} Semicolons */ + +/** + * @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 = []; + /** @type {Semicolons} */ + const semicolons = new Set(); + + const ast = + /** @type {import("estree").Program} */ + ( + acorn.parse(sourceCode, { + ...options, + onComment: options.comments ? comments : undefined, + onInsertedSemicolon: options.semicolons + ? // Set semicolons + /** + * @param {number} pos a position of semicolon + * @returns {Semicolons} set with semicolon positions + */ + (pos) => semicolons.add(pos) + : undefined + }) + ); + + return { ast, comments, semicolons }; +}; + +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..2c0edfb54f7 --- /dev/null +++ b/examples/custom-javascript-parser/internals/bench.mjs @@ -0,0 +1,44 @@ +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, + semicolons: true +}; + +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..61fca25706d --- /dev/null +++ b/examples/custom-javascript-parser/internals/meriyah-parse.js @@ -0,0 +1,59 @@ +"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 */ +/** @typedef {Set} Semicolons */ + +/** + * @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 = []; + /** @type {Semicolons} */ + const semicolons = new Set(); + + 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, + onInsertedSemicolon: options.semicolons + ? // Set semicolons + /** + * @param {number} pos a position of semicolon + * @returns {Semicolons} set with semicolon positions + */ + (pos) => semicolons.add(pos) + : undefined + }) + ); + + return { ast, comments, semicolons }; +}; + +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..44892b46068 --- /dev/null +++ b/examples/custom-javascript-parser/internals/oxc-parse.js @@ -0,0 +1,242 @@ +"use strict"; + +const oxc = require("oxc-parser"); + +/** @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 */ +/** @typedef {Set} Semicolons */ + +class LocationResolver { + /** + * @param {string} sourceCode source code + */ + constructor(sourceCode) { + const len = sourceCode.length; + let ls = new Uint32Array(Math.ceil(len / 40) + 10); + ls[0] = 0; + let count = 1; + + for (let i = 0; i < len; i++) { + if (sourceCode.charCodeAt(i) === 10) { + // '\n' + if (count >= ls.length) { + const newLs = new Uint32Array(ls.length * 2); + newLs.set(ls); + ls = newLs; + } + ls[count++] = i + 1; + } + } + + this.lineStarts = ls.subarray(0, count); + this.lsLen = count; + /** @type {[number, number, number, number]} */ + this.coords = [0, 0, 0, 0]; + } + + /** + * @param {number} start start + * @param {number} end end + * @returns {[number, number, number, number]} location + */ + getLocation(start, end) { + const ls = this.lineStarts; + + // Search Start + let sLow = 0; + let sHigh = this.lsLen - 1; + while (sLow <= sHigh) { + const mid = (sLow + sHigh) >>> 1; + if (ls[mid] <= start) sLow = mid + 1; + else sHigh = mid - 1; + } + + // Search End + let eLow = 0; + let eHigh = this.lsLen - 1; + while (eLow <= eHigh) { + const mid = (eLow + eHigh) >>> 1; + if (ls[mid] <= end) eLow = mid + 1; + else eHigh = mid - 1; + } + + const res = this.coords; + res[0] = sHigh + 1; // Line + res[1] = start - ls[sHigh]; // Col + res[2] = eHigh + 1; // Line + res[3] = end - ls[eHigh]; // Col + + return res; + } +} + +/** + * @param {string} sourceCode source code + * @returns {Semicolons} semicolons + */ +const collectSemicolons = (sourceCode) => { + const semiSet = new Set(); + let pos = sourceCode.indexOf(";"); + + while (pos !== -1) { + semiSet.add(pos); + pos = sourceCode.indexOf(";", pos + 1); + } + + return semiSet; +}; + +/** + * @template {Node} T + * @typedef {T & { start: number, end: number, range: [number, number] }} NodeWithRange + */ + +/** + * @template {Node} T + * @typedef {T & { start: number, end: number, range: [number, number], loc: SourceLocation }} NodeWithRangeAndLocation + */ + +/** + * @template {Node} T + * @param {NodeWithRange} node node + * @param {LocationResolver} locationResolver location resolver + * @param {WeakMap, NodeWithRangeAndLocation>} cache cache + * @returns {NodeWithRangeAndLocation} proxy node + */ +const createLazyProxy = (node, locationResolver, cache) => { + if (cache.has(node)) { + return /** @type {NodeWithRangeAndLocation} */ (cache.get(node)); + } + + const proxy = + /** @type {NodeWithRangeAndLocation} */ + ( + new Proxy(node, { + has(target, prop) { + return prop === "loc" || prop in target; + }, + get(target, prop, receiver) { + if (prop === "loc") { + const loc = locationResolver.getLocation(target.start, target.end); + return { + start: { + line: loc[0], + offset: loc[1] + }, + end: { + line: loc[2], + offset: loc[3] + } + }; + } + + const value = Reflect.get(target, prop, receiver); + + if (Array.isArray(value)) { + return value.map((node) => { + if ( + !node || + typeof node !== "object" || + Array.isArray(node) || + !node.type + ) { + return node; + } + + return createLazyProxy(node, locationResolver, cache); + }); + } else if ( + value && + typeof value === "object" && + /** @type {T} */ (value).type + ) { + return createLazyProxy( + /** @type {NodeWithRange} */ + ( + /** @type {unknown} */ + (value) + ), + locationResolver, + cache + ); + } + + return value; + } + }) + ); + + cache.set(node, proxy); + + return proxy; +}; + +/** + * @param {string} sourceCode the source code + * @param {ParseOptions} options options + * @returns {ParseResult} the parsed result + */ +const oxcParse = (sourceCode, options) => { + const locationResolver = new LocationResolver(sourceCode); + + // We need only automatic semicolon insertion position, but there is no API, so let's collect all semicolons + // But there are rooms to improve it + /** @type {Semicolons} */ + const semicolons = options.semicolons + ? collectSemicolons(sourceCode) + : new Set(); + + const result = oxc.parseSync("file.js", sourceCode, { + ...options, + 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, loc: SourceLocation })[]} */ + (result.comments); + + for (const comment of comments) { + Object.defineProperty(comment, "loc", { + get() { + const loc = locationResolver.getLocation(comment.start, comment.end); + return { + start: { + line: loc[0], + column: loc[1] + }, + end: { + line: loc[2], + column: loc[3] + } + }; + }, + configurable: true, + enumerable: true + }); + Object.defineProperty(comment, "range", { + get() { + return [comment.start, comment.end]; + }, + configurable: true, + enumerable: true + }); + } + + const ast = createLazyProxy( + /** @type {NodeWithRange} */ + (result.program), + locationResolver, + new WeakMap() + ); + return { ast, comments, semicolons }; +}; + +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..66ff3b2c2b2 --- /dev/null +++ b/examples/custom-json-modules/README.md @@ -0,0 +1,265 @@ +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 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 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 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 5.88 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..c2738c3557f 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,96 +45,17 @@ export function square(n) { # dist/vendor.js -``` javascript -var vendor_lib_f3fbcfb4ec389ba5bbf0 = -``` -
/******/ (function(modules) { /* webpackBootstrap */ }) - -``` js -/******/ (function(modules) { // webpackBootstrap -/******/ // The module cache -/******/ var installedModules = {}; -/******/ -/******/ // The require function -/******/ function __webpack_require__(moduleId) { -/******/ -/******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) { -/******/ return installedModules[moduleId].exports; -/******/ } -/******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, -/******/ exports: {} -/******/ }; -/******/ -/******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ -/******/ // Return the exports of the module -/******/ return module.exports; -/******/ } -/******/ -/******/ -/******/ // expose the modules object (__webpack_modules__) -/******/ __webpack_require__.m = modules; -/******/ -/******/ // expose the module cache -/******/ __webpack_require__.c = installedModules; -/******/ -/******/ // define getter function for harmony exports -/******/ __webpack_require__.d = function(exports, name, getter) { -/******/ if(!__webpack_require__.o(exports, name)) { -/******/ Object.defineProperty(exports, name, { -/******/ 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 -/******/ ([ +```javascript +var vendor_lib_0b536460c09d26f43a6b; +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ([ /* 0 */ /*!****************!*\ !*** dll main ***! \****************/ -/*! no static exports found */ -/*! all exports used */ -/***/ (function(module, exports, __webpack_require__) { +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: __webpack_require__, module */ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { module.exports = __webpack_require__; @@ -138,26 +64,119 @@ module.exports = __webpack_require__; /*!*****************************************!*\ !*** ../node_modules/example-vendor.js ***! \*****************************************/ -/*! exports provided: square */ -/*! all exports used */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/*! 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 (binding) */ __webpack_require__.d(__webpack_exports__, "square", function() { return square; }); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ square: () => (/* binding */ square) +/* harmony export */ }); function square(n) { return n * n; } /***/ }) -/******/ ]); +/******/ ]); +``` + +
/* 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/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 +/******/ +/******/ // 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_0b536460c09d26f43a6b = __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_0b536460c09d26f43a6b","content":{"../node_modules/example-vendor.js":{"id":1,"buildMeta":{"exportsType":"namespace"},"exports":["square"]}}} ``` # Info @@ -165,32 +184,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.27 KiB [emitted] (name: main) +chunk (runtime: main) vendor.js (main) 57 bytes (javascript) 1.07 KiB (runtime) [entry] [rendered] + > main + runtime modules 1.07 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.07 KiB (runtime) [entry] [rendered] + > main + runtime modules 1.07 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..7ba5f7421e3 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,106 @@ 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_0b536460c09d26f43a6b ***! + \******************************************************************************************************/ +/*! 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_0b536460c09d26f43a6b */ 2))(1); + +/***/ }), +/* 2 */ +/*!**************************************************!*\ + !*** external "vendor_lib_0b536460c09d26f43a6b" ***! + \**************************************************/ +/*! dynamic exports */ +/*! exports [maybe provided (runtime-defined)] [no usage info] */ +/*! runtime requirements: module */ +/***/ ((module) => { + +"use strict"; +module.exports = vendor_lib_0b536460c09d26f43a6b; + +/***/ }) +/******/ ]); +``` -``` javascript -/******/ (function(modules) { // webpackBootstrap +
/* 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 = 0); -/******/ }) +/******/ +/************************************************************************/ +/******/ /* 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 }); +/******/ }; +/******/ })(); +/******/ /************************************************************************/ ```
-``` 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)); +console.log((0,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) { - -module.exports = vendor_lib_f3fbcfb4ec389ba5bbf0; - -/***/ }) -/******/ ]); +/******/ })() +; ``` # Info @@ -173,45 +161,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.41 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 339 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 ``` + + + + + + +

HTML modules

+ + + logo + + + responsive logo + + + + + + + + +``` + +# src/app.js + +```javascript +// Loaded by the HTML entry via ` + + + + + +``` + +# Info + +## Unoptimized + +``` +assets by chunk 35.9 KiB (auxiliary name: page) + 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 index.html 1.7 KiB [emitted] (auxiliary name: page) +assets by path *.js 11.4 KiB + asset __html_6d047296_1.js 6.84 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 __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) 1.24 KiB (javascript) 1.23 KiB (html) 65 bytes (css-text) 34.2 KiB (asset) 84 bytes (asset-url) [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 chunk 35.5 KiB (auxiliary name: page) + 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 index.html 1.28 KiB [emitted] (auxiliary name: page) +assets by path *.js 4.23 KiB + asset page.js 2.29 KiB [emitted] [minimized] (name: page) + asset __html_6d047296_1.js 1.8 KiB [emitted] [minimized] (name: __html_6d047296_1) + asset __html_6d047296_2.js 143 bytes [emitted] [minimized] (name: __html_6d047296_2) +asset __html_6d047296_0.css 65 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] + [no exports used] + entry ./styles.css __html_6d047296_0 +chunk (runtime: page) page.js (page) 1.28 KiB (javascript) 1.23 KiB (html) 65 bytes (css-text) 34.2 KiB (asset) 84 bytes (asset-url) 1.21 KiB (runtime) [entry] [rendered] + > ./src/index.html page + dependent modules 34.2 KiB (asset) 42 bytes (javascript) 84 bytes (asset-url) 65 bytes (css-text) [dependent] 3 modules + runtime modules 1.21 KiB 2 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) 14.6 KiB (asset) 583 bytes (javascript) 42 bytes (asset-url) 3.67 KiB (runtime) [entry] [rendered] + > ./app.js __html_6d047296_1 + runtime modules 3.67 KiB 5 modules + dependent modules 14.6 KiB (asset) 42 bytes (javascript) 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..ab4ec8dcce7 --- /dev/null +++ b/examples/html/template.md @@ -0,0 +1,64 @@ +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. + +/***/ }, + +/***/ 8 +/*!*********************************************!*\ + !*** 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; + reject(__webpack_error__); + }, "mfeBBB"); +}).then(() => (mfeBBB)); + +/***/ }, + +/***/ 10 +/*!*********************************************!*\ + !*** 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; + 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": [ +/******/ 7, +/******/ 9 +/******/ ], +/******/ "webpack_container_remote_mfe-c_Component2": [ +/******/ 23 +/******/ ] +/******/ }; +/******/ const idToExternalAndNameMapping = { +/******/ "7": [ +/******/ "default", +/******/ "./Component", +/******/ 8 +/******/ ], +/******/ "9": [ +/******/ "default", +/******/ "./Component", +/******/ 10 +/******/ ], +/******/ "23": [ +/******/ "default", +/******/ "./Component2", +/******/ 10 +/******/ ] +/******/ }; +/******/ __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.7", () => (__webpack_require__.e("vendors-node_modules_react_index_js").then(() => (() => (__webpack_require__(/*! ../../node_modules/react/index.js */ 21)))))); +/******/ initExternal(8); +/******/ initExternal(10); +/******/ } +/******/ 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,7], () => (__webpack_require__.e("vendors-node_modules_react_index_js").then(() => (() => (__webpack_require__(/*! react */ 21))))))) +/******/ }; +/******/ // no consumes in initial chunks +/******/ const chunkMapping = { +/******/ "src_bootstrap_js": [ +/******/ 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 = { +/******/ "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; +/******/ 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 */ 6)))))); +/******/ register("react", "19.2.7", () => (__webpack_require__.e("vendors-node_modules_react_index_js").then(() => (() => (__webpack_require__(/*! ../../node_modules/react/index.js */ 309)))))); +/******/ } +/******/ 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 */ 309))))))), +/******/ 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 */ 6))))))) +/******/ }; +/******/ // 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; +/******/ 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 */ 6))))); + } +}; +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 */ 8)))))); +/******/ register("lodash/random", "4.18.1", () => (__webpack_require__.e("vendors-node_modules_lodash_random_js").then(() => (() => (__webpack_require__(/*! ../../node_modules/lodash/random.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/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 */ 8))))))), +/******/ 7: () => (loadStrictVersion("default", "lodash/random", false, [1,4,17,19], () => (__webpack_require__.e("vendors-node_modules_lodash_random_js").then(() => (() => (__webpack_require__(/*! lodash/random */ 311))))))) +/******/ }; +/******/ // no consumes in initial chunks +/******/ const chunkMapping = { +/******/ "webpack_sharing_consume_default_react": [ +/******/ 4 +/******/ ], +/******/ "src-c_Component_js": [ +/******/ 5 +/******/ ], +/******/ "src-c_LazyComponent_js": [ +/******/ 7 +/******/ ] +/******/ }; +/******/ 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; +/******/ 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 43.3 KiB [emitted] + asset app.js 30.5 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.3 KiB (runtime) [entry] [rendered] + > ./src/index.js app + runtime modules 19.3 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.7 = ../../node_modules/react/index.js 42 bytes [built] [code generated] + chunk (runtime: app) src_bootstrap_js.js 26.3 KiB (javascript) 42 bytes (consume-shared) 12 bytes (remote) 12 bytes (share-init) [rendered] + > ./bootstrap ./src/index.js 10:0-21 + dependent modules 25.9 KiB (javascript) 42 bytes (consume-shared) 12 bytes (remote) 12 bytes (share-init) [dependent] 16 modules + ./src/bootstrap.js 380 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.7 = ../../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 7: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.8 KiB [emitted] (name: mfeBBB) + asset src-b_Component_js.js 1.9 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.7 = ../../node_modules/react/index.js 42 bytes [built] [code generated] + chunk (runtime: mfeBBB) src-b_Component_js.js 752 bytes (javascript) 84 bytes (consume-shared) [rendered] + > ./src-b/Component container entry ./Component + dependent modules 84 bytes [dependent] 2 modules + ./src-b/Component.js 752 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.7 = ../../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.3 KiB [emitted] (name: mfeCCC) + asset src-c_LazyComponent_js.js 2.04 KiB [emitted] + asset src-c_Component_js.js 1.81 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 467 bytes (javascript) 42 bytes (consume-shared) [rendered] + > ./src-c/Component container entry ./Component + dependent modules 42 bytes [dependent] 1 module + ./src-c/Component.js 467 bytes [built] [code generated] + chunk (runtime: mfeCCC) src-c_LazyComponent_js.js 504 bytes (javascript) 42 bytes (consume-shared) [rendered] + > ./src-c/LazyComponent container entry ./Component2 + dependent modules 42 bytes [dependent] 1 module + ./src-c/LazyComponent.js 504 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.5 KiB [emitted] [minimized] 1 related asset + asset app.js 7.81 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.3 KiB (runtime) [entry] [rendered] + > ./src/index.js app + runtime modules 19.3 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.7 = ../../node_modules/react/index.js 42 bytes [built] [code generated] + chunk (runtime: app) node_modules_react_index_js.js 17 KiB [rendered] + > consume shared module (default) react@^19.2.7 (singleton) (fallback: ../../node_modules/react/index.js) + > provide shared module (default) react@19.2.7 = ../../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 26.3 KiB (javascript) 42 bytes (consume-shared) 12 bytes (remote) 12 bytes (share-init) [rendered] + > ./bootstrap ./src/index.js 10:0-21 + dependent modules 7.83 KiB (javascript) 42 bytes (consume-shared) 12 bytes (remote) 12 bytes (share-init) [dependent] 5 modules + ./src/bootstrap.js + 11 modules 18.4 KiB [built] [code generated] + chunk (runtime: app) 6 bytes (remote) 6 bytes (share-init) + > mfe-c/Component2 ./src/App.js 7: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.44 KiB [emitted] [minimized] (name: mfeBBB) + asset src-b_Component_js.js 461 bytes [emitted] [minimized] + chunk (runtime: mfeBBB) mfeBBB.js (mfeBBB) 42 bytes (javascript) 84 bytes (share-init) 16.3 KiB (runtime) [entry] [rendered] + > mfeBBB + runtime modules 16.3 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.7 = ../../node_modules/react/index.js 42 bytes [built] [code generated] + chunk (runtime: mfeBBB) node_modules_react_index_js.js 17 KiB [rendered] + > consume shared module (default) react@^19.2.7 (singleton) (fallback: ../../node_modules/react/index.js) + > provide shared module (default) react@19.2.7 = ../../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 752 bytes (javascript) 84 bytes (consume-shared) [rendered] + > ./src-b/Component container entry ./Component + dependent modules 84 bytes [dependent] 2 modules + ./src-b/Component.js 752 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) + > 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.05 KiB [emitted] [minimized] (name: mfeCCC) + asset node_modules_lodash_random_js.js 3.08 KiB [emitted] [minimized] + asset src-c_LazyComponent_js.js 520 bytes [emitted] [minimized] + asset src-c_Component_js.js 475 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 467 bytes (javascript) 42 bytes (consume-shared) [rendered] + > ./src-c/Component container entry ./Component + dependent modules 42 bytes [dependent] 1 module + ./src-c/Component.js 467 bytes [built] [code generated] + chunk (runtime: mfeCCC) src-c_LazyComponent_js.js 504 bytes (javascript) 42 bytes (consume-shared) [rendered] + > ./src-c/LazyComponent container entry ./Component2 + dependent modules 42 bytes [dependent] 1 module + ./src-c/LazyComponent.js 504 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/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..c928bd89f85 --- /dev/null +++ b/examples/module-library/README.md @@ -0,0 +1,167 @@ +# 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.26 KiB [emitted] [javascript module] (name: main) +chunk (runtime: main) output.js (main) 302 bytes (javascript) 1.07 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 1.07 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..0661a692b62 --- /dev/null +++ b/examples/module-worker/README.md @@ -0,0 +1,974 @@ +# 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/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.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/publicPath */ +/******/ (() => { +/******/ __webpack_require__.p = "/dist/"; +/******/ })(); +/******/ +/******/ /* webpack/runtime/import chunk loading */ +/******/ (() => { +/******/ __webpack_require__.b = new URL("./", import.meta.url); +/******/ +/******/ // 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; +/******/ } +/******/ +/******/ } +/******/ +/******/ __webpack_require__.f.j = (chunkId, promises) => { +/******/ // import() 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[1]); +/******/ } else { +/******/ if(true) { // all chunks have JS +/******/ // setup Promise in chunk cache +/******/ let promise = import(__webpack_require__.p + __webpack_require__.u(chunkId)).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 prefetching +/******/ +/******/ // no preloaded +/******/ +/******/ // no external install chunk +/******/ +/******/ // no on chunks loaded +/******/ // no HMR +/******/ +/******/ // no HMR manifest +/******/ })(); +/******/ +/************************************************************************/ +``` + +
+ +``` 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__.* */ +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: "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__.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: "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/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.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/publicPath */ +/******/ (() => { +/******/ __webpack_require__.p = "/dist/"; +/******/ })(); +/******/ +/******/ /* 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; +/******/ } +/******/ +/******/ } +/******/ +/******/ __webpack_require__.f.j = (chunkId, promises) => { +/******/ // import() 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[1]); +/******/ } else { +/******/ if(true) { // all chunks have JS +/******/ // setup Promise in chunk cache +/******/ let promise = import(__webpack_require__.p + __webpack_require__.u(chunkId)).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 prefetching +/******/ +/******/ // no preloaded +/******/ +/******/ // no external install chunk +/******/ +/******/ // no on chunks loaded +/******/ // no HMR +/******/ +/******/ // no HMR manifest +/******/ })(); +/******/ +/************************************************************************/ +``` + +
+ +``` js +let __webpack_exports__ = {}; +/*!************************!*\ + !*** ./chat-worker.js ***! + \************************/ +/*! unknown exports (runtime-defined) */ +/*! runtime requirements: __webpack_require__.e, __webpack_require__, __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__.e(/*! import() */ 936).then(__webpack_require__.bind(__webpack_require__, /*! ./chat-module */ 4)); + add(msg.content, msg.from); + // fallthrough + case "history": + const { history } = await __webpack_require__.e(/*! import() */ 936).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;rPromise.all(Object.keys(o.f).reduce((t,r)=>(o.f[r](e,t),t),[])),o.u=e=>e+".js",o.o=(e,t)=>Object.hasOwn(e,t),o.p="/dist/",(()=>{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=o.o(e,r)?e[r]:void 0;if(0!==n)if(n)s.push(n[1]);else{let i=import(o.p+o.u(r)).then(t,t=>{throw 0!==e[r]&&(e[r]=void 0),t});i=Promise.race([i,new Promise(t=>n=e[r]=[t])]),s.push(n[1]=i)}}})(),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.e(936).then(o.bind(o,936));e(r.content,r.from);case"history":const{history:s}=await o.e(936).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/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.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/publicPath */ +/******/ (() => { +/******/ __webpack_require__.p = "/dist/"; +/******/ })(); +/******/ +/******/ /* 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; +/******/ } +/******/ +/******/ } +/******/ +/******/ __webpack_require__.f.j = (chunkId, promises) => { +/******/ // import() 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[1]); +/******/ } else { +/******/ if(true) { // all chunks have JS +/******/ // setup Promise in chunk cache +/******/ let promise = import(__webpack_require__.p + __webpack_require__.u(chunkId)).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 prefetching +/******/ +/******/ // no preloaded +/******/ +/******/ // no external install chunk +/******/ +/******/ // no on chunks loaded +/******/ // 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__.* */ +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 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;tPromise.all(Object.keys(o.f).reduce((r,t)=>(o.f[t](e,r),r),[])),o.u=e=>e+".js",o.o=(e,r)=>Object.hasOwn(e,r),o.p="/dist/",(()=>{const e={721:0},r=r=>{let{__webpack_esm_ids__:t,__webpack_esm_modules__:s,__webpack_esm_runtime__:n}=r;var a,i,c=0;for(a in s)o.o(s,a)&&(o.m[a]=s[a]);for(n&&n(o);c{let n=o.o(e,t)?e[t]:void 0;if(0!==n)if(n)s.push(n[1]);else{let a=import(o.p+o.u(t)).then(r,r=>{throw 0!==e[t]&&(e[t]=void 0),r});a=Promise.race([a,new Promise(r=>n=e[t]=[r])]),s.push(n[1]=a)}}})(),onmessage=async e=>{const{fibonacci:r}=await o.e(129).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 9.38 KiB [emitted] [javascript module] (name: main) +asset chat.js 7.29 KiB [emitted] [javascript module] (name: chat) +asset workers/fibonacci.js 6.94 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) 3.5 KiB (runtime) [entry] [rendered] + > ./example.js 25:19-31:1 + runtime modules 3.5 KiB 7 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) 3.5 KiB (runtime) [entry] [rendered] + > ./example.js 80:18-84:2 + runtime modules 3.5 KiB 7 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) 3.68 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 3.68 KiB 7 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.55 KiB [emitted] [javascript module] [minimized] (name: main) +asset chat.js 1.28 KiB [emitted] [javascript module] [minimized] (name: chat) +asset workers/fibonacci.js 1.13 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) 3.27 KiB (runtime) [entry] [rendered] + > ./example.js 25:19-31:1 + runtime modules 3.27 KiB 6 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) 3.27 KiB (runtime) [entry] [rendered] + > ./example.js 80:18-84:2 + runtime modules 3.27 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) 3.44 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 3.44 KiB 6 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..a2418198008 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,45 @@ 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 200 bytes [emitted] [minimized] (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] + [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..2573294ae87 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,569 @@ 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; /******/ } -/******/ -/******/ // 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'); -/******/ +/******/ +/******/ // 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 = (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; +/******/ 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; /******/ } -/******/ -/******/ // 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'); -/******/ +/******/ +/******/ // 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 = (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; +/******/ 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 +681,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.7 KiB [emitted] (name: pageA) +asset pageB.js 10.6 KiB [emitted] (name: pageB) +asset shared_js.js 503 bytes [emitted] +asset commons.js 370 bytes [emitted] (name: commons) (id hint: commons) +Entrypoint pageA 11 KiB = commons.js 370 bytes pageA.js 10.7 KiB +Entrypoint pageB 11 KiB = commons.js 370 bytes pageB.js 10.6 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.87 KiB (runtime) [entry] [rendered] + > ./pageA pageA + runtime modules 5.87 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.87 KiB (runtime) [entry] [rendered] + > ./pageB pageB + runtime modules 5.87 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.21 KiB [emitted] [minimized] (name: pageA) +asset pageB.js 2.19 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.3 KiB = commons.js 91 bytes pageA.js 2.21 KiB +Entrypoint pageB 2.28 KiB = commons.js 91 bytes pageB.js 2.19 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.87 KiB (runtime) [entry] [rendered] + > ./pageA pageA + runtime modules 5.87 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.87 KiB (runtime) [entry] [rendered] + > ./pageB pageB + runtime modules 5.87 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..e1bc33a7737 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,300 @@ 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; /******/ } -/******/ -/******/ // 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'); -/******/ +/******/ +/******/ // 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 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; +/******/ 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 +324,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 +361,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.82 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.92 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 4.92 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.96 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.92 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 4.92 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..978497596c3 --- /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.86 KiB [emitted] (name: main) +chunk (runtime: main) output.js (main) 457 bytes (javascript) 16.5 KiB (asset) 1.69 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 1.69 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 522 bytes [emitted] [minimized] (name: main) +chunk (runtime: main) output.js (main) 16.5 KiB (asset) 457 bytes (javascript) 447 bytes (runtime) [entry] [rendered] + > ./example.js main + runtime modules 447 bytes 3 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..b24315a05a3 --- /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.64 MiB [emitted] (name: main) +chunk (runtime: main) output.js (main) 2.27 MiB (javascript) 1.71 KiB (runtime) [entry] + > ./example.js main + cached modules 2.27 MiB (javascript) 1.71 KiB (runtime) [cached] 1528 modules +webpack X.X.X compiled successfully +``` + +## Production mode + +``` +asset output.js 552 KiB [emitted] [minimized] [big] (name: main) 1 related asset +chunk (runtime: main) output.js (main) 2.21 MiB (javascript) 1.71 KiB (runtime) [entry] + > ./example.js main + cached modules 2.21 MiB (javascript) 1.71 KiB (runtime) [cached] 905 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 (552 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 (552 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..49e3bd60bf8 --- /dev/null +++ b/examples/reexport-components/README.md @@ -0,0 +1,263 @@ +# 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; +/******/ 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 an ECMAScript module */ +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.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 an ECMAScript module */ +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};t.f.i=(r,o)=>{e[r]||importScripts(t.p+t.u(r))};var r=self.webpackChunk=self.webpackChunk||[],o=r.push.bind(r);r.push=r=>{let[a,n,s]=r;for(var p in n)t.o(n,p)&&(t.m[p]=n[p]);for(s&&s(t);a.length;)e[a.pop()]=1;o(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.5 KiB [emitted] (name: main) +asset workers/fibonacci.js 5.61 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.33 KiB (runtime) [entry] [rendered] + > ./example.js 80:18-84:2 + runtime modules 2.33 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.88 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 5.88 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.53 KiB [emitted] [minimized] (name: main) +asset workers/fibonacci.js 989 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.33 KiB (runtime) [entry] [rendered] + > ./example.js 80:18-84:2 + runtime modules 2.33 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.88 KiB (runtime) [entry] [rendered] + > ./example.js main + runtime modules 5.88 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..369b800e973
--- /dev/null
+++ b/jest.config.js
@@ -0,0 +1,64 @@
+"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/js",
+		"/test/browsertest/js",
+		"/test/fixtures/temp-cache-fixture",
+		"/test/fixtures/temp-",
+		"/benchmark",
+		"/assembly",
+		"/tooling",
+		"/examples/*/dist",
+		"/coverage",
+		"/.eslintcache"
+	],
+	modulePathIgnorePatterns: [
+		"/.git",
+		"/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$",
+		"/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..0a103644d10 100644
--- a/lib/APIPlugin.js
+++ b/lib/APIPlugin.js
@@ -2,80 +2,394 @@
 	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 REPLACEMENT_TYPES = {
-	__webpack_public_path__: "string",
-	__webpack_require__: "function",
-	__webpack_modules__: "object",
-	__webpack_chunk_load__: "function",
-	__webpack_nonce__: "string"
-};
-/* eslint-enable camelcase */
+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");
+
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */
+/** @typedef {import("./Module").BuildInfo} BuildInfo */
+/** @typedef {import("./javascript/JavascriptModule").JavascriptModuleBuildInfo} JavascriptModuleBuildInfo */
+/** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */
+/** @typedef {import("./javascript/JavascriptParser").Range} Range */
+
+/**
+ * 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_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";
+				}
+
 				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__";
+						}
+					});
+
+					parser.hooks.preStatement.tap(PLUGIN_NAME, (statement) => {
+						if (parser.scope.topLevelScope === true) {
+							if (
+								statement.type === "FunctionDeclaration" &&
+								statement.id &&
+								statement.id.name === "module"
+							) {
+								/** @type {BuildInfo} */
+								(parser.state.module.buildInfo).moduleArgument =
+									"__webpack_module__";
+							} else if (
+								statement.type === "ClassDeclaration" &&
+								statement.id &&
+								statement.id.name === "module"
+							) {
+								/** @type {BuildInfo} */
+								(parser.state.module.buildInfo).moduleArgument =
+									"__webpack_module__";
+							}
+						}
+					});
+
+					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 = /** @type {DependencyLocation} */ (
+										expression.loc
+									);
+									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 = /** @type {DependencyLocation} */ (expr.loc);
+								throw err;
+							});
+						}
+						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 = /** @type {DependencyLocation} */ (expr.loc);
+							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 = /** @type {DependencyLocation} */ (expr.loc);
+								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 = /** @type {DependencyLocation} */ (expr.loc);
+								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 = /** @type {DependencyLocation} */ (expr.loc);
+								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 = /** @type {DependencyLocation} */ (expr.loc);
+								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);
 			}
 		);
 	}
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..7ff2b89f025
--- /dev/null
+++ b/lib/CacheFacade.js
@@ -0,0 +1,375 @@
+/*
+	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) {
+		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) {
+		this._cache = cache;
+		this._name = name;
+		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) {
+		this._cache = cache;
+		this._name = name;
+		this._hashFunction = hashFunction;
+	}
+
+	/**
+	 * 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..1d2c7e0e4b1 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,32 +83,43 @@ 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);
 		/** @type {string=} */
 		this.renderedHash = undefined;
@@ -130,271 +127,487 @@ class Chunk {
 		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 +615,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 +771,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 +779,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 +931,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..34898c71b6d
--- /dev/null
+++ b/lib/ChunkGraph.js
@@ -0,0 +1,2103 @@
+/*
+	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);
+
+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;
+
+		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();
+		}
+
+		/**
+		 * Active state to string.
+		 * @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 strict =
+			module.buildMeta &&
+			/** @type {JavascriptModuleBuildMeta} */ (module.buildMeta)
+				.strictHarmonyModule;
+		return cgm.graphHashesWithConnections.provide(runtime, () => {
+			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..f6903b7578d 100644
--- a/lib/ChunkGroup.js
+++ b/lib/ChunkGroup.js
@@ -2,33 +2,55 @@
 	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 {("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 +59,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 +73,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 +91,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 +160,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 +214,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 +237,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 +251,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 +275,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 +289,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 +325,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 +354,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 +379,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 +419,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 +449,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 +465,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 +479,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 +509,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 +518,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 +526,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..7da5b8d745a 100644
--- a/lib/ChunkTemplate.js
+++ b/lib/ChunkTemplate.js
@@ -2,70 +2,189 @@
 	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 memoize = require("./util/memoize");
 
-/** @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"])
-		};
-	}
+const getJavascriptModulesPlugin = memoize(() =>
+	require("./javascript/JavascriptModulesPlugin")
+);
 
+// 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) => {
+						getJavascriptModulesPlugin()
+							.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) => {
+						getJavascriptModulesPlugin()
+							.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) => {
+						getJavascriptModulesPlugin()
+							.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) => {
+						getJavascriptModulesPlugin()
+							.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..08c6b061e17
--- /dev/null
+++ b/lib/CircularModulesPlugin.js
@@ -0,0 +1,190 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Natsu @xiaoxiaojx
+*/
+
+"use strict";
+
+/** @typedef {import("./Compiler")} Compiler */
+/** @typedef {import("./ModuleGraph")} ModuleGraph */
+/** @typedef {import("./Module")} Module */
+/** @typedef {import("./Module").BuildInfo} BuildInfo */
+
+const PLUGIN_NAME = "CircularModulesPlugin";
+
+/**
+ * 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 involved in circular dependencies
+	 */
+	constructor(circularModules) {
+		/** @type {Set} */
+		this.circularModules = circularModules;
+	}
+
+	/**
+	 * 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 dep = connection.dependency;
+				if (!dep) continue;
+				const target = connection.module;
+				if (!target) continue;
+				// Weak references don't synchronously evaluate the target.
+				if (connection.weak) continue;
+				// Async edges (dynamic import & friends) live in AsyncDependenciesBlock,
+				// so a synchronous dep's parent block is the module itself.
+				if (moduleGraph.getParentBlock(dep) !== module) 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 circular = new Set();
+		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 component = [];
+						let w;
+						do {
+							w = /** @type {number} */ (sccStack.pop());
+							nodeOnStack[w] = 0;
+							component.push(w);
+						} while (w !== v);
+
+						if (component.length > 1 || selfLoops[v]) {
+							for (const idx of component) {
+								circular.add(moduleList[idx]);
+							}
+						}
+					}
+
+					callStack.pop();
+					if (
+						frame.parent !== -1 &&
+						nodeLowLink[v] < nodeLowLink[frame.parent]
+					) {
+						nodeLowLink[frame.parent] = nodeLowLink[v];
+					}
+				}
+			}
+		}
+
+		return new CycleGraph(circular);
+	}
+}
+
+/**
+ * Detects circular dependencies and marks each circular module
+ * via buildInfo.isCircular for downstream consumers.
+ */
+class CircularModulesPlugin {
+	/**
+	 * @param {Compiler} compiler the compiler instance
+	 * @returns {void}
+	 */
+	apply(compiler) {
+		compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
+			compilation.hooks.optimizeModules.tap(PLUGIN_NAME, (modules) => {
+				const { circularModules } = CycleGraph.build(
+					modules,
+					compilation.moduleGraph
+				);
+				for (const m of modules) {
+					/** @type {BuildInfo} */
+					(m.buildInfo).isCircular = circularModules.has(m);
+				}
+			});
+		});
+	}
+}
+
+module.exports = CircularModulesPlugin;
diff --git a/lib/CleanPlugin.js b/lib/CleanPlugin.js
new file mode 100644
index 00000000000..3add811f123
--- /dev/null
+++ b/lib/CleanPlugin.js
@@ -0,0 +1,512 @@
+/*
+	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 Compilation = require("./Compilation");
+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 */
+
+/**
+ * Defines the clean plugin compilation hooks type used by this module.
+ * @typedef {object} CleanPluginCompilationHooks
+ * @property {SyncBailHook<[string], boolean | void>} keep when returning true the file/directory will be kept during cleaning, returning false will clean it and ignore the following plugins and config
+ */
+
+/**
+ * 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);
+		}
+	);
+};
+
+/** @type {WeakMap} */
+const compilationHooksMap = new WeakMap();
+
+const PLUGIN_NAME = "CleanPlugin";
+
+class CleanPlugin {
+	/**
+	 * Returns the attached hooks.
+	 * @param {Compilation} compilation the compilation
+	 * @returns {CleanPluginCompilationHooks} the attached hooks
+	 */
+	static getCompilationHooks(compilation) {
+		if (!(compilation instanceof Compilation)) {
+			throw new TypeError(
+				"The 'compilation' argument must be an instance of Compilation"
+			);
+		}
+		let hooks = compilationHooksMap.get(compilation);
+		if (hooks === undefined) {
+			hooks = {
+				keep: new SyncBailHook(["ignore"])
+			};
+			compilationHooksMap.set(compilation, hooks);
+		}
+		return hooks;
+	}
+
+	/** @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);
+				}
+			}
+		);
+	}
+}
+
+module.exports = CleanPlugin;
+module.exports._getDirectories = getDirectories;
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..326d9a44959 100644
--- a/lib/CompatibilityPlugin.js
+++ b/lib/CompatibilityPlugin.js
@@ -2,69 +2,254 @@
 	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 = /** @type {DependencyLocation} */ (expr.loc);
+								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.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.preStatement.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: /** @type {DependencyLocation} */ (statement.id.loc),
+										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: /** @type {DependencyLocation} */ (pattern.loc),
+									range: /** @type {Range} */ (pattern.range)
 								}
-								parser.state.current.addDependency(dep);
+							});
+							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: /** @type {DependencyLocation} */ (pattern.loc),
+									range: /** @type {Range} */ (pattern.range)
+								}
 							});
+							return true;
+						});
+					// 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 = /** @type {DependencyLocation} */ (expr.loc);
+							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 = /** @type {DependencyLocation} */ (c.loc);
+							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..2a1219e120a 100644
--- a/lib/Compilation.js
+++ b/lib/Compilation.js
@@ -1,1031 +1,4357 @@
 /*
 	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 BuildCycleError = require("./errors/BuildCycleError");
+const ChunkRenderError = require("./errors/ChunkRenderError");
+const CodeGenerationError = require("./errors/CodeGenerationError");
+const {
+	makeWebpackError,
+	tryRunOrWebpackError
+} = require("./errors/HookWebpackError");
+const ModuleDependencyError = require("./errors/ModuleDependencyError");
+const ModuleDependencyWarning = require("./errors/ModuleDependencyWarning");
+const ModuleHashingError = require("./errors/ModuleHashingError");
+const ModuleNotFoundError = require("./errors/ModuleNotFoundError");
+const ModuleRestoreError = require("./errors/ModuleRestoreError");
+const ModuleStoreError = require("./errors/ModuleStoreError");
+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 {
+	compareIds,
+	compareLocations,
+	compareModulesByIdentifier,
+	compareSelect,
+	compareStringsNumeric,
+	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 processAsyncTree = require("./util/processAsyncTree");
+const { getRuntimeKey } = require("./util/runtime");
+const { isSourceEqual } = require("./util/source");
+
+/** @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 {(Record)=} contentHash
+ * @property {(Record)=} contentHashWithLength
+ */
+
+/**
+ * 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 {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 }} 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 {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 */
+
+/**
+ * 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 {HashWithLengthFunction=} hashWithLength
+ * @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 {HashWithLengthFunction=} contentHashWithLength
+ * @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)
+);
+
+const byMessage = compareSelect(
+	(err) => `${err.message}`,
+	compareStringsNumeric
+);
+
+const byModule = compareSelect(
+	(err) => (err.module && err.module.identifier()) || "",
+	compareStringsNumeric
+);
+
+const byLocation = compareSelect((err) => err.loc, compareLocations);
+
+const compareErrors = concatComparators(byModule, byLocation, byMessage);
+
+/**
+ * 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 */
+
+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) {
+		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]>} */
+			executeModule: new SyncHook(["options", "context"]),
+			/** @type {AsyncParallelHook<[ExecuteModuleArgument, ExecuteModuleContext]>} */
+			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]>} */
+			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);
+		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();
 		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
-		);
+		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;
+
+		this.params = params;
+		this.mainTemplate = new MainTemplate(this.outputOptions, this);
+		this.chunkTemplate = new ChunkTemplate(this.outputOptions, this);
 		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 = [];
+		/** @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._modulesCache = this.getCache("Compilation/modules");
+		this._assetsCache = this.getCache("Compilation/assets");
+		this._codeGenerationCache = this.getCache("Compilation/codeGeneration");
 
-		this._buildingModules = new Map();
-		this._rebuildingModules = new Map();
+		const unsafeCache = options.module.unsafeCache;
+		/** @type {boolean} */
+		this._unsafeCache = Boolean(unsafeCache);
+		/** @type {UnsafeCachePredicate} */
+		this._unsafeCachePredicate =
+			typeof unsafeCache === "function" ? unsafeCache : () => true;
+
+		/**
+		 * @private
+		 * @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 ModuleRestoreError(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 ModuleStoreError(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(
+		/**
+		 * On transitive tasks finished.
+		 * @param {WebpackError=} err error
+		 * @returns {void}
+		 */
+		const onTransitiveTasksFinished = (err) => {
+			if (err) return callback(err);
+			this.processDependenciesQueue.decreaseParallelism();
+
+			return callback();
+		};
+
+		/**
+		 * 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) {
+								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 (--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();
+							}
+						} 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;
+			}
+		};
+
+		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));
+		}
+
+		if (--inProgressSorting === 0) onDependenciesSorted();
+	}
+
+	/**
+	 * 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;
+
+		moduleGraph.setResolvedModule(originModule, dependency, module);
+
+		moduleGraph.setIssuerIfUnset(
+			module,
+			originModule !== undefined ? originModule : null
+		);
+
+		this._modules.set(module.identifier(), module);
+		this.modules.add(module);
+		if (this._backCompat) {
+			ModuleGraph.setModuleGraphForModule(module, this.moduleGraph);
+		}
+
+		this._handleModuleBuildAndDependencies(
+			originModule,
 			module,
-			sortedDependencies,
-			this.bail,
-			null,
 			true,
+			false,
+			[dependency],
 			callback
 		);
 	}
 
-	addModuleDependencies(
-		module,
-		dependencies,
-		bail,
-		cacheGroup,
-		recursive,
+	/**
+	 * 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);
+	}
+
+	/**
+	 * 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
 	) {
-		const start = this.profile && Date.now();
-		const currentProfile = this.profile && {};
+		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 ModuleNotFoundError(
+						originModule,
+						err,
+						/** @type {DependencyLocation} */
+						(dependencies.map((d) => d.loc).find(Boolean))
+					);
+					return callback(notFoundError, factoryResult ? result : undefined);
+				}
+				if (!result) {
+					return callback();
+				}
 
-		asyncLib.forEach(
-			dependencies,
-			(item, callback) => {
-				const dependencies = item.dependencies;
+				if (currentProfile !== undefined) {
+					currentProfile.markFactoryEnd();
+				}
 
-				const errorAndCallback = err => {
-					err.origin = module;
-					err.dependencies = dependencies;
-					this.errors.push(err);
-					if (bail) {
-						callback(err);
-					} else {
-						callback();
+				callback(null, factoryResult ? result : result.module);
+			}
+		);
+	}
+
+	/**
+	 * 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)
+		);
+	}
+
+	/**
+	 * 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);
 					}
 				};
-				const warningAndCallback = err => {
-					err.origin = module;
-					this.warnings.push(err);
-					callback();
-				};
+				if (err) {
+					if (factoryResult) applyFactoryResultDependencies();
+					if (dependencies.every((d) => d.optional)) {
+						this.warnings.push(err);
+						return callback();
+					}
+					this.errors.push(err);
+					return callback(err);
+				}
 
-				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);
-							};
+				const newModule =
+					/** @type {ModuleFactoryResult} */
+					(factoryResult).module;
 
-							const errorOrWarningAndCallback = err => {
-								if (isOptional()) {
-									return warningAndCallback(err);
-								} else {
-									return errorAndCallback(err);
-								}
-							};
+				if (!newModule) {
+					applyFactoryResultDependencies();
+					return callback();
+				}
 
-							if (err) {
-								semaphore.release();
-								return errorOrWarningAndCallback(
-									new ModuleNotFoundError(module, err)
-								);
-							}
-							if (!dependentModule) {
-								semaphore.release();
-								return process.nextTick(callback);
-							}
-							if (currentProfile) {
-								afterFactory = Date.now();
-								currentProfile.factory = afterFactory - start;
-							}
+				if (currentProfile !== undefined) {
+					moduleGraph.setProfile(newModule, currentProfile);
+				}
 
-							const iterationDependencies = depend => {
-								for (let index = 0; index < depend.length; index++) {
-									const dep = depend[index];
-									dep.module = dependentModule;
-									dependentModule.addReason(module, dep);
-								}
-							};
+				this.addModule(newModule, (err, _module) => {
+					if (err) {
+						applyFactoryResultDependencies();
+						if (!err.module) {
+							err.module = _module;
+						}
+						this.errors.push(err);
 
-							const addModuleResult = this.addModule(
-								dependentModule,
-								cacheGroup
-							);
-							dependentModule = addModuleResult.module;
-							iterationDependencies(dependencies);
+						return callback(err);
+					}
 
-							const afterBuild = () => {
-								if (currentProfile) {
-									const afterBuilding = Date.now();
-									currentProfile.building = afterBuilding - afterFactory;
-								}
+					const module =
+						/** @type {ModuleWithRestoreFromUnsafeCache} */
+						(_module);
 
-								if (recursive && addModuleResult.dependencies) {
-									this.processModuleDependencies(dependentModule, callback);
-								} else {
-									return callback();
-								}
-							};
+					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.issuer) {
-								if (currentProfile) {
-									dependentModule.profile = currentProfile;
-								}
+					moduleGraph.setIssuerIfUnset(
+						module,
+						originModule !== undefined ? originModule : null
+					);
+					if (module !== newModule && currentProfile !== undefined) {
+						const otherProfile = moduleGraph.getProfile(module);
+						if (otherProfile !== undefined) {
+							currentProfile.mergeInto(otherProfile);
+						} else {
+							moduleGraph.setProfile(module, currentProfile);
+						}
+					}
 
-								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._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 BuildCycleError(module));
 							}
+							set.add(m);
+						}
+					}
+				}
+			}
+		}
 
-							if (addModuleResult.build) {
-								this.buildModule(
-									dependentModule,
-									isOptional(),
-									module,
-									dependencies,
-									err => {
-										if (err) {
-											semaphore.release();
-											return errorOrWarningAndCallback(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);
+			}
 
-										semaphore.release();
-										afterBuild();
-									}
-								);
-							} else {
-								semaphore.release();
-								this.waitForBuildingFinished(dependentModule, afterBuild);
-							}
-						}
-					);
-				});
-			},
-			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 (!recursive) {
+				this.processModuleDependenciesNonRecursive(module);
+				callback(null, module);
+				return;
+			}
 
-				if (err) {
-					err.stack = err.stack;
-					return callback(err);
+			// 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);
 				}
 
-				return process.nextTick(callback);
+				this.processModuleDependencies(module, (err) => {
+					if (err) {
+						return callback(err);
+					}
+					callback(null, module);
+				});
 			}
-		);
+		});
 	}
 
-	_addModuleChain(context, dependency, onModule, callback) {
-		const start = this.profile && Date.now();
-		const currentProfile = this.profile && {};
-
-		const errorAndCallback = this.bail
-			? err => {
-					callback(err);
-			  }
-			: err => {
-					err.dependencies = [dependency];
-					this.errors.push(err);
-					callback();
-			  };
+	/**
+	 * 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
 		) {
-			throw new Error("Parameter 'dependency' must be a Dependency");
+			return callback(
+				new WebpackError("Parameter 'dependency' must be a Dependency")
+			);
 		}
-
-		const moduleFactory = this.dependencyFactories.get(dependency.constructor);
+		const Dep =
+			/** @type {DependencyConstructor} */
+			(dependency.constructor);
+		const moduleFactory = this.dependencyFactories.get(Dep);
 		if (!moduleFactory) {
-			throw new Error(
-				`No dependency factory available for this dependency type: ${
-					dependency.constructor.name
-				}`
+			return callback(
+				new WebpackError(
+					`No dependency factory available for this dependency type: ${dependency.constructor.name}`
+				)
 			);
 		}
 
-		this.semaphore.acquire(() => {
-			moduleFactory.create(
-				{
-					contextInfo: {
-						issuer: "",
-						compiler: this.compiler.name
-					},
-					context: context,
-					dependencies: [dependency]
-				},
-				(err, module) => {
-					if (err) {
-						this.semaphore.release();
-						return errorAndCallback(new EntryModuleNotFoundError(err));
-					}
+		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();
+				}
+			}
+		);
+	}
 
-					let afterFactory;
+	/**
+	 * 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);
+	}
 
-					if (currentProfile) {
-						afterFactory = Date.now();
-						currentProfile.factory = afterFactory - start;
-					}
+	/**
+	 * 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,
+			dependency,
+			"includeDependencies",
+			options,
+			callback
+		);
+	}
 
-					const addModuleResult = this.addModule(module);
-					module = addModuleResult.module;
+	/**
+	 * 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]}`
+						)
+					);
+				}
+			}
+		}
 
-					onModule(module);
+		this.hooks.addEntry.call(entry, options);
 
-					dependency.module = module;
-					module.addReason(null, dependency);
+		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);
+			}
+		);
+	}
 
-					const afterBuild = () => {
-						if (currentProfile) {
-							const afterBuilding = Date.now();
-							currentProfile.building = afterBuilding - afterFactory;
-						}
+	/**
+	 * 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);
+	}
 
-						if (addModuleResult.dependencies) {
-							this.processModuleDependencies(module, err => {
-								if (err) return callback(err);
-								callback(null, module);
-							});
-						} else {
-							return callback(null, 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.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);
+				});
+			}
 
-					if (addModuleResult.issuer) {
-						if (currentProfile) {
-							module.profile = currentProfile;
-						}
+			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);
+				});
+			});
+		});
+	}
 
-					if (addModuleResult.build) {
-						this.buildModule(module, false, null, null, err => {
-							if (err) {
-								this.semaphore.release();
-								return errorAndCallback(err);
-							}
+	/**
+	 * 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;
 
-							if (currentProfile) {
-								const afterBuilding = Date.now();
-								currentProfile.building = afterBuilding - afterFactory;
-							}
+		/**
+		 * 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;
+		};
 
-							this.semaphore.release();
-							afterBuild();
-						});
+		/**
+		 * 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 {
-						this.semaphore.release();
-						this.waitForBuildingFinished(module, afterBuild);
+						// keep the old mem cache
+						moduleMemCaches.set(module, cachedMemCache.memCache);
+						statUnchanged++;
 					}
+				} else {
+					infectedModules.add(module);
+					moduleMemCacheCache.delete(module);
+					statWithoutBuild++;
 				}
-			);
-		});
-	}
+				modulesWithoutCache.delete(module);
+			} else {
+				moduleMemCacheCache.delete(module);
+			}
+		}
 
-	addEntry(context, entry, name, callback) {
-		const slot = {
-			name: name,
-			request: entry.request,
-			module: null
-		};
-		this._preparedEntrypoints.push(slot);
-		this._addModuleChain(
-			context,
-			entry,
-			module => {
-				this.entries.push(module);
-			},
-			(err, module) => {
-				if (err) {
-					return callback(err);
-				}
+		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++;
+			}
+		}
 
-				if (module) {
-					slot.module = module;
+		/** @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 {
-					const idx = this._preparedEntrypoints.indexOf(slot);
-					this._preparedEntrypoints.splice(idx, 1);
+					infectedModules.add(referencingModule);
 				}
-				return callback(null, module);
 			}
+		}
+		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);
+			}
 		}
-		this._rebuildingModules.set(module, (callbackList = [thisCallback]));
+		// 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);
+		}
+	}
 
-		const callback = err => {
-			this._rebuildingModules.delete(module);
-			for (const cb of callbackList) {
-				cb(err);
+	/**
+	 * 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 });
+				}
 
-		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);
+				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.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);
+			}
+			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();
+			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);
+							return finalCallback(err);
 						}
-						this.hooks.afterOptimizeAssets.call(this.assets);
-						if (this.hooks.needAdditionalSeal.call()) {
-							this.unseal();
-							return this.seal(callback);
-						}
-						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.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 ModuleDependencyWarning(
+							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 ModuleDependencyError(
+							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
+	 */
+	_codeGenerationModule(
+		module,
+		runtime,
+		runtimes,
+		hash,
+		dependencyTemplates,
+		chunkGraph,
+		moduleGraph,
+		runtimeTemplate,
+		errors,
+		results,
+		callback
+	) {
+		let codeGenerated = false;
+		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 CodeGenerationError(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");
+	}
 
-			this.reportDependencyErrorsAndWarnings(module, block.blocks);
+	// 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);
+
+		// 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,26 +4360,91 @@ 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);
+		const chunkGroup = new ChunkGroup(groupOptions);
+		if (module) {
+			chunkGroup.addOrigin(
+				module,
+				/** @type {DependencyLocation} */
+				(loc),
+				/** @type {string} */
+				(request)
+			);
+		}
+		const chunk = this.addChunk(name);
+
+		if (chunkGroup.pushChunk(chunk)) {
+			chunk.addGroup(chunkGroup);
+		}
+
+		this.chunkGroups.push(chunkGroup);
+		if (name) {
+			this.namedChunkGroups.set(name, chunkGroup);
+		}
+		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);
-
-		GraphHelpers.connectChunkGroupAndChunk(chunkGroup, chunk);
-
-		this.chunkGroups.push(chunkGroup);
+		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, chunkGroup);
+			this.namedChunkGroups.set(name, entrypoint);
 		}
-		return chunkGroup;
+		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 +4452,1319 @@ 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);
+		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)) {
+				if (memCache) {
+					const digest =
+						/** @type {string} */
+						(memCache.get(`moduleHash-${getRuntimeKey(runtime)}`));
+					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(`moduleHash-${getRuntimeKey(runtime)}`, 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 ModuleHashingError(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 ChunkRenderError(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);
+		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;
+	}
 
-		if (block.variables) {
-			iterationBlockVariable(block.variables, iteratorDependency);
+	/**
+	 * 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);
 	}
 
-	patchChunksAfterReasonRemoval(module, chunk) {
-		if (!module.hasReasons()) {
-			this.removeReasonsOfDependencyBlock(module, module);
+	/**
+	 * 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);
 		}
-		if (!module.hasReasonForChunk(chunk)) {
-			if (module.removeChunk(chunk)) {
-				this.removeChunkFromDependencies(module, chunk);
+		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);
+				}
 			}
 		}
-	}
-
-	removeChunkFromDependencies(block, chunk) {
-		const iteratorDependency = d => {
-			if (!d.module) {
-				return;
+		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);
+				}
 			}
-			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);
+	/**
+	 * 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}`
+			);
+		}
+		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
+				);
 			}
 		}
+	}
 
-		if (block.dependencies) {
-			iterationOfArrayCallback(block.dependencies, iteratorDependency);
+	/**
+	 * 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 (block.variables) {
-			iterationBlockVariable(block.variables, iteratorDependency);
+		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`
+				)
+			);
 		}
-	}
-
-	applyModuleIds() {
-		const unusedIds = [];
-		let nextFreeModuleId = 0;
-		const usedIds = new Set();
-		if (this.usedModuleIds) {
-			for (const id of this.usedModuleIds) {
-				usedIds.add(id);
+		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 ChunkRenderError(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 ChunkRenderError(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 +5777,415 @@ 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
+				);
+				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
+						});
+						this.chunkGraph = old;
+
+						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 {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
+										);
+										if (
+											/** @type {BuildInfo} */ (module.buildInfo).cacheable ===
+											false
+										) {
+											cacheable = false;
+										}
+										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,
+											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 +6193,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 +6218,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..e872c3c2e73 100644
--- a/lib/Compiler.js
+++ b/lib/Compiler.js
@@ -2,245 +2,735 @@
 	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 Watching = require("./Watching");
+const ConcurrentCompilationError = require("./errors/ConcurrentCompilationError");
+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(".");
+
+/** @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("./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);
+};
+
+const getValidate = memoize(() => require("schema-utils").validate);
+
+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<[]>} */
+			readRecords: new AsyncSeriesHook([]),
+			/** @type {AsyncSeriesHook<[]>} */
+			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<[]>} */
+			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<[]>} */
+			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.')
+		this.context = context;
+
+		this.requestShortener = new RequestShortener(context, this.root);
+
+		this.cache = new Cache();
+
+		/** @type {Map | undefined} */
+		this.moduleMemCaches = undefined;
+
+		this.compilerPath = "";
+
+		/** @type {boolean} */
+		this.running = false;
+
+		/** @type {boolean} */
+		this.idle = false;
+
+		/** @type {boolean} */
+		this.watchMode = false;
+
+		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 ConcurrentCompilationError());
+			return;
+		}
 
 		this.running = true;
-		this.fileTimestamps = new Map();
-		this.contextTimestamps = new Map();
-		return new Watching(this, watchOptions, handler);
+		this.watchMode = true;
+		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 ConcurrentCompilationError());
+			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);
 
-					const stats = new Stats(compilation);
-					stats.startTime = startTime;
-					stats.endTime = Date.now();
-					this.hooks.done.callAsync(stats, err => {
+							this.hooks.additionalPass.callAsync((err) => {
+								if (err) return finalCallback(err);
+								this.compile(onCompiled);
+							});
+						});
+						return;
+					}
+
+					/** @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);
 
-			this.parentCompilation.children.push(compilation);
-			for (const name of Object.keys(compilation.assets)) {
-				this.parentCompilation.assets[name] = compilation.assets[name];
+			for (const { name, source, info } of compilation.getAssets()) {
+				parentCompilation.emitAsset(name, source, info);
 			}
 
-			const entries = Array.from(
-				compilation.entrypoints.values(),
-				ep => ep.chunks
-			).reduce((array, chunks) => {
-				return array.concat(chunks);
-			}, []);
+			/** @type {Chunk[]} */
+			const entries = [];
 
-			return callback(null, entries, compilation);
+			for (const ep of compilation.entrypoints.values()) {
+				entries.push(...ep.chunks);
+			}
+
+			compilation.startTime = startTime;
+			compilation.endTime = Date.now();
+
+			return finalCallback(null, entries, compilation);
 		});
 	}
 
@@ -250,124 +740,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._assetEmittingPreviousFiles = allTargetPaths;
 
-					this.hooks.afterEmit.callAsync(compilation, err => {
+					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 +1261,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);
-			}
-		}
-		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();
-				}
+		const childCompiler = new Compiler(this.context, {
+			...this.options,
+			output: {
+				...this.options.output,
+				...outputOptions
 			}
-		}
+		});
 		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 +1345,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 +1395,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..fb7691e4f41
--- /dev/null
+++ b/lib/ConcatenationScope.js
@@ -0,0 +1,204 @@
+/*
+	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("./Chunk")} Chunk */
+/** @typedef {import("./Module")} Module */
+/** @typedef {import("./optimize/ConcatenatedModule").ConcatenatedModuleInfo} ConcatenatedModuleInfo */
+/** @typedef {import("./optimize/ConcatenatedModule").ModuleInfo} ModuleInfo */
+/** @typedef {import("./optimize/ConcatenatedModule").ExportName} Ids */
+
+const MODULE_REFERENCE_REGEXP =
+	/^__WEBPACK_MODULE_REFERENCE__(\d+)_([\da-f]+|ns)(_call)?(_directImport)?(_deferredImport)?(?:_asiSafe(\d))?__$/;
+
+/**
+ * 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 | 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) {
+		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;
+		}
+		this.usedNames = usedNames;
+		this._modulesMap = modulesMap;
+	}
+
+	/**
+	 * 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);
+	}
+
+	/**
+	 * 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,
+			asiSafe = false
+		}
+	) {
+		const info = /** @type {ModuleInfo} */ (this._modulesMap.get(module));
+		const callFlag = call ? "_call" : "";
+		const directImportFlag = directImport ? "_directImport" : "";
+		const deferredImportFlag = deferredImport ? "_deferredImport" : "";
+		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}${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);
+	}
+
+	/**
+	 * 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[6];
+		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]),
+			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..01226215fb0 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 = /** @type {SourceLocation} */ (statement.loc);
+								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 = /** @type {SourceLocation} */ (expression.loc);
+									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 = /** @type {SourceLocation} */ (branchToRemove.loc);
+								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 = /** @type {SourceLocation} */ (expression.loc);
+										parser.state.module.addPresentationalDependency(dep);
+									} else {
+										parser.walkExpression(expression.left);
+									}
+									if (!keepRight) {
+										const dep = new ConstDependency(
+											"0",
+											/** @type {Range} */ (expression.right.range)
+										);
+										dep.loc = /** @type {SourceLocation} */ (expression.loc);
+										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 = /** @type {SourceLocation} */ (expression.loc);
+										parser.state.module.addPresentationalDependency(dep);
+									} else {
+										const dep = new ConstDependency(
+											"0",
+											/** @type {Range} */ (expression.right.range)
+										);
+										dep.loc = /** @type {SourceLocation} */ (expression.loc);
+										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 = /** @type {SourceLocation} */ (expr.loc);
+								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 = /** @type {SourceLocation} */ (expr.loc);
+							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 = /** @type {SourceLocation} */ (expr.loc);
+							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 = /** @type {SourceLocation} */ (statement.loc);
+		parser.state.module.addPresentationalDependency(dep);
+	}
 }
 
 module.exports = ConstPlugin;
diff --git a/lib/ContextExclusionPlugin.js b/lib/ContextExclusionPlugin.js
index 1333e9dbcfd..bbbeb5618e5 100644
--- a/lib/ContextExclusionPlugin.js
+++ b/lib/ContextExclusionPlugin.js
@@ -1,15 +1,32 @@
+/*
+	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) {
 		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..a9fc5a08dd5 100644
--- a/lib/ContextModule.js
+++ b/lib/ContextModule.js
@@ -2,122 +2,326 @@
 	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");
 
+/** @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
+ */
+
+/**
+ * @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) {
 			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");
 		}
 
 		this._identifier = this._createIdentifier();
+		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}`;
+		return str.replace(/!/g, "%21").replace(/\|/g, "%7C");
 	}
 
 	_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)}`;
 		}
 		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.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 +335,28 @@ 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)}`;
 		}
 		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 +368,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 +407,98 @@ 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)}`;
 		}
 		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 +509,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 +529,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 +554,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 +576,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 +863,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 +904,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 +1002,464 @@ 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
+	 * @returns {string} source code
+	 */
+	getLazyOnceSource(
+		block,
+		dependencies,
+		id,
+		phase,
+		{ runtimeTemplate, chunkGraph }
+	) {
 		const promise = runtimeTemplate.blockPromise({
+			chunkGraph,
 			block,
-			message: "lazy-once context"
+			message: "lazy-once context",
+			/** @type {RuntimeRequirements} */
+			runtimeRequirements: new Set()
 		});
-		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
+	 * @returns {string} source code
+	 */
+	getLazySource(blocks, id, phase, { chunkGraph, runtimeTemplate }) {
+		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;
+		const asyncDepsPosition = chunksPosition + 1;
+		const requestPrefix = hasNoChunk
+			? "Promise.resolve()"
+			: hasMultipleOrNoChunks
+				? `Promise.all(ids[${chunksPosition}].map(${RuntimeGlobals.ensureChunk}))`
+				: `${RuntimeGlobals.ensureChunk}(ids[${chunksPosition}][0])`;
 		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;`;
 	}
 
-	getSourceString(asyncMode, runtimeTemplate) {
+	/**
+	 * @param {string} asyncMode module mode
+	 * @param {ImportPhaseType} phase import phase
+	 * @param {CodeGenerationContext} context context info
+	 * @returns {string} the source code
+	 */
+	getSourceString(asyncMode, phase, { runtimeTemplate, chunkGraph }) {
+		const id = /** @type {ModuleId} */ (chunkGraph.getModuleId(this));
 		if (asyncMode === "lazy") {
 			if (this.blocks && this.blocks.length > 0) {
-				return this.getLazySource(this.blocks, this.id);
+				return this.getLazySource(this.blocks, id, phase, {
+					runtimeTemplate,
+					chunkGraph
+				});
 			}
-			return this.getSourceForEmptyAsyncContext(this.id);
+			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
+				});
 			}
-			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 {Sources} */
+		const sources = new Map();
+		sources.set(
+			JAVASCRIPT_TYPE,
+			this.getSource(
+				this.getSourceString(
+					this.options.mode,
+					this.options.phase || ImportPhase.Evaluation,
+					context
+				),
+				compilation
+			)
 		);
+		/** @type {RuntimeRequirements} */
+		const set = new Set();
+		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);
+			if (asyncMode === "weak") {
+				set.add(RuntimeGlobals.moduleFactories);
+			} else if (asyncMode === "async-weak") {
+				set.add(RuntimeGlobals.moduleFactories);
+				set.add(RuntimeGlobals.ensureChunk);
+			} else if (asyncMode === "lazy" || asyncMode === "lazy-once") {
+				set.add(RuntimeGlobals.ensureChunk);
+			}
+			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..269a54f7718 100644
--- a/lib/ContextModuleFactory.js
+++ b/lib/ContextModuleFactory.js
@@ -2,72 +2,167 @@
 	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");
+
+/** @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 +172,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 +184,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 +231,11 @@ module.exports = class ContextModuleFactory extends Tapable {
 										{},
 										context,
 										loader,
-										{},
+										{
+											fileDependencies,
+											missingDependencies,
+											contextDependencies
+										},
 										(err, result) => {
 											if (err) return callback(err);
 											callback(null, result);
@@ -134,30 +247,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 +308,114 @@ 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;
+		const {
+			resource,
+			resourceQuery,
+			resourceFragment,
+			recursive,
+			regExp,
+			include,
+			exclude,
+			referencedExports,
+			category,
+			typePrefix,
+			attributes
+		} = options;
 		if (!regExp || !resource) return callback(null, []);
 
-		const addDirectory = (directory, callback) => {
+		/**
+		 * Adds directory checked.
+		 * @param {string} ctx context
+		 * @param {string} directory directory
+		 * @param {Set} visited visited
+		 * @param {ResolveDependenciesCallback} callback callback
+		 */
+		const addDirectoryChecked = (ctx, directory, visited, callback) => {
+			/** @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);
+					},
+					callback
+				);
+			});
+		};
+
+		/**
+		 * Adds the provided ctx to the context module factory.
+		 * @param {string} ctx context
+		 * @param {string} directory directory
+		 * @param {(context: string, subResource: string, callback: () => void) => void} addSubDirectory addSubDirectoryFn
+		 * @param {ResolveDependenciesCallback} callback callback
+		 * @returns {void}
+		 */
+		const addDirectory = (ctx, directory, addSubDirectory, callback) => {
 			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 = [];
 				asyncLib.map(
-					files.filter(p => p.indexOf(".") !== 0),
+					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);
+									addSubDirectory(ctx, subResource, callback);
 								} 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);
-										}
-									);
+									// 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 +427,105 @@ module.exports = class ContextModuleFactory extends Tapable {
 					(err, result) => {
 						if (err) return callback(err);
 
-						if (!result) return callback(null, []);
+						/** @type {ContextElementDependency[]} */
+						const flattenedResult = [];
 
-						callback(
-							null,
-							result.filter(Boolean).reduce((a, i) => a.concat(i), [])
+						if (result) {
+							for (const item of result) {
+								if (item) flattenedResult.push(...item);
+							}
+						}
+
+						if (fileObjs.length === 0) {
+							return callback(null, flattenedResult);
+						}
+
+						this.hooks.alternativeRequests.callAsync(
+							fileObjs,
+							options,
+							(err, alternatives) => {
+								if (err) return callback(err);
+								for (const alt of /** @type {ContextAlternativeRequest[]} */ (
+									alternatives
+								)) {
+									if (!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
+		 * @returns {void}
+		 */
+		const addSubDirectory = (ctx, dir, callback) =>
+			addDirectory(ctx, dir, addSubDirectory, callback);
+
+		/**
+		 * Processes the provided resource.
+		 * @param {string} resource resource
+		 * @param {ResolveDependenciesCallback} callback callback
+		 */
+		const visitResource = (resource, callback) => {
+			if (typeof fs.realpath === "function") {
+				addDirectoryChecked(
+					resource,
+					resource,
+					/** @type {Set} */
+					new Set(),
+					callback
+				);
+			} else {
+				addDirectory(resource, resource, addSubDirectory, callback);
+			}
+		};
+
+		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..4a66266d90e 100644
--- a/lib/ContextReplacementPlugin.js
+++ b/lib/ContextReplacementPlugin.js
@@ -2,12 +2,30 @@
 	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,
@@ -16,38 +34,65 @@ class ContextReplacementPlugin {
 	) {
 		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"
 		) {
 			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);
+			this.newContentRecursive =
+				/** @type {boolean | undefined} */
+				(newContentRecursive);
+			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 +101,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 +124,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 +203,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..c8d9ea3b193 100644
--- a/lib/DefinePlugin.js
+++ b/lib/DefinePlugin.js
@@ -2,102 +2,622 @@
 	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");
+
+/** @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;
+		} 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 `${key === "__proto__" ? '["__proto__"]' : JSON.stringify(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})`;
+	}
+};
+
+/**
+ * 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;
 };
 
-const toCode = code => {
+/**
+ * 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(", ")}}`;
 	}
-	return code + "";
+	if (typeof code === "bigint") {
+		return `${code}n`;
+	}
+	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 WEBPACK_REQUIRE_FUNCTION_REGEXP = new RegExp(
+	`${RuntimeGlobals.require}\\s*(!?\\.)`
+);
+const WEBPACK_REQUIRE_IDENTIFIER_REGEXP = new RegExp(RuntimeGlobals.require);
+
+/**
+ * Defines the define plugin hooks type used by this module.
+ * @typedef {object} DefinePluginHooks
+ * @property {SyncWaterfallHook<[Record]>} definitions
+ */
+
+/** @typedef {Record} Definitions */
+
+/** @type {WeakMap} */
+const compilationHooksMap = new WeakMap();
+
 class DefinePlugin {
+	/**
+	 * Returns the attached hooks.
+	 * @param {Compilation} compilation the compilation
+	 * @returns {DefinePluginHooks} the attached hooks
+	 */
+	static getCompilationHooks(compilation) {
+		let hooks = compilationHooksMap.get(compilation);
+		if (hooks === undefined) {
+			hooks = {
+				definitions: new SyncWaterfallHook(["definitions"])
+			};
+			compilationHooksMap.set(compilation, hooks);
+		}
+		return hooks;
+	}
+
+	/**
+	 * 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
+				}));
+
+				/**
+				 * @type {Map>}
+				 */
+				const finalByNestedKey = new Map();
+				/**
+				 * @type {Map>}
+				 */
+				const nestedByFinalKey = new Map();
+
+				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)) || ""
+				);
+
+				/**
+				 * Handles the hook callback for this code path.
+				 * @param {JavascriptParser} parser Parser
+				 * @returns {void}
+				 */
+				const handler = (parser) => {
+					/** @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))
+						);
+					};
 
-				const handler = parser => {
+					/**
+					 * 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);
-						});
+							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];
+							const nestedSet = 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 = finalByNestedKey.get(nested);
+											for (const { id } of destructed) {
+												const fullKey = `${nested}.${id}`;
+												if (
+													!finalSet ||
+													!finalSet.has(id) ||
+													!definitions[fullKey]
+												) {
+													return;
+												}
+												obj[id] = 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 +627,53 @@ 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}`;
+								}
+
+								if (WEBPACK_REQUIRE_FUNCTION_REGEXP.test(strCode)) {
+									return toConstantDependency(parser, strCode, [
+										RuntimeGlobals.require
+									])(expr);
+								} else if (WEBPACK_REQUIRE_IDENTIFIER_REGEXP.test(strCode)) {
+									return toConstantDependency(parser, strCode, [
+										RuntimeGlobals.requireScope
+									])(expr);
+								}
+								return toConstantDependency(parser, 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 +684,182 @@ 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 => {
+						parser.hooks.typeof.for(key).tap(PLUGIN_NAME, (expr) => {
+							addValueDependency(originalKey);
+							const codeCode = toCode(
+								code,
+								parser,
+								compilation.valueCacheVersions,
+								originalKey,
+								runtimeTemplate,
+								logger,
+								null
+							);
+							const typeofCode = isTypeof ? codeCode : `typeof (${codeCode})`;
 							const res = parser.evaluate(typeofCode);
 							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);
+						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) => {
+							addValueDependency(key);
+							let strCode = stringifyObj(
+								obj,
+								parser,
+								compilation.valueCacheVersions,
+								key,
+								runtimeTemplate,
+								logger,
+								!parser.isAsiPosition(/** @type {Range} */ (expr.range)[0]),
+								getObjKeys(parser.destructuringAssignmentPropertiesFor(expr))
+							);
+
+							if (parser.scope.inShorthand) {
+								strCode = `${parser.scope.inShorthand}:${strCode}`;
+							}
+
+							if (WEBPACK_REQUIRE_FUNCTION_REGEXP.test(strCode)) {
+								return toConstantDependency(parser, strCode, [
+									RuntimeGlobals.require
+								])(expr);
+							} else if (WEBPACK_REQUIRE_IDENTIFIER_REGEXP.test(strCode)) {
+								return toConstantDependency(parser, strCode, [
+									RuntimeGlobals.requireScope
+								])(expr);
+							}
+							return toConstantDependency(parser, 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.
+						 * 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).
+						 * `member in value` keeps inherited members (e.g. `toString`) 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 {CodeValue} */
+							let value = /** @type {CodeValue} */ (obj);
+							let path = key;
+							for (let i = chainPrefix.length; i < members.length; i++) {
+								// a leaf with members left is a real property access on a value
+								if (!isObjectDefinition(value)) return false;
+								const member = members[i];
+								const nextPath = `${path}.${member}`;
+								if (member in value) {
+									value = value[member];
+								} else if (
+									Object.prototype.hasOwnProperty.call(definitions, nextPath)
+								) {
+									// defined via a dotted sibling key, e.g. `OBJECT.SUB2`
+									deps.push(nextPath);
+									value = definitions[nextPath];
+								} else {
+									return true;
+								}
+								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 +868,98 @@ 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}.`
+							);
+						}
+					}
+				};
+
+				/**
+				 * Walk definitions for keys.
+				 * @param {Definitions} definitions Definitions map
+				 * @returns {void}
+				 */
+				const walkDefinitionsForKeys = (definitions) => {
+					/**
+					 * Adds the provided map to the define plugin.
+					 * @param {Map>} map Map
+					 * @param {string} key key
+					 * @param {string} value v
+					 * @returns {void}
+					 */
+					const addToMap = (map, key, value) => {
+						if (map.has(key)) {
+							/** @type {Set} */
+							(map.get(key)).add(value);
+						} else {
+							map.set(key, new Set([value]));
+						}
+					};
+					for (const key of Object.keys(definitions)) {
+						const code = definitions[key];
+						if (
+							!code ||
+							typeof code === "object" ||
+							TYPEOF_OPERATOR_REGEXP.test(key)
+						) {
+							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);
+					}
+				};
+
+				walkDefinitionsForKeys(definitions);
+				walkDefinitionsForValues(definitions, "");
+
+				compilation.valueCacheVersions.set(
+					VALUE_DEP_MAIN,
+					mainHash.digest("hex").slice(0, 8)
+				);
 			}
 		);
 	}
 }
+
 module.exports = DefinePlugin;
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..92ae393cd3f 100644
--- a/lib/Dependency.js
+++ b/lib/Dependency.js
@@ -2,61 +2,528 @@
 	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 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("./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
+ */
+
+/**
+ * Defines the real dependency location type used by this module.
+ * @typedef {object} RealDependencyLocation
+ * @property {SourcePosition} start
+ * @property {SourcePosition=} end
+ * @property {number=} index
+ */
+
+/**
+ * Defines the synthetic dependency location type used by this module.
+ * @typedef {object} SyntheticDependencyLocation
+ * @property {string} name
+ * @property {number=} index
+ */
+
+/** @typedef {SyntheticDependencyLocation | RealDependencyLocation} DependencyLocation */
 
-/** @typedef {Object} Position
- *  @property {number} column
- *  @property {number} line
+/** @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 {Object} Loc
- *  @property {Position} start
- *  @property {Position} end
+/** @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;
+		this._locSL = 0;
+		this._locSC = 0;
+		this._locEL = 0;
+		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 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;
 	}
 
-	disconnect() {
-		this.module = null;
+	/**
+	 * 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
+	 * @returns {boolean} true if this dependency can be concatenated
+	 */
+	canConcatenate() {
+		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);
+	}
+
+	/**
+	 * 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 = [[]];
+
+// 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
+ * @returns {boolean} true if this dependency supports concatenation
+ */
+Dependency.canConcatenate = (dependency) => {
+	if (typeof dependency.canConcatenate === "function") {
+		return dependency.canConcatenate();
+	}
+	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..6d7bd8ca49f
--- /dev/null
+++ b/lib/EntryPlugin.js
@@ -0,0 +1,73 @@
+/*
+	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) {
+		this.context = context;
+		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..7a0c5845453 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 } & 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..d3332a929a8
--- /dev/null
+++ b/lib/ExportsInfo.js
@@ -0,0 +1,1950 @@
+/*
+	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;
+
+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
+	) {
+		this.exports = exports;
+		this.otherProvided = otherProvided;
+		this.otherCanMangleProvide = otherCanMangleProvide;
+		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();
+		}
+	}
+
+	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;
+		}
+	}
+
+	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) {
+		if (exportName) exportName = [...exportName];
+		if (!this._target) {
+			this._target = /** @type {Target} */ (new Map());
+			this._target.set(key, {
+				connection,
+				export: /** @type {ExportInfoName[]} */ (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),
+				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);
+			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, new Set());
+	}
+
+	/**
+	 * 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..e104835309f
--- /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 = /** @type {DependencyLocation} */ (expr.loc);
+							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 = /** @type {DependencyLocation} */ (expr.loc);
+							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..e78629f35b6 100644
--- a/lib/ExternalModule.js
+++ b/lib/ExternalModule.js
@@ -2,158 +2,1347 @@
 	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 { DEFAULTS } = require("./config/defaults");
+const { ImportPhaseUtils } = require("./dependencies/ImportPhase");
+const StaticExportsDependency = require("./dependencies/StaticExportsDependency");
+const EnvironmentNotSupportAsyncWarning = require("./errors/EnvironmentNotSupportAsyncWarning");
+const createHash = require("./util/createHash");
+const extractUrlAndGlobal = require("./util/extractUrlAndGlobal");
+const makeSerializable = require("./util/makeSerializable");
+const { propertyAccess } = require("./util/property");
+const { register } = require("./util/serialization");
+
+/** @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").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("./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("./serialization/ObjectMiddleware").ObjectDeserializerContext<[ExternalModuleRequest, ExternalsType, string, DependencyMeta | undefined]>} ObjectDeserializerContext */
+/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext<[ExternalModuleRequest, ExternalsType, string, DependencyMeta | 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();
+
+/**
+ * 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(" ")
+			}`
+		);
+		this._ident = ident;
+		this._request = request;
+		this._dependencyMeta = dependencyMeta;
+		this._identifier = this.buildIdentifier(ident);
+		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;",
+					"reject(__webpack_error__);"
+				])}, ${JSON.stringify(globalName)});`
+			]
+		)}).then(${runtimeTemplate.returningFunction(
+			`${globalName}${propertyAccess(urlAndGlobal, 2)}`
+		)})`,
+		runtimeRequirements: RUNTIME_REQUIREMENTS_FOR_SCRIPT
+	};
+};
+
+/**
+ * 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 */
+
+/**
+ * Defines the external module hooks type used by this module.
+ * @typedef {object} ExternalModuleHooks
+ * @property {SyncBailHook<[Chunk, Compilation], boolean>} chunkCondition
+ */
+
+/** @type {WeakMap} */
+const compilationHooksMap = new WeakMap();
+
+/**
+ * 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
+	 */
+	constructor(request, type, userRequest, dependencyMeta) {
+		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;
+	}
+
+	/**
+	 * Returns the attached hooks.
+	 * @param {Compilation} compilation the compilation
+	 * @returns {ExternalModuleHooks} the attached hooks
+	 */
+	static getCompilationHooks(compilation) {
+		let hooks = compilationHooksMap.get(compilation);
+		if (hooks === undefined) {
+			hooks = {
+				chunkCondition: new SyncBailHook(["chunk", "compilation"])
+			};
+			compilationHooksMap.set(compilation, hooks);
+		}
+		return hooks;
+	}
+
+	/**
+	 * Returns the source types this module can generate.
+	 * @returns {SourceTypes} types available (do not mutate)
+	 */
+	getSourceTypes() {
+		if (this.externalType === "asset" && this.dependencyMeta) {
+			const sourceType =
+				/** @type {AssetDependencyMeta} */
+				(this.dependencyMeta).sourceType;
+			// TODO webpack 6 drop "css-url" once the alias is removed
+			if (sourceType === ASSET_URL_TYPE || sourceType === "css-url") {
+				return ASSET_URL_TYPES;
+			}
+		} else if (this.externalType === "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;
 	}
 
+	/**
+	 * 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)}`;
+			}
+		}
+		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();
+		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;
+					EnvironmentNotSupportAsyncWarning.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;
+				EnvironmentNotSupportAsyncWarning.check(
+					this,
+					compilation.runtimeTemplate,
+					"external script"
+				);
+				break;
+			case "promise":
+				this.buildMeta.async = true;
+				EnvironmentNotSupportAsyncWarning.check(
+					this,
+					compilation.runtimeTemplate,
+					"external promise"
+				);
+				break;
+			case "import":
+				this.buildMeta.async = true;
+				EnvironmentNotSupportAsyncWarning.check(
+					this,
+					compilation.runtimeTemplate,
+					"external import"
+				);
+				if (!Array.isArray(request) || request.length === 1) {
+					this.buildMeta.exportsType = "namespace";
+					canMangle = false;
+				}
+				break;
+		}
+		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`;
+	/**
+	 * 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 };
 	}
 
-	getSourceForAmdOrUmdExternal(id, optional, request) {
-		const externalVariable = `__WEBPACK_EXTERNAL_MODULE_${Template.toIdentifier(
-			`${id}`
-		)}__`;
-		const missingModuleError = optional
-			? this.checkExternalVariable(externalVariable, request)
-			: "";
-		return `${missingModuleError}module.exports = ${externalVariable};`;
-	}
+	/**
+	 * 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 (externalType === "asset") {
+			if (
+				this.dependencyMeta &&
+				/** @type {AssetDependencyMeta} */
+				(this.dependencyMeta).sourceType
+			) {
+				return /** @type {AssetDependencyMeta} */ (this.dependencyMeta)
+					.sourceType;
+			}
 
-	getSourceForDefaultCase(optional, request) {
-		const missingModuleError = optional
-			? this.checkExternalVariable(request, request)
-			: "";
-		return `${missingModuleError}module.exports = ${request};`;
+			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
+	) {
+		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":
+			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: {
+				const sourceData = this._getSourceData(
+					request,
+					externalType,
+					runtimeTemplate,
+					moduleGraph,
+					chunkGraph,
+					runtime,
+					this.dependencyMeta,
+					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) {
+						scope.registerRawExport(specifier, finalName);
+					}
+				} else if (concatenationScope) {
+					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)}`);
+			}
+		}
+		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);
+
+		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();
+
+		super.deserialize(c3.rest);
 	}
 }
 
+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..cf01c0cd57d 100644
--- a/lib/ExternalModuleFactoryPlugin.js
+++ b/lib/ExternalModuleFactoryPlugin.js
@@ -2,52 +2,253 @@
 	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 { 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").Externals} Externals */
+/** @typedef {import("./Dependency")} Dependency */
+/** @typedef {import("./ExternalModule").DependencyMeta} DependencyMeta */
+/** @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 = {};
+
+// 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;
+};
+
+/** @typedef {string | string[] | boolean | Record} 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;
 		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;
+					if (value === false) {
+						// Not externals, fallback to original factory
+						return callback();
 					}
-					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);
+					/** @type {ExternalValue} */
+					let externalConfig = value === true ? dependency.request : value;
+					// 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 (
+						resolvedType === "asset" &&
+						dependency instanceof CssUrlDependency
+					) {
+						dependencyMeta = { sourceType: ASSET_URL_TYPE };
+					}
+
 					callback(
 						null,
-						new ExternalModule(value, type || globalType, dependency.request)
+						new ExternalModule(
+							externalConfig,
+							resolvedType,
+							dependency.request,
+							dependencyMeta
+						)
 					);
-					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);
+							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 +265,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 +273,114 @@ 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
+							);
+						}
 					}
 					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..cab4149cbd8 100644
--- a/lib/ExternalsPlugin.js
+++ b/lib/ExternalsPlugin.js
@@ -2,21 +2,92 @@
 	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;
 		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..0f86250ed27
--- /dev/null
+++ b/lib/FileSystemInfo.js
@@ -0,0 +1,4475 @@
+/*
+	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 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");
+
+/** @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) {
+		this.snapshot = snapshot;
+		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 */
+
+class Snapshot {
+	constructor() {
+		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 & 1) !== 0;
+	}
+
+	/**
+	 * Updates start time using the provided value.
+	 * @param {number} value start value
+	 */
+	setStartTime(value) {
+		this._flags |= 1;
+		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 & 2) !== 0;
+	}
+
+	/**
+	 * Sets file timestamps.
+	 * @param {FileTimestamps} value file timestamps
+	 */
+	setFileTimestamps(value) {
+		this._flags |= 2;
+		this.fileTimestamps = value;
+	}
+
+	hasFileHashes() {
+		return (this._flags & 4) !== 0;
+	}
+
+	/**
+	 * Updates file hashes using the provided value.
+	 * @param {FileHashes} value file hashes
+	 */
+	setFileHashes(value) {
+		this._flags |= 4;
+		this.fileHashes = value;
+	}
+
+	hasFileTshs() {
+		return (this._flags & 8) !== 0;
+	}
+
+	/**
+	 * Updates file tshs using the provided value.
+	 * @param {FileTshs} value file tshs
+	 */
+	setFileTshs(value) {
+		this._flags |= 8;
+		this.fileTshs = value;
+	}
+
+	hasContextTimestamps() {
+		return (this._flags & 0x10) !== 0;
+	}
+
+	/**
+	 * Sets context timestamps.
+	 * @param {ContextTimestamps} value context timestamps
+	 */
+	setContextTimestamps(value) {
+		this._flags |= 0x10;
+		this.contextTimestamps = value;
+	}
+
+	hasContextHashes() {
+		return (this._flags & 0x20) !== 0;
+	}
+
+	/**
+	 * Sets context hashes.
+	 * @param {ContextHashes} value context hashes
+	 */
+	setContextHashes(value) {
+		this._flags |= 0x20;
+		this.contextHashes = value;
+	}
+
+	hasContextTshs() {
+		return (this._flags & 0x40) !== 0;
+	}
+
+	/**
+	 * Updates context tshs using the provided value.
+	 * @param {ContextTshs} value context tshs
+	 */
+	setContextTshs(value) {
+		this._flags |= 0x40;
+		this.contextTshs = value;
+	}
+
+	hasMissingExistence() {
+		return (this._flags & 0x80) !== 0;
+	}
+
+	/**
+	 * Sets missing existence.
+	 * @param {MissingExistence} value context tshs
+	 */
+	setMissingExistence(value) {
+		this._flags |= 0x80;
+		this.missingExistence = value;
+	}
+
+	hasManagedItemInfo() {
+		return (this._flags & 0x100) !== 0;
+	}
+
+	/**
+	 * Sets managed item info.
+	 * @param {ManagedItemInfo} value managed item info
+	 */
+	setManagedItemInfo(value) {
+		this._flags |= 0x100;
+		this.managedItemInfo = value;
+	}
+
+	hasManagedFiles() {
+		return (this._flags & 0x200) !== 0;
+	}
+
+	/**
+	 * Sets managed files.
+	 * @param {ManagedFiles} value managed files
+	 */
+	setManagedFiles(value) {
+		this._flags |= 0x200;
+		this.managedFiles = value;
+	}
+
+	hasManagedContexts() {
+		return (this._flags & 0x400) !== 0;
+	}
+
+	/**
+	 * Sets managed contexts.
+	 * @param {ManagedContexts} value managed contexts
+	 */
+	setManagedContexts(value) {
+		this._flags |= 0x400;
+		this.managedContexts = value;
+	}
+
+	hasManagedMissing() {
+		return (this._flags & 0x800) !== 0;
+	}
+
+	/**
+	 * Sets managed missing.
+	 * @param {ManagedMissing} value managed missing
+	 */
+	setManagedMissing(value) {
+		this._flags |= 0x800;
+		this.managedMissing = value;
+	}
+
+	hasChildren() {
+		return (this._flags & 0x1000) !== 0;
+	}
+
+	/**
+	 * Updates children using the provided value.
+	 * @param {Children} value children
+	 */
+	setChildren(value) {
+		this._flags |= 0x1000;
+		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;
+		this._useStartTime = useStartTime;
+		/** @type {U} */
+		this._isSet = isSet;
+		/** @type {Map} */
+		this._map = new Map();
+		this._statItemsShared = 0;
+		this._statItemsUnshared = 0;
+		this._statSharedSnapshots = 0;
+		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);
+};
+
+const getEsModuleLexer = memoize(() => require("es-module-lexer"));
+
+/** @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
+	);
+};
+
+/** @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
+		} = {}
+	) {
+		this.fs = fs;
+		this.logger = logger;
+		this._remainingLogs = logger ? 40 : 0;
+		/** @type {LoggedPaths | undefined} */
+		this._loggedPaths = logger ? new Set() : undefined;
+		this._hashFunction = hashFunction;
+		/** @type {WeakMap} */
+		this._snapshotCache = new WeakMap();
+		this._fileTimestampsOptimization = new SnapshotOptimization(
+			(s) => s.hasFileTimestamps(),
+			(s) => s.fileTimestamps,
+			(s, v) => s.setFileTimestamps(v)
+		);
+		this._fileHashesOptimization = new SnapshotOptimization(
+			(s) => s.hasFileHashes(),
+			(s) => s.fileHashes,
+			(s, v) => s.setFileHashes(v),
+			false
+		);
+		this._fileTshsOptimization = new SnapshotOptimization(
+			(s) => s.hasFileTshs(),
+			(s) => s.fileTshs,
+			(s, v) => s.setFileTshs(v)
+		);
+		this._contextTimestampsOptimization = new SnapshotOptimization(
+			(s) => s.hasContextTimestamps(),
+			(s) => s.contextTimestamps,
+			(s, v) => s.setContextTimestamps(v)
+		);
+		this._contextHashesOptimization = new SnapshotOptimization(
+			(s) => s.hasContextHashes(),
+			(s) => s.contextHashes,
+			(s, v) => s.setContextHashes(v),
+			false
+		);
+		this._contextTshsOptimization = new SnapshotOptimization(
+			(s) => s.hasContextTshs(),
+			(s) => s.contextTshs,
+			(s, v) => s.setContextTshs(v)
+		);
+		this._missingExistenceOptimization = new SnapshotOptimization(
+			(s) => s.hasMissingExistence(),
+			(s) => s.missingExistence,
+			(s, v) => s.setMissingExistence(v),
+			false
+		);
+		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();
+		/** @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"
+		);
+
+		this._cachedDeprecatedFileTimestamps = undefined;
+		this._cachedDeprecatedContextTimestamps = undefined;
+
+		this._warnAboutExperimentalEsmTracking = false;
+
+		this._statCreatedSnapshots = 0;
+		this._statTestedSnapshotsCached = 0;
+		this._statTestedSnapshotsNotCached = 0;
+		this._statTestedChildrenCached = 0;
+		this._statTestedChildrenNotCached = 0;
+		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._managedItems.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)) {
+			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
+									});
+								}
+							}
+						} 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) {
+		/** @type {FileTimestamps} */
+		const fileTimestamps = new Map();
+		/** @type {FileHashes} */
+		const fileHashes = new Map();
+		/** @type {FileTshs} */
+		const fileTshs = new Map();
+		/** @type {ContextTimestamps} */
+		const contextTimestamps = new Map();
+		/** @type {ContextHashes} */
+		const contextHashes = new Map();
+		/** @type {ContextTshs} */
+		const contextTshs = new Map();
+		/** @type {MissingExistence} */
+		const missingExistence = new Map();
+		/** @type {ManagedItemInfo} */
+		const managedItemInfo = new Map();
+		/** @type {ManagedFiles} */
+		const managedFiles = new Set();
+		/** @type {ManagedContexts} */
+		const managedContexts = new Set();
+		/** @type {ManagedMissing} */
+		const managedMissing = new Set();
+		/** @type {Children} */
+		const children = 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.size !== 0) {
+					snapshot.setFileTimestamps(fileTimestamps);
+				}
+				if (fileHashes.size !== 0) {
+					snapshot.setFileHashes(fileHashes);
+				}
+				if (fileTshs.size !== 0) {
+					snapshot.setFileTshs(fileTshs);
+				}
+				if (contextTimestamps.size !== 0) {
+					snapshot.setContextTimestamps(contextTimestamps);
+				}
+				if (contextHashes.size !== 0) {
+					snapshot.setContextHashes(contextHashes);
+				}
+				if (contextTshs.size !== 0) {
+					snapshot.setContextTshs(contextTshs);
+				}
+				if (missingExistence.size !== 0) {
+					snapshot.setMissingExistence(missingExistence);
+				}
+				if (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);
+				}
+				if (children.size !== 0) {
+					snapshot.setChildren(children);
+				}
+				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);
+			}
+		};
+		/**
+		 * Checks true when managed.
+		 * @param {string} path path
+		 * @param {ManagedFiles} managedSet managed set
+		 * @returns {boolean} true when managed
+		 */
+		const checkManaged = (path, managedSet) => {
+			for (const unmanagedPath of this.unmanagedPathsRegExps) {
+				if (unmanagedPath.test(path)) return false;
+			}
+			for (const unmanagedPath of this.unmanagedPathsWithSlash) {
+				if (path.startsWith(unmanagedPath)) return false;
+			}
+			for (const immutablePath of this.immutablePathsRegExps) {
+				if (immutablePath.test(path)) {
+					managedSet.add(path);
+					return true;
+				}
+			}
+			for (const immutablePath of this.immutablePathsWithSlash) {
+				if (path.startsWith(immutablePath)) {
+					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) {
+						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) {
+						managedItems.add(managedItem);
+						managedSet.add(path);
+						return true;
+					}
+				}
+			}
+			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);
+					for (const path of capturedFiles) {
+						const cache = this._fileTshs.get(path);
+						if (cache !== undefined) {
+							fileTshs.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 {
+									fileTshs.set(path, /** @type {TimestampAndHash} */ (entry));
+									jobDone();
+								}
+							});
+						}
+					}
+					break;
+				case 2:
+					this._fileHashesOptimization.optimize(snapshot, capturedFiles);
+					for (const path of capturedFiles) {
+						const cache = this._fileHashes.get(path);
+						if (cache !== undefined) {
+							fileHashes.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 {
+									fileHashes.set(path, /** @type {string} */ (entry));
+									jobDone();
+								}
+							});
+						}
+					}
+					break;
+				case 1:
+					this._fileTimestampsOptimization.optimize(snapshot, capturedFiles);
+					for (const path of capturedFiles) {
+						const cache = this._fileTimestamps.get(path);
+						if (cache !== undefined && !isExistenceOnly(cache)) {
+							if (cache !== "ignore") {
+								fileTimestamps.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 {
+									fileTimestamps.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);
+					for (const path of capturedDirectories) {
+						const cache = this._contextTshs.get(path);
+						/** @type {ResolvedContextTimestampAndHash | null | undefined} */
+						let resolved;
+						if (
+							cache !== undefined &&
+							(resolved = getResolvedTimestamp(cache)) !== undefined
+						) {
+							contextTshs.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 {
+									contextTshs.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
+					);
+					for (const path of capturedDirectories) {
+						const cache = this._contextHashes.get(path);
+						/** @type {undefined | null | string} */
+						let resolved;
+						if (
+							cache !== undefined &&
+							(resolved = getResolvedHash(cache)) !== undefined
+						) {
+							contextHashes.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 {
+									contextHashes.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
+					);
+					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);
+						// A non-null cache entry without `timestampHash` cannot be
+						// used to populate the snapshot — the snapshot would then
+						// miss directory-change detection, since validity relies on
+						// `timestampHash`. Re-read the directory in that case.
+						const cacheLacksHash =
+							usableCache !== undefined &&
+							usableCache !== null &&
+							usableCache.timestampHash === undefined;
+						/** @type {undefined | null | ResolvedContextFileSystemInfoEntry} */
+						let resolved;
+						if (
+							usableCache !== undefined &&
+							!cacheLacksHash &&
+							(resolved = getResolvedTimestamp(usableCache)) !== undefined
+						) {
+							contextTimestamps.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 {
+									contextTimestamps.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);
+			for (const path of capturedMissing) {
+				const cache = this._fileTimestamps.get(path);
+				if (cache !== undefined && !isExistenceOnly(cache)) {
+					if (cache !== "ignore") {
+						missingExistence.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 {
+							missingExistence.set(path, Boolean(entry));
+							jobDone();
+						}
+					});
+				}
+			}
+		};
+		if (missing) {
+			processCapturedMissing(captureNonManaged(missing, managedMissing));
+		}
+		this._managedItemInfoOptimization.optimize(snapshot, managedItems);
+		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"));
+				}
+				managedItemInfo.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"));
+						}
+						managedItemInfo.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);
+				// A non-null cache entry that lacks `timestampHash` while the
+				// snapshot has one cannot be used either; we re-read the
+				// directory through the disk-backed queue instead.
+				const cacheLacksHash =
+					usableCache !== undefined &&
+					usableCache !== null &&
+					usableCache.timestampHash === undefined &&
+					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 =
+						usableCache !== undefined &&
+						usableCache !== null &&
+						usableCache.timestampHash === undefined &&
+						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)[]} 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) {
+						if (typeof entry === "string") {
+							hash.update(entry);
+						} else {
+							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} hash hash
+		 */
+		const finalize = (timestamp, hash) => {
+			const result =
+				/** @type {ContextTimestampAndHash} */
+				(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..a1c4e2df8e6
--- /dev/null
+++ b/lib/FlagAllModulesAsUsedPlugin.js
@@ -0,0 +1,51 @@
+/*
+	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) {
+		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..2a61adcadf5 100644
--- a/lib/FlagDependencyExportsPlugin.js
+++ b/lib/FlagDependencyExportsPlugin.js
@@ -2,144 +2,461 @@
 	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 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("./Module")} Module */
+/** @typedef {import("./Module").BuildInfo} BuildInfo */
+
+const PLUGIN_NAME = "FlagDependencyExportsPlugin";
+const PLUGIN_LOGGER_NAME = `webpack.${PLUGIN_NAME}`;
 
 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);
+			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();
+
+					// 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++;
+								// Enqueue uncacheable module for determining the exports
+								queue.enqueue(module);
+								exportsInfo.setHasProvideInfo();
+								return callback();
 							}
-							// merge in new exports
-							if (Array.isArray(exports)) {
-								if (addToSet(moduleProvidedExports, exports)) {
-									notifyDependencies();
-								}
+							const memCache = moduleMemCaches && moduleMemCaches.get(module);
+							const memCacheValue = memCache && memCache.get(this);
+							if (memCacheValue !== undefined) {
+								statRestoredFromMemCache++;
+								exportsInfo.restoreProvided(memCacheValue);
+								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]));
+							cache.get(
+								module.identifier(),
+								/** @type {BuildInfo} */
+								(module.buildInfo).hash,
+								(err, result) => {
+									if (err) return callback(err);
+
+									if (result !== undefined) {
+										statRestoredFromCache++;
+										exportsInfo.restoreProvided(result);
 									} else {
-										set.add(module);
+										statNotCached++;
+										// Without cached info enqueue module for determining the exports
+										queue.enqueue(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);
+
+							/** @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);
+								}
+							};
 
-						// Start with all modules without provided exports
-						for (const module of modules) {
-							if (!module.buildMeta.providedExports) {
-								queue.enqueue(module);
-							}
-						}
+							/**
+							 * 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);
+
+											if (
+												exportInfo.provided === false ||
+												exportInfo.provided === null
+											) {
+												exportInfo.provided = true;
+												changed = true;
+											}
 
-						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.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);
+									if (memCache) {
+										memCache.set(this, cachedData);
+									}
+									cache.store(
+										module.identifier(),
+										/** @type {BuildInfo} */
+										(module.buildInfo).hash,
+										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..21467526327 100644
--- a/lib/FlagDependencyUsagePlugin.js
+++ b/lib/FlagDependencyUsagePlugin.js
@@ -2,110 +2,360 @@
 	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 { 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 } = Dependency;
 
-const isSubset = (biggerSet, subset) => {
-	if (biggerSet === true) return true;
-	if (subset === true) return false;
-	return subset.every(item => biggerSet.indexOf(item) >= 0);
-};
+const PLUGIN_NAME = "FlagDependencyUsagePlugin";
+const PLUGIN_LOGGER_NAME = `webpack.${PLUGIN_NAME}`;
 
 class FlagDependencyUsagePlugin {
+	/**
+	 * Creates an instance of FlagDependencyUsagePlugin.
+	 * @param {boolean} global do a global analysis instead of per runtime
+	 */
+	constructor(global) {
+		/** @type {boolean} */
+		this.global = global;
+	}
+
+	/**
+	 * 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) {
+				{ 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();
+
+					/**
+					 * Process referenced module.
+					 * @param {Module} module module to process
+					 * @param {ReferencedExports} usedExports list of used exports
+					 * @param {RuntimeSpec} runtime part of which runtime
+					 * @param {boolean} forceSideEffects always apply side effects
+					 * @returns {void}
+					 */
+					const processReferencedModule = (
+						module,
+						usedExports,
+						runtime,
+						forceSideEffects
+					) => {
+						const exportsInfo = moduleGraph.getExportsInfo(module);
+						if (usedExports.length > 0) {
+							if (!module.buildMeta || !module.buildMeta.exportsType) {
+								if (exportsInfo.setUsedWithoutInfo(runtime)) {
+									queue.enqueue(module, runtime);
+								}
 								return;
 							}
-						} else if (Array.isArray(module.usedExports)) {
-							return;
-						} else {
-							module.usedExports = false;
-						}
+							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;
+										}
 
-						// 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 (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(
+														(used) => used === UsageState.Unused,
+														UsageState.OnlyPropertiesUsed,
+														runtime
+													)
+												) {
+													const currentModule =
+														currentExportsInfo === exportsInfo
+															? module
+															: exportInfoToModuleMap.get(currentExportsInfo);
+													if (currentModule) {
+														queue.enqueue(currentModule, runtime);
+													}
+												}
+												currentExportsInfo = nestedInfo;
+												continue;
+											}
+										}
+										if (
+											exportInfo.setUsedConditionally(
+												(v) => v !== UsageState.Used,
+												UsageState.Used,
+												runtime
+											)
+										) {
+											const currentModule =
+												currentExportsInfo === exportsInfo
+													? module
+													: exportInfoToModuleMap.get(currentExportsInfo);
+											if (currentModule) {
+												queue.enqueue(currentModule, runtime);
+											}
+										}
+										break;
+									}
+								}
+							}
+						} else {
+							// 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
+							// TODO webpack 6 remove this check
 							if (
-								Array.isArray(module.usedExports) &&
-								module.usedExports.length === 0
-							)
+								!forceSideEffects &&
+								module.factoryMeta !== undefined &&
+								module.factoryMeta.sideEffectFree
+							) {
 								return;
+							}
+							if (exportsInfo.setUsedForSideEffectsOnly(runtime)) {
+								queue.enqueue(module, runtime);
+							}
 						}
-
-						queue.push([module, module.usedExports]);
 					};
 
-					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
+					 * @param {boolean} forceSideEffects always apply side effects
+					 * @returns {void}
+					 */
+					const processModule = (module, runtime, forceSideEffects) => {
+						/** @typedef {Map} ExportMaps */
+						/** @type {Map} */
+						const map = new Map();
+
+						/** @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,
+										true
+									);
+								} 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, false);
+									continue;
+								}
+								const oldReferencedExports = map.get(module);
+								if (oldReferencedExports === EXPORTS_OBJECT_REFERENCED) {
+									continue;
+								}
+								const referencedExports =
+									compilation.getDependencyReferencedExports(dep, runtime);
+								if (
+									oldReferencedExports === undefined ||
+									oldReferencedExports === NO_EXPORTS_REFERENCED ||
+									referencedExports === EXPORTS_OBJECT_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,
+									forceSideEffects
+								);
+							} else {
+								processReferencedModule(
+									module,
+									[...referencedExports.values()],
+									runtime,
+									forceSideEffects
+								);
+							}
 						}
 					};
 
+					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");
 
-					const queue = [];
-					for (const preparedEntrypoint of compilation._preparedEntrypoints) {
-						if (preparedEntrypoint.module) {
-							processModule(preparedEntrypoint.module, true);
+					logger.time("trace exports usage in graph");
+
+					/**
+					 * 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,
+								true
+							);
+						}
+					};
+					/** @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, false);
 					}
+					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..53040abd195
--- /dev/null
+++ b/lib/FlagEntryExportAsUsedPlugin.js
@@ -0,0 +1,57 @@
+/*
+	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) {
+		this.nsObjectUsed = nsObjectUsed;
+		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..5308239e9d7 100644
--- a/lib/Generator.js
+++ b/lib/Generator.js
@@ -2,50 +2,206 @@
 	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;
+		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..f98a1a5bf43 100644
--- a/lib/HotModuleReplacementPlugin.js
+++ b/lib/HotModuleReplacementPlugin.js
@@ -2,51 +2,335 @@
 	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 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 JavascriptParser = require("./javascript/JavascriptParser");
+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("estree").SpreadElement} SpreadElement */
+/** @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("./RuntimeModule")} RuntimeModule */
+/** @typedef {import("./javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */
+/** @typedef {import("./javascript/JavascriptParserHelpers").Range} Range */
+/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */
+
+/** @typedef {string[]} Requests */
+
+/**
+ * Defines the hmr javascript parser hooks type used by this module.
+ * @typedef {object} HMRJavascriptParserHooks
+ * @property {SyncBailHook<[Expression | SpreadElement, Requests], void>} hotAcceptCallback
+ * @property {SyncBailHook<[CallExpression, Requests], void>} hotAcceptWithoutCallback
+ */
+
+/** @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 */
+
+/** @type {WeakMap} */
+const parserHooksMap = new WeakMap();
+
+const PLUGIN_NAME = "HotModuleReplacementPlugin";
+
+class HotModuleReplacementPlugin {
+	/**
+	 * Returns the attached hooks.
+	 * @param {JavascriptParser} parser the parser
+	 * @returns {HMRJavascriptParserHooks} the attached hooks
+	 */
+	static getParserHooks(parser) {
+		if (!(parser instanceof JavascriptParser)) {
+			throw new TypeError(
+				"The 'parser' argument must be an instance of JavascriptParser"
+			);
+		}
+		let hooks = parserHooksMap.get(parser);
+		if (hooks === undefined) {
+			hooks = {
+				hotAcceptCallback: new SyncBailHook(["expression", "requests"]),
+				hotAcceptWithoutCallback: new SyncBailHook(["expression", "requests"])
+			};
+			parserHooksMap.set(parser, hooks);
+		}
+		return hooks;
 	}
 
+	/**
+	 * 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) => {
+			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 = /** @type {DependencyLocation} */ (expr.loc);
+				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(
+								/** @type {DependencyLocation} */ (expr.loc),
+								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 = /** @type {DependencyLocation} */ (expr.loc);
+			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(
+						/** @type {DependencyLocation} */ (expr.loc),
+						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 = /** @type {DependencyLocation} */ (expr.loc);
+			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 +339,6 @@ module.exports = class HotModuleReplacementPlugin {
 					ModuleHotAcceptDependency,
 					new ModuleHotAcceptDependency.Template()
 				);
-
 				compilation.dependencyFactories.set(
 					ModuleHotDeclineDependency,
 					normalModuleFactory
@@ -64,350 +347,647 @@ 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()
+				);
+				compilation.dependencyFactories.set(
+					ImportMetaHotDeclineDependency,
+					normalModuleFactory
+				);
+				compilation.dependencyTemplates.set(
+					ImportMetaHotDeclineDependency,
+					new ImportMetaHotDeclineDependency.Template()
 				);
-				let initialPass = false;
-				let recompilation = false;
-				compilation.hooks.afterHash.tap("HotModuleReplacementPlugin", () => {
-					let records = compilation.records;
-					if (!records) {
-						initialPass = true;
-						return;
+				// #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: []"
-						]);
-					}
-				);
-
-				const handler = (parser, parserOptions) => {
-					parser.hooks.expression
-						.for("__webpack_hash__")
-						.tap(
-							"HotModuleReplacementPlugin",
-							ParserHelpers.toConstantDependencyWithWebpackRequire(
-								parser,
-								"__webpack_require__.h()"
-							)
+				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()
 						);
-					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);
-						}
-					);
-					// 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_AUTO)
+					.tap(PLUGIN_NAME, (parser) => {
+						applyModuleHot(parser);
+						applyImportMetaHot(parser);
+					});
 				normalModuleFactory.hooks.parser
-					.for("javascript/dynamic")
-					.tap("HotModuleReplacementPlugin", handler);
+					.for(JAVASCRIPT_MODULE_TYPE_DYNAMIC)
+					.tap(PLUGIN_NAME, (parser) => {
+						applyModuleHot(parser);
+					});
+				normalModuleFactory.hooks.parser
+					.for(JAVASCRIPT_MODULE_TYPE_ESM)
+					.tap(PLUGIN_NAME, (parser) => {
+						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..2792d13d143 100644
--- a/lib/IgnorePlugin.js
+++ b/lib/IgnorePlugin.js
@@ -2,68 +2,105 @@
 	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) {
+		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..7a2cc1a4db2
--- /dev/null
+++ b/lib/InitFragment.js
@@ -0,0 +1,214 @@
+/*
+	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
+ */
+
+/**
+ * Extract fragment index.
+ * @template T
+ * @param {T} fragment the init fragment
+ * @param {number} index index
+ * @returns {[T, number]} tuple with both
+ */
+const extractFragmentIndex = (fragment, index) => [fragment, index];
+
+/**
+ * Sorts fragment with index.
+ * @template T
+ * @param {[MaybeMergeableInitFragment, number]} a first pair
+ * @param {[MaybeMergeableInitFragment, number]} b second pair
+ * @returns {number} sort value
+ */
+const sortFragmentWithIndex = ([a, i], [b, j]) => {
+	const stageCmp = a.stage - b.stage;
+	if (stageCmp !== 0) return stageCmp;
+	const positionCmp = a.position - b.position;
+	if (positionCmp !== 0) return positionCmp;
+	return i - j;
+};
+
+/**
+ * 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) {
+		this.content = content;
+		this.stage = stage;
+		this.position = position;
+		this.key = key;
+		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 fragments by position. If 2 fragments have the same position,
+			// use their index.
+			const sortedFragments = initFragments
+				.map(extractFragmentIndex)
+				.sort(sortFragmentWithIndex);
+
+			// Deduplicate fragments. If a fragment has no key, it is always included.
+			/** @type {Map | MaybeMergeableInitFragment[]>} */
+			const keyedFragments = new Map();
+			for (const [fragment] of sortedFragments) {
+				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 || Symbol("fragment key"), 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..240b6b43086
--- /dev/null
+++ b/lib/JavascriptMetaInfoPlugin.js
@@ -0,0 +1,81 @@
+/*
+	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.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 {
+							innerGraph.bailout(parser.state);
+						}
+					});
+					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..78706dba8c7
--- /dev/null
+++ b/lib/LazyBarrel.js
@@ -0,0 +1,360 @@
+/*
+	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} request keys of star re-exports */
+		this._fallbackRequests = undefined;
+	}
+
+	/**
+	 * Defers a 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);
+		dependency.setLazy(true);
+	}
+
+	/**
+	 * 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;
+	}
+
+	/**
+	 * 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) {
+		this._compilation = compilation;
+		/** @type {WeakMap} */
+		this._modules = new WeakMap();
+	}
+
+	/**
+	 * Releases all per-module deferral state. Lazy barrel only acts while the
+	 * module graph is built, so the bookkeeping is dead weight once make is done.
+	 */
+	clear() {
+		this._modules = new WeakMap();
+	}
+
+	/**
+	 * 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();
+			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..0ab4733b6e6 100644
--- a/lib/LoaderTargetPlugin.js
+++ b/lib/LoaderTargetPlugin.js
@@ -2,18 +2,34 @@
 	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) {
 		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..645799cc463 100644
--- a/lib/MainTemplate.js
+++ b/lib/MainTemplate.js
@@ -2,86 +2,274 @@
 	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 memoize = require("./util/memoize");
 
-/** @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
+const getJavascriptModulesPlugin = memoize(() =>
+	require("./javascript/JavascriptModulesPlugin")
+);
+const getJsonpTemplatePlugin = memoize(() =>
+	require("./web/JsonpTemplatePlugin")
+);
+const getLoadScriptRuntimeModule = memoize(() =>
+	require("./runtime/LoadScriptRuntimeModule")
+);
 
-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) => {
+						getJavascriptModulesPlugin()
+							.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) => {
+						getJavascriptModulesPlugin()
+							.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) => {
+						getJavascriptModulesPlugin()
+							.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) => {
+						getJavascriptModulesPlugin()
+							.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 +277,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)});`
-				);
+			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;
 			}
-			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("};");
-			}
-			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))
-		);
-		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
+		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"
 		);
-		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
+		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 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.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 {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.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"
 		);
 	}
+}
 
-	/**
-	 *
-	 * @param {object} options get public path options
-	 * @returns {string} hook call
-	 */
-	getPublicPath(options) {
-		return this.hooks.assetPath.call(
-			this.outputOptions.publicPath || "",
-			options
-		);
-	}
+Object.defineProperty(MainTemplate.prototype, "requireFn", {
+	get: util.deprecate(
+		() => RuntimeGlobals.require,
+		`MainTemplate.requireFn is deprecated (use "${RuntimeGlobals.require}")`,
+		"DEP_WEBPACK_MAIN_TEMPLATE_REQUIRE_FN"
+	)
+});
 
-	getAssetPath(path, options) {
-		return this.hooks.assetPath.call(path, options);
-	}
+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"
+	)
+});
 
-	updateHash(hash) {
-		hash.update("maintemplate");
-		hash.update("3");
-		hash.update(this.outputOptions.publicPath + "");
-		this.hooks.hash.call(hash);
-	}
-
-	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..fcb58bf4dcd 100644
--- a/lib/Module.js
+++ b/lib/Module.js
@@ -2,384 +2,1441 @@
 	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
+ */
+
+/** @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
+ */
+
+/**
+ * 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 {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
+ */
+
+/** @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);
 	}
 
-	disconnect() {
-		this.hash = undefined;
-		this.renderedHash = undefined;
+	/**
+	 * 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);
+	}
 
-		this.reasons.length = 0;
-		this._rewriteChunkInReasons = undefined;
-		this._chunks.clear();
+	/**
+	 * 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);
+	}
+
+	/**
+	 * 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
 
-	addReason(module, dependency, explanation) {
-		this.reasons.push(new ModuleReason(module, dependency, explanation));
+	/**
+	 * Gets exports argument.
+	 * @returns {string} name of the exports argument
+	 */
+	get exportsArgument() {
+		return (this.buildInfo && this.buildInfo.exportsArgument) || "exports";
 	}
 
-	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;
+	/**
+	 * Gets module argument.
+	 * @returns {string} name of the module argument
+	 */
+	get moduleArgument() {
+		return (this.buildInfo && this.buildInfo.moduleArgument) || "module";
+	}
+
+	/**
+	 * 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;
+	}
+
+	/**
+	 * Gets number of warnings.
+	 * @returns {number} number of warnings
+	 */
+	getNumberOfWarnings() {
+		return this._warnings !== undefined ? this._warnings.length : 0;
 	}
 
-	_doRewriteChunkInReasons(oldChunk, newChunks) {
-		for (let i = 0; i < this.reasons.length; i++) {
-			this.reasons[i].rewriteChunks(oldChunk, newChunks);
+	/**
+	 * 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);
 	}
 
-	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;
+	/**
+	 * Returns list of errors if any.
+	 * @returns {Error[] | undefined} list of errors if any
+	 */
+	getErrors() {
+		return this._errors;
+	}
 
-		// Mangle export name if possible
-		if (this.isProvided(exportName)) {
-			if (this.buildMeta.exportsType === "namespace") {
-				return Template.numberToIdentifer(idx);
-			}
+	/**
+	 * Gets number of errors.
+	 * @returns {number} number of errors
+	 */
+	getNumberOfErrors() {
+		return this._errors !== undefined ? this._errors.length : 0;
+	}
+
+	/**
+	 * 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
 	}
 
-	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 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();
+	}
+
+	/* 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;
+	}
+
+	/**
+	 * 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();
+	}
+
+	/**
+	 * 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;
 	}
 
-	get arguments() {
-		throw new Error("Module.arguments was removed, there is no replacement.");
+	/**
+	 * 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;
 	}
 
-	set arguments(value) {
-		throw new Error("Module.arguments was removed, there is no replacement.");
+	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;
+	}
+
+	/**
+	 * 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..308b52089e9 100644
--- a/lib/ModuleFilenameHelpers.js
+++ b/lib/ModuleFilenameHelpers.js
@@ -2,20 +2,37 @@
 	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;
 
+// 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 +48,344 @@ 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 = "";
 	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);
+		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)
+		);
+		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 resource =
+		/** @type {ReturnStringCallback} */
+		(memoize(() => shortIdentifier().split("!").pop()));
+
 	const loaders = getBefore(shortIdentifier, "!");
 	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."); // false
+ * ModuleFilenameHelpers.matchPart("foo.js", "foo*"); // false
+ * ModuleFilenameHelpers.matchPart("foo.js", "foo.*"); // true
+ * 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..789559a82bd
--- /dev/null
+++ b/lib/ModuleGraph.js
@@ -0,0 +1,1094 @@
+/*
+	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;
+
+		/**
+		 * @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);
+	}
+
+	/**
+	 * 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 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);
+		// 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);
+				}
+			}
+		}
+		// 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);
+			}
+		}
+	}
+
+	/**
+	 * 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);
+		// 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);
+					}
+				}
+			}
+		}
+	}
+
+	/**
+	 * 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..65dba49eb7c
--- /dev/null
+++ b/lib/ModuleProfile.js
@@ -0,0 +1,108 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+class ModuleProfile {
+	constructor() {
+		this.startTime = Date.now();
+
+		this.factoryStartTime = 0;
+		this.factoryEndTime = 0;
+		this.factory = 0;
+		this.factoryParallelismFactor = 0;
+
+		this.restoringStartTime = 0;
+		this.restoringEndTime = 0;
+		this.restoring = 0;
+		this.restoringParallelismFactor = 0;
+
+		this.integrationStartTime = 0;
+		this.integrationEndTime = 0;
+		this.integration = 0;
+		this.integrationParallelismFactor = 0;
+
+		this.buildingStartTime = 0;
+		this.buildingEndTime = 0;
+		this.building = 0;
+		this.buildingParallelismFactor = 0;
+
+		this.storingStartTime = 0;
+		this.storingEndTime = 0;
+		this.storing = 0;
+		this.storingParallelismFactor = 0;
+
+		/** @type {{ start: number, end: number }[] | undefined} */
+		this.additionalFactoryTimes = undefined;
+		this.additionalFactories = 0;
+		this.additionalFactoriesParallelismFactor = 0;
+
+		/** @deprecated */
+		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..56adaff4bc9 100644
--- a/lib/ModuleTemplate.js
+++ b/lib/ModuleTemplate.js
@@ -2,92 +2,180 @@
 	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 memoize = require("./util/memoize");
 
+/** @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
+ */
+
+const getJavascriptModulesPlugin = memoize(() =>
+	require("./javascript/JavascriptModulesPlugin")
+);
 
+// 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) {
+		this._runtimeTemplate = runtimeTemplate;
+		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) => {
+						getJavascriptModulesPlugin()
+							.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) => {
+						getJavascriptModulesPlugin()
+							.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) => {
+						getJavascriptModulesPlugin()
+							.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) => {
+						getJavascriptModulesPlugin()
+							.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..a00bc661502
--- /dev/null
+++ b/lib/ModuleTypeConstants.js
@@ -0,0 +1,199 @@
+/*
+	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<"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"} 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;
+/** @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..1f5477b4da9 100644
--- a/lib/MultiCompiler.js
+++ b/lib/MultiCompiler.js
@@ -2,57 +2,190 @@
 	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");
+const MultiWatching = require("./MultiWatching");
+const ConcurrentCompilationError = require("./errors/ConcurrentCompilationError");
+const WebpackError = require("./errors/WebpackError");
+const ArrayQueue = require("./util/ArrayQueue");
 
-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))
-		};
+/**
+ * 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]>} */
+			done: new SyncHook(["stats"]),
+			/** @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>} */
+			watchRun: new MultiHook(compilers.map((c) => c.hooks.watchRun)),
+			/** @type {MultiHook>} */
+			infrastructureLog: new MultiHook(
+				compilers.map((c) => c.hooks.infrastructureLog)
+			)
+		});
 		this.compilers = compilers;
+		/** @type {MultiCompilerOptions} */
+		this._options = {
+			parallelism: options.parallelism || Infinity
+		};
+		/** @type {WeakMap} */
+		this.dependencies = new WeakMap();
+		this.running = false;
+
+		/** @type {(Stats | null)[]} */
+		const compilerStats = this.compilers.map(() => null);
 		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) => {
 				if (!compilerDone) {
 					compilerDone = true;
 					doneCompilers++;
 				}
 				compilerStats[compilerIndex] = stats;
 				if (doneCompilers === this.compilers.length) {
-					this.hooks.done.call(new MultiStats(compilerStats));
+					this.hooks.done.call(
+						new MultiStats(/** @type {Stats[]} */ (compilerStats))
+					);
 				}
 			});
 			// 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.running = false;
+		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);
+				}
+			}
+		}
+	}
+
+	get options() {
+		return Object.assign(
+			this.compilers.map((c) => c.options),
+			this._options
+		);
 	}
 
 	get outputPath() {
@@ -74,26 +207,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 +301,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 +328,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 +346,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 +361,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,104 +401,293 @@ 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);
 	}
 
+	/**
+	 * 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;
+		const parallelism = /** @type {number} */ (this._options.parallelism);
+		/**
+		 * 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;
+				return asyncLib.each(
+					nodes,
+					(node, callback) => {
+						if (node.compiler.watching) {
+							node.compiler.watching.close(callback);
+						} else {
+							callback();
+						}
+					},
+					() => 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));
+				}
+			}
+		};
+		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) return handler(new ConcurrentCompilationError());
+		if (this.running) {
+			handler(new ConcurrentCompilationError());
+			return;
+		}
+		this.running = true;
 
-		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();
-							}
-						}
+			const watchings = this._runGraph(
+				(compiler, idx, callback, isBlocked, setChanged, setInvalid) => {
+					const watching = compiler.watch(
+						Array.isArray(watchOptions) ? watchOptions[idx] : watchOptions,
+						callback
 					);
-					watchings.push(watching);
+					if (watching) {
+						watching._onInvalid = setInvalid;
+						watching._onChange = setChanged;
+						watching._isBlocked = isBlocked;
+					}
+					return watching;
 				},
-				() => {
-					// ignore
-				}
+				(compiler, watching, _callback) => {
+					if (compiler.watching !== watching) return;
+					if (!watching.running) watching.invalidate();
+				},
+				handler
 			);
+			return new MultiWatching(watchings, this);
 		}
 
-		return new MultiWatching(watchings, this);
+		return new MultiWatching([], this);
 	}
 
+	/**
+	 * 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 ConcurrentCompilationError());
+			return;
 		}
+		this.running = true;
 
-		const finalCallback = (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);
+			this._runGraph(
+				() => {},
+				(compiler, setupResult, callback) => compiler.run(callback),
+				(err, stats) => {
+					this.running = false;
+
+					if (callback !== undefined) {
+						return callback(err, stats);
 					}
-					finalCallback(null, new MultiStats(allStats));
 				}
 			);
 		}
@@ -280,4 +700,21 @@ module.exports = class MultiCompiler extends Tapable {
 			}
 		}
 	}
+
+	/**
+	 * Processes the provided error callback.
+	 * @param {ErrorCallback} callback signals when the compiler closes
+	 * @returns {void}
+	 */
+	close(callback) {
+		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..704e1f7ea49 100644
--- a/lib/MultiStats.js
+++ b/lib/MultiStats.js
@@ -2,90 +2,219 @@
 	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, children: NormalizedStatsOptions[] }} ChildOptions */
 
 class MultiStats {
+	/**
+	 * Creates an instance of MultiStats.
+	 * @param {Stats[]} stats the child stats
+	 */
 	constructor(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),
+			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.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);
 			}
 		}
-		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..381443c294b 100644
--- a/lib/MultiWatching.js
+++ b/lib/MultiWatching.js
@@ -2,33 +2,75 @@
 	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) {
 		this.watchings = watchings;
 		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) => {
 				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..2808b834bf4 100644
--- a/lib/NodeStuffPlugin.js
+++ b/lib/NodeStuffPlugin.js
@@ -2,178 +2,599 @@
 	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 NodeStuffInWebError = require("./errors/NodeStuffInWebError");
+const { evaluateToString } = require("./javascript/JavascriptParserHelpers");
+const { relative } = require("./util/fs");
+const { parseResource } = require("./util/identifier");
+
+/** @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 } = ImportMetaPlugin;
 
 class NodeStuffPlugin {
+	/**
+	 * Creates an instance of NodeStuffPlugin.
+	 * @param {NodeOptions} options options
+	 */
 	constructor(options) {
 		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 = /** @type {DependencyLocation} */ (expr.loc);
+						parser.state.module.addPresentationalDependency(dep);
+
+						if (withWarning) {
+							parser.state.module.addWarning(
+								new NodeStuffInWebError(
+									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 = /** @type {DependencyLocation} */ (expr.loc);
+						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 = /** @type {DependencyLocation} */ (expr.loc);
+							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 = /** @type {DependencyLocation} */ (expr.loc);
+							parser.state.module.addPresentationalDependency(dep);
+
+							if (warning) {
+								parser.state.module.addWarning(
+									new NodeStuffInWebError(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 NodeStuffInWebError(
+											usingProperty.loc,
+											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 = /** @type {DependencyLocation} */ (expr.loc);
+								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 = /** @type {DependencyLocation} */ (expr.loc);
+							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 = /** @type {DependencyLocation} */ (
+										usingProperty.loc
+									);
+									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 = /** @type {DependencyLocation} */ (usingProperty.loc);
+								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" }} identifiers options
+				 * @returns {void}
+				 */
+				const dirnameAndFilenameHandler = (
+					parser,
+					nodeOptions,
+					{ dirname, filename }
+				) => {
+					// Keep `import.meta.filename` in code
+					if (
+						nodeOptions.__filename === false &&
+						filename === IMPORT_META_FILENAME
+					) {
+						setModuleConstant(parser, filename, () => filename, "filename");
+					}
+
+					if (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.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 (
+						nodeOptions.__dirname === false &&
+						dirname === IMPORT_META_DIRNAME
+					) {
+						setModuleConstant(parser, dirname, () => dirname, "dirname");
+					}
+
+					if (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) {
+						// Keep `import.meta.dirname` and `import.meta.filename` in code
+						setModuleConstant(
 							parser,
-							"module",
-							`require(${JSON.stringify(moduleJsPath)})(module)`
+							IMPORT_META_DIRNAME,
+							() => IMPORT_META_DIRNAME,
+							"dirname"
 						);
-					});
+						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 && parserOptions.importMeta !== false) {
+						dirnameAndFilenameHandler(parser, localOptions, {
+							dirname: IMPORT_META_DIRNAME,
+							filename: IMPORT_META_FILENAME
+						});
+					}
 				};
 
 				normalModuleFactory.hooks.parser
-					.for("javascript/auto")
-					.tap("NodeStuffPlugin", handler);
+					.for(JAVASCRIPT_MODULE_TYPE_AUTO)
+					.tap(PLUGIN_NAME, (parser, parserOptions) => {
+						handler(parser, parserOptions, true, true);
+					});
 				normalModuleFactory.hooks.parser
-					.for("javascript/dynamic")
-					.tap("NodeStuffPlugin", handler);
+					.for(JAVASCRIPT_MODULE_TYPE_DYNAMIC)
+					.tap(PLUGIN_NAME, (parser, parserOptions) => {
+						handler(parser, parserOptions, true, false);
+					});
+				normalModuleFactory.hooks.parser
+					.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..754e0109ef6 100644
--- a/lib/NormalModule.js
+++ b/lib/NormalModule.js
@@ -2,234 +2,1409 @@
 	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 { getContext, runLoaders } = require("loader-runner");
+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 Compilation = require("./Compilation");
+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 HookWebpackError = require("./errors/HookWebpackError");
+const ModuleBuildError = require("./errors/ModuleBuildError");
+const ModuleError = require("./errors/ModuleError");
+const ModuleParseError = require("./errors/ModuleParseError");
+const ModuleWarning = require("./errors/ModuleWarning");
+const NonErrorEmittedError = require("./errors/NonErrorEmittedError");
+const UnhandledSchemeError = require("./errors/UnhandledSchemeError");
+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 { createFakeHook } = require("./util/deprecation");
+const formatLocation = require("./util/formatLocation");
+const { join } = require("./util/fs");
+const {
+	ABSOLUTE_PATH_REGEXP,
+	absolutify,
+	contextify,
+	makePathsRelative
+} = require("./util/identifier");
+const makeSerializable = require("./util/makeSerializable");
+const memoize = require("./util/memoize");
+const parseJson = require("./util/parseJson");
+
+/** @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("./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
+ */
+
+/** @typedef {{ [k: string]: EXPECTED_ANY }} ParserOptions */
+/** @typedef {{ [k: string]: EXPECTED_ANY }} GeneratorOptions */
+
+/**
+ * @template T
+ * @typedef {import("../declarations/LoaderContext").LoaderContext} LoaderContext
+ */
+
+/**
+ * @template T
+ * @typedef {import("../declarations/LoaderContext").NormalModuleLoaderContext} NormalModuleLoaderContext
+ */
+
+/** @typedef {(content: string) => boolean} NoParseFn */
+
+const getInvalidDependenciesModuleWarning = memoize(() =>
+	require("./errors/InvalidDependenciesModuleWarning")
+);
+
+const getExtractSourceMap = memoize(() => require("./util/extractSourceMap"));
+
+const getValidate = memoize(() => require("schema-utils").validate);
+
+const getHarmonyImportSideEffectDependency = memoize(() =>
+	require("./dependencies/HarmonyImportSideEffectDependency")
+);
 
-const asString = buf => {
-	if (Buffer.isBuffer(buf)) {
-		return buf.toString("utf-8");
+/**
+ * @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 buf;
+	mod._addedSideEffectsBailout.add(moduleGraph);
+	moduleGraph
+		.getOptimizationBailout(mod)
+		.push(
+			() =>
+				`Dependency (${dep.type}) with side effects at ${formatLocation(dep.loc)}`
+		);
 };
 
-const asBuffer = str => {
-	if (!Buffer.isBuffer(str)) {
-		return Buffer.from(str, "utf-8");
+// Maximum recursive descent depth before switching to the iterative walker.
+// #20986 reported overflow around 1300 modules in webpack 5.107.0 where each
+// step consumed two stack frames (`NormalModule.getSideEffectsConnectionState`
+// plus `HarmonyImportSideEffectDependency.getModuleEvaluationSideEffectsState`).
+// `walkSideEffectsRecursive` folds the second call into the first and uses
+// one frame per module, so this limit caps the native stack at half the depth
+// where the original code overflowed — well within the safe range across
+// platforms while keeping the common (shallow) case purely recursive.
+const SIDE_EFFECTS_RECURSION_LIMIT = 2000;
+
+/**
+ * Iterative form of the side-effects walker. Used as a fallback once the
+ * recursive form reaches `SIDE_EFFECTS_RECURSION_LIMIT` so deep chains
+ * (#20986) don't overflow V8's stack. Safe to enter while ancestors set
+ * `_isEvaluatingSideEffects` on their own modules — those are treated as
+ * `CIRCULAR_CONNECTION` if revisited, matching the original recursive
+ * behavior.
+ * @param {NormalModule} rootMod the module to walk
+ * @param {ModuleGraph} moduleGraph the module graph
+ * @param {SideEffectsWalkContext} ctx per-walk cycle-tracking context
+ * @returns {ConnectionState} the side-effects connection state
+ */
+const walkSideEffectsIterative = (rootMod, moduleGraph, ctx) => {
+	const SideEffectDep = getHarmonyImportSideEffectDependency();
+
+	/** @type {NormalModule[]} */
+	const modStack = [rootMod];
+	/** @type {Dependency[][]} */
+	const depsStack = [rootMod.dependencies];
+	const indexStack = [0];
+	/** @type {ConnectionState[]} */
+	const currentStack = [false];
+	rootMod._isEvaluatingSideEffects = true;
+
+	/**
+	 * Result from a just-popped child frame, to be applied to the new
+	 * top's current dep. `undefined` means "no pending; advance".
+	 * @type {ConnectionState | undefined}
+	 */
+	let pending;
+
+	while (modStack.length > 0) {
+		const top = modStack.length - 1;
+		const topMod = modStack[top];
+		const deps = depsStack[top];
+		let index = indexStack[top];
+		let current = currentStack[top];
+
+		if (pending !== undefined) {
+			const state = pending;
+			pending = undefined;
+			const dep = deps[index];
+
+			if (state === true) {
+				recordSideEffectsBailout(topMod, moduleGraph, dep);
+				topMod._isEvaluatingSideEffects = false;
+				// `true` is monotonic — safe to memoize regardless of cycle
+				// status, matching the direct-bailout branch below.
+				topMod._sideEffectsStateGraph = moduleGraph;
+				topMod._sideEffectsStateValue = true;
+				modStack.pop();
+				depsStack.pop();
+				indexStack.pop();
+				currentStack.pop();
+				pending = true;
+				continue;
+			}
+			if (state !== ModuleGraphConnection.CIRCULAR_CONNECTION) {
+				current = ModuleGraphConnection.addConnectionStates(current, state);
+			}
+			index++;
+		}
+
+		let descended = false;
+		const depCount = deps.length;
+		while (index < depCount) {
+			const dep = deps[index];
+			/** @type {ConnectionState} */
+			let state;
+
+			if (dep instanceof SideEffectDep) {
+				const refModule = moduleGraph.getModule(dep);
+				if (!refModule) {
+					state = true;
+				} else if (refModule instanceof NormalModule) {
+					// Cache hit
+					if (refModule._sideEffectsStateGraph === moduleGraph) {
+						state = /** @type {ConnectionState} */ (
+							refModule._sideEffectsStateValue
+						);
+					}
+					// Fast-path checks inlined to avoid the helper call.
+					else if (refModule.factoryMeta !== undefined) {
+						if (refModule.factoryMeta.sideEffectFree) {
+							state = false;
+						} else if (refModule.factoryMeta.sideEffectFree === false) {
+							state = true;
+						} else if (
+							!(
+								refModule.buildMeta !== undefined &&
+								refModule.buildMeta.sideEffectFree
+							)
+						) {
+							state = true;
+						} else if (refModule._isEvaluatingSideEffects) {
+							ctx.circular = true;
+							state = ModuleGraphConnection.CIRCULAR_CONNECTION;
+						} else {
+							// Descend
+							indexStack[top] = index;
+							currentStack[top] = current;
+							refModule._isEvaluatingSideEffects = true;
+							modStack.push(refModule);
+							depsStack.push(refModule.dependencies);
+							indexStack.push(0);
+							currentStack.push(false);
+							descended = true;
+							break;
+						}
+					} else if (
+						!(
+							refModule.buildMeta !== undefined &&
+							refModule.buildMeta.sideEffectFree
+						)
+					) {
+						state = true;
+					} else if (refModule._isEvaluatingSideEffects) {
+						ctx.circular = true;
+						state = ModuleGraphConnection.CIRCULAR_CONNECTION;
+					} else {
+						// Descend
+						indexStack[top] = index;
+						currentStack[top] = current;
+						refModule._isEvaluatingSideEffects = true;
+						modStack.push(refModule);
+						depsStack.push(refModule.dependencies);
+						indexStack.push(0);
+						currentStack.push(false);
+						descended = true;
+						break;
+					}
+				} else {
+					ctx.circular = true;
+					state = refModule.getSideEffectsConnectionState(moduleGraph);
+				}
+			} else {
+				state = dep.getModuleEvaluationSideEffectsState(moduleGraph);
+			}
+
+			if (state === true) {
+				recordSideEffectsBailout(topMod, moduleGraph, dep);
+				topMod._isEvaluatingSideEffects = false;
+				topMod._sideEffectsStateGraph = moduleGraph;
+				topMod._sideEffectsStateValue = true;
+				modStack.pop();
+				depsStack.pop();
+				indexStack.pop();
+				currentStack.pop();
+				pending = true;
+				descended = true;
+				break;
+			}
+			if (state !== ModuleGraphConnection.CIRCULAR_CONNECTION) {
+				current = ModuleGraphConnection.addConnectionStates(current, state);
+			}
+			index++;
+		}
+
+		if (descended) continue;
+
+		topMod._isEvaluatingSideEffects = false;
+		if (!ctx.circular) {
+			topMod._sideEffectsStateGraph = moduleGraph;
+			topMod._sideEffectsStateValue = current;
+		}
+		pending = current;
+		modStack.pop();
+		depsStack.pop();
+		indexStack.pop();
+		currentStack.pop();
 	}
-	return str;
+
+	return /** @type {ConnectionState} */ (pending);
 };
 
-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, "/");
+/**
+ * @typedef {object} SideEffectsWalkContext
+ * @property {boolean} circular whether a cycle was seen anywhere in this walk
+ */
+
+/**
+ * Walks back up a stack of linear-chain ancestors, applying the result
+ * `state` of the chain's tail to each ancestor. Each ancestor in the
+ * stack had exactly one `HarmonyImportSideEffectDependency` returning
+ * `state`, so its result is just `state` (with the usual aggregation
+ * rules) and we can avoid building per-frame `current` accumulators.
+ * @param {(NormalModule | Dependency)[] | null} ancestors interleaved stack of `[mod, sideEffectDep, mod, sideEffectDep, …]` in descent order; `null` if there were none
+ * @param {ConnectionState} state result from the chain's tail
+ * @param {ModuleGraph} moduleGraph the module graph
+ * @param {SideEffectsWalkContext} ctx per-walk cycle-tracking context
+ * @returns {ConnectionState} the root ancestor's result
+ */
+const propagateLinearResult = (ancestors, state, moduleGraph, ctx) => {
+	if (ancestors === null) return state;
+	while (ancestors.length > 0) {
+		const dep = /** @type {Dependency} */ (ancestors.pop());
+		const ancestor = /** @type {NormalModule} */ (ancestors.pop());
+		ancestor._isEvaluatingSideEffects = false;
+
+		if (state === true) {
+			recordSideEffectsBailout(ancestor, moduleGraph, dep);
+			// `true` is monotonic — safe to cache regardless of cycle status.
+			ancestor._sideEffectsStateGraph = moduleGraph;
+			ancestor._sideEffectsStateValue = true;
+		} else if (state === ModuleGraphConnection.CIRCULAR_CONNECTION) {
+			// CIRCULAR_CONNECTION is filtered before folding into `current`,
+			// so the ancestor's `current` stays at its initial `false`. From
+			// this point upward the propagated state is `false`, and the
+			// cycle taint prevents memoization further up (handled by
+			// `ctx.circular`).
+			state = false;
+		} else if (!ctx.circular) {
+			ancestor._sideEffectsStateGraph = moduleGraph;
+			ancestor._sideEffectsStateValue = state;
+		}
+	}
+	return state;
+};
+
+/**
+ * Recursive form of the side-effects walker. Folds the descent through
+ * `HarmonyImportSideEffectDependency.getModuleEvaluationSideEffectsState`
+ * directly into the loop so each module costs only one V8 stack frame
+ * (vs. two in the original recursive code).
+ *
+ * Linear-chain heads (modules with exactly one
+ * `HarmonyImportSideEffectDependency` to another `NormalModule`) are
+ * walked iteratively inside the function via an explicit
+ * `linearAncestors` stack — no additional V8 frame per descent — and
+ * their results are propagated back up by `propagateLinearResult`. This
+ * keeps stack consumption at O(1) for the common deep-import-chain
+ * pattern that motivated #20986 and also avoids the heavier iterative
+ * fallback.
+ *
+ * Falls back to `walkSideEffectsIterative` only when a *non-linear*
+ * walk reaches `SIDE_EFFECTS_RECURSION_LIMIT` — i.e. a deep tree, where
+ * each level genuinely needs its own recursion frame.
+ *
+ * Caches the result on the module when the walk did not encounter a
+ * cycle, so subsequent queries (e.g. repeated lookups during
+ * `SideEffectsFlagPlugin`'s incoming-connection optimization and its
+ * `exportInfo.getTarget` callbacks) return in O(1).
+ * @param {NormalModule} mod the module being walked
+ * @param {ModuleGraph} moduleGraph the module graph
+ * @param {number} depth current recursion depth (only counts true V8 frames)
+ * @param {EXPECTED_ANY} SideEffectDep `HarmonyImportSideEffectDependency` constructor, resolved once at the public entry to avoid repeated `require` lookups in the recursive call
+ * @param {SideEffectsWalkContext} ctx per-walk cycle-tracking context
+ * @returns {ConnectionState} the side-effects connection state
+ */
+const walkSideEffectsRecursive = (
+	mod,
+	moduleGraph,
+	depth,
+	SideEffectDep,
+	ctx
+) => {
+	// Interleaved `[mod, linearDep, mod, linearDep, …]` for the linear
+	// chain head; `null` until the first descent.
+	/** @type {(NormalModule | Dependency)[] | null} */
+	let linearAncestors = null;
+
+	// Walk the linear-chain head iteratively. Every loop iteration peels
+	// off one module without consuming a V8 stack frame.
+	while (true) {
+		if (mod._sideEffectsStateGraph === moduleGraph) {
+			return propagateLinearResult(
+				linearAncestors,
+				/** @type {ConnectionState} */ (mod._sideEffectsStateValue),
+				moduleGraph,
+				ctx
+			);
+		}
+
+		if (mod.factoryMeta !== undefined) {
+			if (mod.factoryMeta.sideEffectFree) {
+				return propagateLinearResult(linearAncestors, false, moduleGraph, ctx);
+			}
+			if (mod.factoryMeta.sideEffectFree === false) {
+				return propagateLinearResult(linearAncestors, true, moduleGraph, ctx);
+			}
+		}
+		if (!(mod.buildMeta !== undefined && mod.buildMeta.sideEffectFree)) {
+			return propagateLinearResult(linearAncestors, true, moduleGraph, ctx);
+		}
+		if (mod._isEvaluatingSideEffects) {
+			ctx.circular = true;
+			return propagateLinearResult(
+				linearAncestors,
+				ModuleGraphConnection.CIRCULAR_CONNECTION,
+				moduleGraph,
+				ctx
+			);
+		}
+
+		// A real ESM module's `dependencies` typically include one
+		// `HarmonyImportSideEffectDependency` plus several non-recursive deps
+		// (export specifiers, const dependencies, …) that report
+		// `false`/`CIRCULAR_CONNECTION` from
+		// `getModuleEvaluationSideEffectsState`. Walk the deps in order
+		// here: as long as at most one is a `SideEffectDep` and no
+		// non-recursive dep triggers a bailout or contributes a non-`false`
+		// state, we can still tail-call iteratively through that one
+		// `SideEffectDep` — no V8 frame needed. This is what keeps the
+		// 1300-module cyclic chain from #20986 from overflowing V8's stack
+		// even though each generated module has multiple `dependencies`.
+		const deps = mod.dependencies;
+		/** @type {Dependency | null} */
+		let linearDep = null;
+		/** @type {ConnectionState} */
+		let nonRecursiveCurrent = false;
+		let linearOk = true;
+		for (let i = 0; i < deps.length; i++) {
+			const dep = deps[i];
+			if (dep instanceof SideEffectDep) {
+				if (linearDep !== null) {
+					// Two `SideEffectDep`s in the same module — fall back to
+					// the general walk so each can recurse normally.
+					linearOk = false;
+					break;
+				}
+				linearDep = dep;
+			} else {
+				const state = dep.getModuleEvaluationSideEffectsState(moduleGraph);
+				if (state === true) {
+					recordSideEffectsBailout(mod, moduleGraph, dep);
+					mod._sideEffectsStateGraph = moduleGraph;
+					mod._sideEffectsStateValue = true;
+					return propagateLinearResult(linearAncestors, true, moduleGraph, ctx);
+				}
+				if (state !== ModuleGraphConnection.CIRCULAR_CONNECTION) {
+					nonRecursiveCurrent = ModuleGraphConnection.addConnectionStates(
+						nonRecursiveCurrent,
+						state
+					);
 				}
 			}
-			if (/^\//.test(splitPath[0])) {
-				splitPath[0] = path.posix.relative(context, splitPath[0]);
+		}
+
+		if (!linearOk || nonRecursiveCurrent !== false) {
+			// Multiple `SideEffectDep`s, or a non-recursive dep contributed
+			// a non-`false` state (e.g. `TRANSITIVE_ONLY`). The linear
+			// propagation rule "ancestor's current = chain state" no longer
+			// holds, so fall back to the general walk.
+			break;
+		}
+
+		if (linearDep === null) {
+			// No `SideEffectDep` — the module is a leaf as far as the
+			// side-effects graph is concerned. Cache and propagate `false`.
+			if (!ctx.circular) {
+				mod._sideEffectsStateGraph = moduleGraph;
+				mod._sideEffectsStateValue = false;
 			}
-			if (!/^(\.\.\/|\/|[a-zA-Z]:\\)/.test(splitPath[0])) {
-				splitPath[0] = "./" + splitPath[0];
+			return propagateLinearResult(linearAncestors, false, moduleGraph, ctx);
+		}
+
+		const refModule = moduleGraph.getModule(linearDep);
+		if (!refModule) {
+			recordSideEffectsBailout(mod, moduleGraph, linearDep);
+			mod._sideEffectsStateGraph = moduleGraph;
+			mod._sideEffectsStateValue = true;
+			return propagateLinearResult(linearAncestors, true, moduleGraph, ctx);
+		}
+		if (!(refModule instanceof NormalModule)) {
+			// Non-NormalModule's `getSideEffectsConnectionState` re-enters
+			// the public method; defer to the general walk for safety.
+			break;
+		}
+
+		mod._isEvaluatingSideEffects = true;
+		if (linearAncestors === null) linearAncestors = [];
+		// Push (mod, linearDep) so `propagateLinearResult` records bailouts
+		// against the actual `SideEffectDep` that triggered the descent —
+		// which may not be `dependencies[0]` when the module also has
+		// export / const dependencies.
+		linearAncestors.push(mod, linearDep);
+		mod = refModule;
+		continue;
+	}
+
+	// Non-linear walk. Each genuine recursive call costs one V8 frame, so
+	// honour the depth limit here.
+	if (depth >= SIDE_EFFECTS_RECURSION_LIMIT) {
+		return propagateLinearResult(
+			linearAncestors,
+			walkSideEffectsIterative(mod, moduleGraph, ctx),
+			moduleGraph,
+			ctx
+		);
+	}
+
+	mod._isEvaluatingSideEffects = true;
+	/** @type {ConnectionState} */
+	let current = false;
+
+	for (const dep of mod.dependencies) {
+		/** @type {ConnectionState} */
+		let state;
+		if (dep instanceof SideEffectDep) {
+			const refModule = moduleGraph.getModule(dep);
+			if (!refModule) {
+				state = true;
+			} else if (refModule instanceof NormalModule) {
+				state = walkSideEffectsRecursive(
+					refModule,
+					moduleGraph,
+					depth + 1,
+					SideEffectDep,
+					ctx
+				);
+			} else {
+				// Non-NormalModule's `getSideEffectsConnectionState` (notably
+				// `ConcatenatedModule` delegating to its root) re-enters the
+				// public method and may walk through modules that the outer
+				// walk has marked as evaluating. We can't observe whether
+				// the inner walk hit a cycle that reflected the outer's
+				// state, so treat the walk as cycle-tainted and skip the
+				// cache for safety.
+				ctx.circular = true;
+				state = refModule.getSideEffectsConnectionState(moduleGraph);
 			}
-			return splitPath.join("?");
-		})
-		.join("!");
+		} else {
+			state = dep.getModuleEvaluationSideEffectsState(moduleGraph);
+		}
+
+		if (state === true) {
+			recordSideEffectsBailout(mod, moduleGraph, dep);
+			mod._isEvaluatingSideEffects = false;
+			// `true` is monotonic — once any dep declares side effects, the
+			// answer is `true` regardless of how cycles resolve, so it's
+			// always safe to memoize.
+			mod._sideEffectsStateGraph = moduleGraph;
+			mod._sideEffectsStateValue = true;
+			return propagateLinearResult(linearAncestors, true, moduleGraph, ctx);
+		}
+		if (state !== ModuleGraphConnection.CIRCULAR_CONNECTION) {
+			current = ModuleGraphConnection.addConnectionStates(current, state);
+		}
+	}
+
+	mod._isEvaluatingSideEffects = false;
+
+	// Only memoize when no cycle has been observed anywhere in the walk
+	// since the public entry. A cycle anywhere can affect any ancestor's
+	// result (the back-edge target's contribution is hidden by
+	// CIRCULAR_CONNECTION short-circuiting), so this single flag is the
+	// conservative-but-cheap approximation of "this subtree's result is
+	// independent of cycle context". The public entry resets the flag.
+	if (!ctx.circular) {
+		mod._sideEffectsStateGraph = moduleGraph;
+		mod._sideEffectsStateValue = current;
+	}
+	return propagateLinearResult(linearAncestors, current, moduleGraph, ctx);
 };
 
-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} source a source path
+ * @param {AssociatedObjectForCache=} associatedObjectForCache an object to which the cache will be attached
+ * @returns {string} new source path
+ */
+const contextifySourceUrl = (context, source, associatedObjectForCache) => {
+	if (source.startsWith("webpack://")) return source;
+	return `webpack://${makePathsRelative(
+		context,
+		source,
+		associatedObjectForCache
+	)}`;
+};
 
-		Error.captureStackTrace(this, this.constructor);
+/**
+ * @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
+	};
+};
+
+/**
+ * @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
+ */
 
 /**
- * @typedef {Object} CachedSourceEntry
- * @property {TODO} source the generated source
- * @property {string} hash the hash value
+ * @typedef {object} NormalModuleCompilationHooks
+ * @property {SyncHook<[AnyLoaderContext, NormalModule]>} loader
+ * @property {SyncHook<[LoaderItem[], NormalModule, AnyLoaderContext]>} beforeLoaders
+ * @property {SyncHook<[NormalModule]>} beforeParse
+ * @property {SyncHook<[NormalModule]>} beforeSnapshot
+ * @property {DeprecatedReadResourceForScheme} readResourceForScheme
+ * @property {HookMap>} readResource
+ * @property {SyncWaterfallHook<[Result, NormalModule]>} processResult
+ * @property {AsyncSeriesBailHook<[NormalModule, NeedBuildContext], boolean>} needBuild
  */
 
+/**
+ * @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 */
+
+/** @type {WeakMap} */
+const compilationHooksMap = new WeakMap();
+
 class NormalModule extends Module {
+	/**
+	 * @param {Compilation} compilation the compilation
+	 * @returns {NormalModuleCompilationHooks} the attached hooks
+	 */
+	static getCompilationHooks(compilation) {
+		if (!(compilation instanceof Compilation)) {
+			throw new TypeError(
+				"The 'compilation' argument must be an instance of Compilation"
+			);
+		}
+		let hooks = compilationHooksMap.get(compilation);
+		if (hooks === undefined) {
+			hooks = {
+				loader: new SyncHook(["loaderContext", "module"]),
+				beforeLoaders: new SyncHook(["loaders", "module", "loaderContext"]),
+				beforeParse: new SyncHook(["module"]),
+				beforeSnapshot: new SyncHook(["module"]),
+				// TODO webpack 6 deprecate
+				readResourceForScheme: new HookMap((scheme) => {
+					const hook =
+						/** @type {NormalModuleCompilationHooks} */
+						(hooks).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)
+									)
+								)
+						})
+					);
+				}),
+				readResource: new HookMap(
+					() => new AsyncSeriesBailHook(["loaderContext"])
+				),
+				processResult: new SyncWaterfallHook(["result", "module"]),
+				needBuild: new AsyncSeriesBailHook(["module", "context"])
+			};
+			compilationHooksMap.set(
+				compilation,
+				/** @type {NormalModuleCompilationHooks} */ (hooks)
+			);
+		}
+		return /** @type {NormalModuleCompilationHooks} */ (hooks);
+	}
+
+	/**
+	 * @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;
-
+		/**
+		 * @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 results computed without encountering
+		 * a circular connection (see `walkSideEffectsRecursive`).
+		 * @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;
+	}
+
+	/**
+	 * 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);
+	}
 
-		if (typeof sourceMap === "string") {
-			return new OriginalSource(content, sourceMap);
+	/**
+	 * 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
+	}
+
+	/**
+	 * @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);
 				}
-				const currentLoader = this.getCurrentLoader(loaderContext);
-				this.warnings.push(
-					new ModuleWarning(this, warning, {
-						from: requestShortener.shorten(currentLoader.loader)
+				this.addWarning(
+					new ModuleWarning(warning, {
+						from: getCurrentLoaderName()
 					})
 				);
 			},
-			emitError: error => {
+			/** @type {LoaderContext["emitError"]} */
+			emitError: (error) => {
 				if (!(error instanceof Error)) {
 					error = new NonErrorEmittedError(error);
 				}
-				const currentLoader = this.getCurrentLoader(loaderContext);
-				this.errors.push(
-					new ModuleError(this, error, {
-						from: requestShortener.shorten(currentLoader.loader)
+				this.addError(
+					new ModuleError(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);
+
+		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 +1418,300 @@ 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 NonErrorEmittedError(err);
+				}
+				const currentLoader = this.getCurrentLoader(loaderContext);
+				const error = new ModuleBuildError(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 ModuleBuildError(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 UnhandledSchemeError(
+													/** @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 ModuleWarning(/** @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);
-				}
-
-				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();
+				buildInfo.cacheable = buildInfo.cacheable && result.cacheable;
+				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 +1721,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 +1751,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 +1763,61 @@ 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()
+			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
 		};
 
-		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 +1825,528 @@ 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 ModuleParseError(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) {
+		// Fresh per-walk context; re-entrant calls (e.g.
+		// `ConcatenatedModule.getSideEffectsConnectionState` delegating back
+		// through here) get their own and can't clobber the outer walk.
+		return walkSideEffectsRecursive(
 			this,
-			dependencyTemplates,
-			runtimeTemplate,
-			type
+			moduleGraph,
+			0,
+			getHarmonyImportSideEffectDependency(),
+			{ circular: false }
 		);
+	}
 
-		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);
+	}
+
+	/**
+	 * 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);
 	}
 
-	updateHash(hash) {
-		hash.update(this._buildHash);
-		super.updateHash(hash);
+	/**
+	 * @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..8f7cf6b99ad 100644
--- a/lib/NormalModuleFactory.js
+++ b/lib/NormalModuleFactory.js
@@ -1,443 +1,1365 @@
 /*
- 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 { getContext } = require("loader-runner");
 const asyncLib = require("neo-async");
 const {
-	Tapable,
-	AsyncSeriesWaterfallHook,
-	SyncWaterfallHook,
+	AsyncSeriesBailHook,
+	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 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 { join } = require("./util/fs");
+const {
+	escapeHashInPathRequest,
+	parseResource,
+	parseResourceWithoutFragment
+} = require("./util/identifier");
+
+/** @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
+ */
+
+/** @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 = /^[^.]/;
 
-const loaderToIdent = data => {
+/**
+ * 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(", ");
 
-class NormalModuleFactory extends Tapable {
-	constructor(context, resolverFactory, options) {
+	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 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 */
+
+/** @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>} */
+			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 {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>} */
+			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();
+		/** @type {Set} */
+		this._restoredUnsafeCacheEntries = new Set();
 
-			// 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();
 
-					createdModule = this.hooks.module.call(createdModule, result);
+						const createData =
+							/** @type {CreateData} */
+							(resolveData.createData);
 
-					return callback(null, createdModule);
+						this.hooks.createModule.callAsync(
+							createData,
+							resolveData,
+							(err, createdModule) => {
+								if (!createdModule) {
+									if (!resolveData.request) {
+										return callback(new Error("Empty dependency (no request)"));
+									}
+
+									// 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,
+							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,
-									userRequest,
-									rawRequest: request,
-									loaders,
-									resource,
-									matchResource,
-									resourceResolveData,
-									settings,
-									type,
-									parser: this.getParser(type, settings.parser),
-									generator: this.getGenerator(type, settings.generator),
-									resolveOptions
-								});
+
+					/** @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;
+
+						try {
+							Object.assign(data.createData, {
+								layer:
+									layer === undefined ? contextInfo.issuerLayer || null : layer,
+								request: stringifyLoadersAndResource(
+									allLoaders,
+									resourceData.resource
+								),
+								userRequest,
+								rawRequest: request,
+								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),
+								generatorOptions: settings.generator,
+								resolveOptions,
+								extractSourceMap: settings.extractSourceMap || false
 							});
+						} catch (createDataErr) {
+							return callback(/** @type {Error} */ (createDataErr));
+						}
+						callback();
+					});
+					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);
+			});
+		});
+	}
 
-					if (module && this.cachePredicate(module)) {
-						for (const d of dependencies) {
-							dependencyCache.set(d, module);
+	/**
+	 * 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);
+			}
+		);
+	}
 
-					callback(null, module);
-				});
+	/**
+	 * 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
+	) {
+		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);
+				callback(null, /** @type {string[]} */ (hints).filter(Boolean));
 			}
 		);
 	}
 
-	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
+			 */
 			(item, callback) => {
 				resolver.resolve(
 					contextInfo,
 					context,
 					item.loader,
-					{},
-					(err, result) => {
+					resolveContext,
+					(err, result, resolveRequest) => {
 						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 +1368,133 @@ 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
+										);
+						/** @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]);
-			}
+	/**
+	 * 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 +1502,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..c1020c04be6 100644
--- a/lib/NormalModuleReplacementPlugin.js
+++ b/lib/NormalModuleReplacementPlugin.js
@@ -2,49 +2,73 @@
 	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) {
 		this.resourceRegExp = resourceRegExp;
 		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..12d2994f696 100644
--- a/lib/PrefetchPlugin.js
+++ b/lib/PrefetchPlugin.js
@@ -2,22 +2,39 @@
 	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) {
 			this.context = context;
 			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 +42,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..821a80bea91 100644
--- a/lib/ProgressPlugin.js
+++ b/lib/ProgressPlugin.js
@@ -2,245 +2,811 @@
 	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 { 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(() => {
+	const cli = require("./cli");
+
+	return 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("./Compilation").FactorizeModuleOptions} FactorizeModuleOptions */
+/** @typedef {import("./Dependency")} Dependency */
+/** @typedef {import("./Entrypoint").EntryOptions} EntryOptions */
+/** @typedef {import("./Module")} Module */
+/** @typedef {import("./ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */
+/** @typedef {import("./logging/Logger").Logger} Logger */
+/** @typedef {import("./cli").Colors} Colors */
+
+/**
+ * Defines the async queue type used by this module.
+ * @template T, K, R
+ * @typedef {import("./util/AsyncQueue")} AsyncQueue
+ */
+
+/**
+ * Defines the counts data type used by this module.
+ * @typedef {object} CountsData
+ * @property {number} modulesCount modules count
+ * @property {number} dependenciesCount dependencies count
+ */
+
+/**
+ * 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 */
 				}
-				msg += ` ${detail}`;
-			}
-		}
-		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;
+				if (stateItem === undefined) {
+					lastStateInfo.length = i;
+				} else {
+					lastStateItem.value = stateItem;
+					lastStateItem.time = now;
+					lastStateInfo.length = i + 1;
 				}
-				lastState = state;
-				lastStateTime = now;
 			}
+		} else {
+			lastStateInfo[i] = {
+				value: stateItem,
+				time: now
+			};
 		}
-		goToLineStart(msg);
-		process.stderr.write(msg);
+	}
+};
+
+/**
+ * @param {string} name progress bar name
+ * @param {string} color progress bar color
+ * @returns {(percentage: number) => string} bar renderer
+ */
+const createReportBar = (name, color) => {
+	const c = getColors();
+
+	return (percentage) => {
+		const w = Math.round(percentage * BAR_LENGTH);
+		const filled = BLOCK_CHAR.repeat(w);
+		const empty = BLOCK_CHAR.repeat(BAR_LENGTH - w);
+		const colorFn =
+			color in c ? c[/** @type {keyof Colors} */ (color)] : c.green;
+
+		return `${[BULLET_ICON, name, filled].map(colorFn).join(" ")}${c.white(empty)}`;
 	};
+};
+
+/** @typedef {Required, boolean>>} ProgressBarOptions */
 
-	const goToLineStart = nextMessage => {
-		let str = "";
-		for (; lineCaretPosition > nextMessage.length; lineCaretPosition--) {
-			str += "\b \b";
+/**
+ * Creates a default handler.
+ * @param {boolean | null | undefined} profile need profile
+ * @param {Logger} logger logger
+ * @param {ProgressBarOptions | false=} progressBar render bar
+ * @returns {HandlerFn} default handler
+ */
+const createDefaultHandler = (profile, logger, progressBar) => {
+	/** @type {{ value: string | undefined, time: number }[]} */
+	const lastStateInfo = [];
+
+	/** @type {HandlerFn} */
+	const defaultHandler = (percentage, msg, ...args) => {
+		if (profile) {
+			reportProfile(logger, lastStateInfo, percentage, msg, args);
 		}
-		for (var i = 0; i < lineCaretPosition; i++) {
-			str += "\b";
+
+		if (progressBar) {
+			const reportBar = createReportBar(progressBar.name, progressBar.color);
+			const c = getColors();
+			/** @type {string} */
+			const currentBar = reportBar(percentage);
+
+			if (percentage === 1) {
+				logger.status();
+			} else if (msg) {
+				logger.status(
+					`${currentBar} (${Math.floor(percentage * 100)}%)`,
+					`\n${[msg, ...args].map(c.gray).join(" ")}`
+				);
+			} else {
+				logger.status(`${currentBar} (${Math.floor(percentage * 100)}%)`);
+			}
+			return;
 		}
-		lineCaretPosition = nextMessage.length;
-		if (str) process.stderr.write(str);
+
+		logger.status(`${Math.floor(percentage * 100)}%`, msg, ...args);
+		if (percentage === 1 || (!msg && args.length === 0)) logger.status();
 	};
 
 	return defaultHandler;
 };
 
+const SKIPPED_QUEUE_CONTEXTS = ["import-module", "load-module"];
+
+/**
+ * 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
+};
+
 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 };
+		this.profile = merged.profile;
+		this.handler = merged.handler;
+		this.modulesCount = merged.modulesCount;
+		this.dependenciesCount = merged.dependenciesCount;
+		this.showEntries = merged.entries;
+		this.showModules = merged.modules;
+		this.showDependencies = merged.dependencies;
+		this.showActiveModules = merged.activeModules;
+		this.percentBy = merged.percentBy;
+
+		const progressBar = merged.progressBar === true ? {} : merged.progressBar;
+		/** @type {ProgressBarOptions | false} */
+		this.progressBar = progressBar
+			? { name: "Build", color: "green", ...progressBar }
+			: false;
 	}
 
+	/**
+	 * 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
+		const handler =
+			this.handler ||
+			createDefaultHandler(
+				this.profile,
+				compiler.getInfrastructureLogger("webpack.Progress"),
+				this.progressBar
+			);
+		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 skippedModulesCount = 0;
+		let dependenciesCount = 0;
+		let skippedDependenciesCount = 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 moduleDone = module => {
-				doneModules++;
+		/**
+		 * Processes the provided factorize queue.
+		 * @template T
+		 * @param {AsyncQueue} factorizeQueue async queue
+		 * @param {T} _item item
+		 */
+		const factorizeAdd = (factorizeQueue, _item) => {
+			if (SKIPPED_QUEUE_CONTEXTS.includes(factorizeQueue.getContext())) {
+				skippedDependenciesCount++;
+			}
+			dependenciesCount++;
+			if (dependenciesCount < 50 || dependenciesCount % 100 === 0) {
+				updateThrottled();
+			}
+		};
+
+		const factorizeDone = () => {
+			doneDependencies++;
+			if (doneDependencies < 50 || doneDependencies % 100 === 0) {
+				updateThrottled();
+			}
+		};
+
+		/**
+		 * Processes the provided add module queue.
+		 * @template T
+		 * @param {AsyncQueue} addModuleQueue async queue
+		 * @param {T} _item item
+		 */
+		const moduleAdd = (addModuleQueue, _item) => {
+			if (SKIPPED_QUEUE_CONTEXTS.includes(addModuleQueue.getContext())) {
+				skippedModulesCount++;
+			}
+			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);
+					activeModules.delete(ident);
+					if (lastActiveModule === ident) {
+						lastActiveModule = "";
+						for (const m of activeModules) {
+							lastActiveModule = m;
+						}
+						update();
+						return;
+					}
 				}
-				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);
+			}
+			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();
+		};
+
+		const cache = compiler.getCache(PLUGIN_NAME).getItemCache("counts", null);
+
+		/** @type {Promise | undefined} */
+		let cacheGetPromise;
+
+		compiler.hooks.beforeCompile.tap(PLUGIN_NAME, () => {
+			if (!cacheGetPromise) {
+				cacheGetPromise = cache.getPromise().then(
+					(data) => {
+						if (data) {
+							lastModulesCount = lastModulesCount || data.modulesCount;
+							lastDependenciesCount =
+								lastDependenciesCount || data.dependenciesCount;
+						}
+						return data;
+					},
+					(_err) => {
+						// Ignore error
 					}
-					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);
-								};
+				);
+			}
+		});
+
+		compiler.hooks.afterCompile.tapPromise(PLUGIN_NAME, (compilation) => {
+			if (compilation.compiler.isChild()) return Promise.resolve();
+			return /** @type {Promise} */ (cacheGetPromise).then(
+				async (oldData) => {
+					const realModulesCount = modulesCount - skippedModulesCount;
+					const realDependenciesCount =
+						dependenciesCount - skippedDependenciesCount;
+
+					if (
+						!oldData ||
+						oldData.modulesCount !== realModulesCount ||
+						oldData.dependenciesCount !== realDependenciesCount
+					) {
+						await cache.storePromise({
+							modulesCount: realModulesCount,
+							dependenciesCount: realDependenciesCount
+						});
+					}
+				}
+			);
+		});
+
+		compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
+			if (compilation.compiler.isChild()) return;
+			lastModulesCount = modulesCount;
+			lastEntriesCount = entriesCount;
+			lastDependenciesCount = dependenciesCount;
+			modulesCount =
+				skippedModulesCount =
+				dependenciesCount =
+				skippedDependenciesCount =
+				entriesCount =
+					0;
+			doneModules = doneDependencies = doneEntries = 0;
+
+			compilation.factorizeQueue.hooks.added.tap(PLUGIN_NAME, (item) =>
+				factorizeAdd(compilation.factorizeQueue, item)
+			);
+			compilation.factorizeQueue.hooks.result.tap(PLUGIN_NAME, factorizeDone);
+
+			compilation.addModuleQueue.hooks.added.tap(PLUGIN_NAME, (item) =>
+				moduleAdd(compilation.addModuleQueue, item)
+			);
+			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..098cce3d218 100644
--- a/lib/ProvidePlugin.js
+++ b/lib/ProvidePlugin.js
@@ -2,85 +2,122 @@
 	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) {
 		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 = /** @type {DependencyLocation} */ (expr.loc);
+							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 = /** @type {DependencyLocation} */ (expr.callee.loc);
+							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..d144dcdd53c 100644
--- a/lib/RawModule.js
+++ b/lib/RawModule.js
@@ -2,38 +2,110 @@
 	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);
 		this.sourceStr = source;
 		this.identifierStr = identifier || this.sourceStr;
 		this.readableIdentifierStr = readableIdentifier || this.identifierStr;
-		this.built = false;
+		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 +113,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..cc7d2935444 100644
--- a/lib/RecordIdsPlugin.js
+++ b/lib/RecordIdsPlugin.js
@@ -2,155 +2,161 @@
 	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) {
 		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 +164,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..c0ffec34031
--- /dev/null
+++ b/lib/RuntimeGlobals.js
@@ -0,0 +1,462 @@
+/*
+	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";
+
+/**
+ * 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";
+
+/**
+ * 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";
+
+/**
+ * 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";
+
+/**
+ * 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 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 chunk prefetch function
+ */
+module.exports.prefetchChunk = "__webpack_require__.E";
+
+/**
+ * an object with handlers to prefetch a chunk
+ */
+module.exports.prefetchChunkHandlers = "__webpack_require__.F";
+
+/**
+ * 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";
+
+/**
+ * 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..2c1c58902e6
--- /dev/null
+++ b/lib/RuntimeModule.js
@@ -0,0 +1,257 @@
+/*
+	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.
+	 * @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..ddd0cf93a91
--- /dev/null
+++ b/lib/RuntimePlugin.js
@@ -0,0 +1,572 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const RuntimeGlobals = require("./RuntimeGlobals");
+const RuntimeRequirementsDependency = require("./dependencies/RuntimeRequirementsDependency");
+const JavascriptModulesPlugin = require("./javascript/JavascriptModulesPlugin");
+const AsyncModuleRuntimeModule = require("./runtime/AsyncModuleRuntimeModule");
+const AutoPublicPathRuntimeModule = require("./runtime/AutoPublicPathRuntimeModule");
+const BaseUriRuntimeModule = require("./runtime/BaseUriRuntimeModule");
+const CompatGetDefaultExportRuntimeModule = require("./runtime/CompatGetDefaultExportRuntimeModule");
+const CompatRuntimeModule = require("./runtime/CompatRuntimeModule");
+const CreateFakeNamespaceObjectRuntimeModule = require("./runtime/CreateFakeNamespaceObjectRuntimeModule");
+const CreateScriptRuntimeModule = require("./runtime/CreateScriptRuntimeModule");
+const CreateScriptUrlRuntimeModule = require("./runtime/CreateScriptUrlRuntimeModule");
+const DefinePropertyGettersRuntimeModule = require("./runtime/DefinePropertyGettersRuntimeModule");
+const EnsureChunkRuntimeModule = require("./runtime/EnsureChunkRuntimeModule");
+const GetChunkFilenameRuntimeModule = require("./runtime/GetChunkFilenameRuntimeModule");
+const GetMainFilenameRuntimeModule = require("./runtime/GetMainFilenameRuntimeModule");
+const GetTrustedTypesPolicyRuntimeModule = require("./runtime/GetTrustedTypesPolicyRuntimeModule");
+const GlobalRuntimeModule = require("./runtime/GlobalRuntimeModule");
+const HasOwnPropertyRuntimeModule = require("./runtime/HasOwnPropertyRuntimeModule");
+const LoadScriptRuntimeModule = require("./runtime/LoadScriptRuntimeModule");
+const {
+	MakeDeferredNamespaceObjectRuntimeModule,
+	MakeOptimizedDeferredNamespaceObjectRuntimeModule
+} = require("./runtime/MakeDeferredNamespaceObjectRuntime");
+const MakeNamespaceObjectRuntimeModule = require("./runtime/MakeNamespaceObjectRuntimeModule");
+const NonceRuntimeModule = require("./runtime/NonceRuntimeModule");
+const OnChunksLoadedRuntimeModule = require("./runtime/OnChunksLoadedRuntimeModule");
+const PublicPathRuntimeModule = require("./runtime/PublicPathRuntimeModule");
+const RelativeUrlRuntimeModule = require("./runtime/RelativeUrlRuntimeModule");
+const RuntimeIdRuntimeModule = require("./runtime/RuntimeIdRuntimeModule");
+const SetAnonymousDefaultNameRuntimeModule = require("./runtime/SetAnonymousDefaultNameRuntimeModule");
+const SystemContextRuntimeModule = require("./runtime/SystemContextRuntimeModule");
+const ToBinaryRuntimeModule = require("./runtime/ToBinaryRuntimeModule");
+const WorkerRuntimeModule = require("./runtime/WorkerRuntimeModule");
+const ShareRuntimeModule = require("./sharing/ShareRuntimeModule");
+const StringXor = require("./util/StringXor");
+const memoize = require("./util/memoize");
+
+/** @typedef {import("../declarations/WebpackOptions").LibraryOptions} LibraryOptions */
+/** @typedef {import("./Chunk")} Chunk */
+/** @typedef {import("./Compiler")} Compiler */
+
+const getJavascriptModulesPlugin = memoize(() =>
+	require("./javascript/JavascriptModulesPlugin")
+);
+const getCssModulesPlugin = memoize(() => require("./css/CssModulesPlugin"));
+
+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.getFullHash,
+	RuntimeGlobals.global,
+	RuntimeGlobals.makeNamespaceObject,
+	RuntimeGlobals.moduleCache,
+	RuntimeGlobals.moduleFactories,
+	RuntimeGlobals.moduleFactoriesAddOnly,
+	RuntimeGlobals.interceptModuleExecution,
+	RuntimeGlobals.publicPath,
+	RuntimeGlobals.baseURI,
+	RuntimeGlobals.relativeUrl,
+	// TODO webpack 6 - rename to nonce, because we use it for CSS too
+	RuntimeGlobals.scriptNonce,
+	RuntimeGlobals.uncaughtErrorHandler,
+	RuntimeGlobals.asyncModule,
+	RuntimeGlobals.wasmInstances,
+	RuntimeGlobals.instantiateWasm,
+	RuntimeGlobals.shareScopeMap,
+	RuntimeGlobals.initializeSharing,
+	RuntimeGlobals.loadScript,
+	RuntimeGlobals.setAnonymousDefaultName,
+	RuntimeGlobals.systemContext,
+	RuntimeGlobals.onChunksLoaded,
+	RuntimeGlobals.makeOptimizedDeferredNamespaceObject,
+	RuntimeGlobals.makeDeferredNamespaceObject
+];
+
+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]
+};
+
+const FULLHASH_REGEXP = /\[(?:full)?hash(?::\d+)?\]/;
+
+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);
+					});
+			}
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.definePropertyGetters)
+				.tap(PLUGIN_NAME, (chunk) => {
+					compilation.addRuntimeModule(
+						chunk,
+						new DefinePropertyGettersRuntimeModule()
+					);
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.makeNamespaceObject)
+				.tap(PLUGIN_NAME, (chunk) => {
+					compilation.addRuntimeModule(
+						chunk,
+						new MakeNamespaceObjectRuntimeModule()
+					);
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.createFakeNamespaceObject)
+				.tap(PLUGIN_NAME, (chunk) => {
+					compilation.addRuntimeModule(
+						chunk,
+						new CreateFakeNamespaceObjectRuntimeModule()
+					);
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.makeOptimizedDeferredNamespaceObject)
+				.tap("RuntimePlugin", (chunk, runtimeRequirement) => {
+					compilation.addRuntimeModule(
+						chunk,
+						new MakeOptimizedDeferredNamespaceObjectRuntimeModule(
+							runtimeRequirement.has(RuntimeGlobals.asyncModule)
+						)
+					);
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.makeDeferredNamespaceObject)
+				.tap("RuntimePlugin", (chunk, runtimeRequirement) => {
+					compilation.addRuntimeModule(
+						chunk,
+						new MakeDeferredNamespaceObjectRuntimeModule(
+							runtimeRequirement.has(RuntimeGlobals.asyncModule)
+						)
+					);
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.hasOwnProperty)
+				.tap(PLUGIN_NAME, (chunk) => {
+					compilation.addRuntimeModule(
+						chunk,
+						new HasOwnPropertyRuntimeModule()
+					);
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.compatGetDefaultExport)
+				.tap(PLUGIN_NAME, (chunk) => {
+					compilation.addRuntimeModule(
+						chunk,
+						new CompatGetDefaultExportRuntimeModule()
+					);
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.setAnonymousDefaultName)
+				.tap(PLUGIN_NAME, (chunk) => {
+					compilation.addRuntimeModule(
+						chunk,
+						new SetAnonymousDefaultNameRuntimeModule()
+					);
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.runtimeId)
+				.tap(PLUGIN_NAME, (chunk) => {
+					compilation.addRuntimeModule(chunk, new RuntimeIdRuntimeModule());
+					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") {
+						const module = new AutoPublicPathRuntimeModule();
+						if (
+							scriptType !== "module" &&
+							!outputOptions.environment.globalThis
+						) {
+							set.add(RuntimeGlobals.global);
+						}
+
+						compilation.addRuntimeModule(chunk, module);
+					} else {
+						const module = new PublicPathRuntimeModule(publicPath);
+
+						if (
+							typeof publicPath !== "string" ||
+							FULLHASH_REGEXP.test(publicPath)
+						) {
+							module.fullHash = true;
+						}
+
+						compilation.addRuntimeModule(chunk, module);
+					}
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.global)
+				.tap(PLUGIN_NAME, (chunk) => {
+					compilation.addRuntimeModule(chunk, new GlobalRuntimeModule());
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.asyncModule)
+				.tap(PLUGIN_NAME, (chunk) => {
+					const experiments = compilation.options.experiments;
+					compilation.addRuntimeModule(
+						chunk,
+						new AsyncModuleRuntimeModule(experiments.deferImport)
+					);
+					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.addRuntimeModule(
+							chunk,
+							new SystemContextRuntimeModule()
+						);
+					}
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.getChunkScriptFilename)
+				.tap(PLUGIN_NAME, (chunk, set, { chunkGraph }) => {
+					if (
+						typeof compilation.outputOptions.chunkFilename === "string" &&
+						FULLHASH_REGEXP.test(compilation.outputOptions.chunkFilename)
+					) {
+						set.add(RuntimeGlobals.getFullHash);
+					}
+					compilation.addRuntimeModule(
+						chunk,
+						new GetChunkFilenameRuntimeModule(
+							"javascript",
+							"javascript",
+							RuntimeGlobals.getChunkScriptFilename,
+							(chunk) => {
+								const javascriptModulesPlugin = getJavascriptModulesPlugin();
+
+								return (
+									javascriptModulesPlugin.chunkHasJs(chunk, chunkGraph) &&
+									javascriptModulesPlugin.getChunkFilenameTemplate(
+										chunk,
+										compilation.outputOptions
+									)
+								);
+							},
+							set.has(RuntimeGlobals.hmrDownloadUpdateHandlers)
+						)
+					);
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.getChunkCssFilename)
+				.tap(PLUGIN_NAME, (chunk, set, { chunkGraph }) => {
+					if (
+						typeof compilation.outputOptions.cssChunkFilename === "string" &&
+						FULLHASH_REGEXP.test(compilation.outputOptions.cssChunkFilename)
+					) {
+						set.add(RuntimeGlobals.getFullHash);
+					}
+					compilation.addRuntimeModule(
+						chunk,
+						new GetChunkFilenameRuntimeModule(
+							"css",
+							"css",
+							RuntimeGlobals.getChunkCssFilename,
+							(chunk) => {
+								const cssModulePlugin = getCssModulesPlugin();
+
+								return (
+									cssModulePlugin.chunkHasCss(chunk, chunkGraph) &&
+									cssModulePlugin.getChunkFilenameTemplate(
+										chunk,
+										compilation.outputOptions
+									)
+								);
+							},
+							set.has(RuntimeGlobals.hmrDownloadUpdateHandlers)
+						)
+					);
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.getChunkUpdateScriptFilename)
+				.tap(PLUGIN_NAME, (chunk, set) => {
+					if (
+						FULLHASH_REGEXP.test(
+							compilation.outputOptions.hotUpdateChunkFilename
+						)
+					) {
+						set.add(RuntimeGlobals.getFullHash);
+					}
+					compilation.addRuntimeModule(
+						chunk,
+						new GetChunkFilenameRuntimeModule(
+							"javascript",
+							"javascript update",
+							RuntimeGlobals.getChunkUpdateScriptFilename,
+							(_chunk) => compilation.outputOptions.hotUpdateChunkFilename,
+							true
+						)
+					);
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.getUpdateManifestFilename)
+				.tap(PLUGIN_NAME, (chunk, set) => {
+					if (
+						FULLHASH_REGEXP.test(
+							compilation.outputOptions.hotUpdateMainFilename
+						)
+					) {
+						set.add(RuntimeGlobals.getFullHash);
+					}
+					compilation.addRuntimeModule(
+						chunk,
+						new GetMainFilenameRuntimeModule(
+							"update manifest",
+							RuntimeGlobals.getUpdateManifestFilename,
+							compilation.outputOptions.hotUpdateMainFilename
+						)
+					);
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.ensureChunk)
+				.tap(PLUGIN_NAME, (chunk, set) => {
+					const hasAsyncChunks = chunk.hasAsyncChunks();
+					if (hasAsyncChunks) {
+						set.add(RuntimeGlobals.ensureChunkHandlers);
+					}
+					compilation.addRuntimeModule(
+						chunk,
+						new EnsureChunkRuntimeModule(set)
+					);
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.ensureChunkIncludeEntries)
+				.tap(PLUGIN_NAME, (chunk, set) => {
+					set.add(RuntimeGlobals.ensureChunkHandlers);
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.shareScopeMap)
+				.tap(PLUGIN_NAME, (chunk, set) => {
+					compilation.addRuntimeModule(chunk, new ShareRuntimeModule());
+					return true;
+				});
+			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.addRuntimeModule(
+						chunk,
+						new LoadScriptRuntimeModule(withCreateScriptUrl, withFetchPriority)
+					);
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.createScript)
+				.tap(PLUGIN_NAME, (chunk, set) => {
+					if (compilation.outputOptions.trustedTypes) {
+						set.add(RuntimeGlobals.getTrustedTypesPolicy);
+					}
+					compilation.addRuntimeModule(chunk, new CreateScriptRuntimeModule());
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.createScriptUrl)
+				.tap(PLUGIN_NAME, (chunk, set) => {
+					if (compilation.outputOptions.trustedTypes) {
+						set.add(RuntimeGlobals.getTrustedTypesPolicy);
+					}
+					compilation.addRuntimeModule(
+						chunk,
+						new CreateScriptUrlRuntimeModule()
+					);
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.getTrustedTypesPolicy)
+				.tap(PLUGIN_NAME, (chunk, set) => {
+					compilation.addRuntimeModule(
+						chunk,
+						new GetTrustedTypesPolicyRuntimeModule(set)
+					);
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.relativeUrl)
+				.tap(PLUGIN_NAME, (chunk, _set) => {
+					compilation.addRuntimeModule(chunk, new RelativeUrlRuntimeModule());
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.worker)
+				.tap(PLUGIN_NAME, (chunk, _set) => {
+					compilation.addRuntimeModule(chunk, new WorkerRuntimeModule());
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.onChunksLoaded)
+				.tap(PLUGIN_NAME, (chunk, _set) => {
+					compilation.addRuntimeModule(
+						chunk,
+						new OnChunksLoadedRuntimeModule()
+					);
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.baseURI)
+				.tap(PLUGIN_NAME, (chunk) => {
+					if (isChunkLoadingDisabledForChunk(chunk)) {
+						compilation.addRuntimeModule(chunk, new BaseUriRuntimeModule());
+						return true;
+					}
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.scriptNonce)
+				.tap(PLUGIN_NAME, (chunk) => {
+					compilation.addRuntimeModule(chunk, new NonceRuntimeModule());
+					return true;
+				});
+			compilation.hooks.runtimeRequirementInTree
+				.for(RuntimeGlobals.toBinary)
+				.tap(PLUGIN_NAME, (chunk) => {
+					compilation.addRuntimeModule(chunk, new ToBinaryRuntimeModule());
+					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.addRuntimeModule(chunk, new CompatRuntimeModule());
+					}
+				}
+			);
+			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..a4adc2ef049 100644
--- a/lib/RuntimeTemplate.js
+++ b/lib/RuntimeTemplate.js
@@ -2,142 +2,852 @@
 	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");
+const {
+	getOutgoingAsyncModules
+} = require("./async-modules/AsyncModuleHelpers");
+const { ImportPhaseUtils } = require("./dependencies/ImportPhase");
+const { InlinedUsedName } = require("./optimize/InlineExports");
+const {
+	getMakeDeferredNamespaceModeFromExportsType,
+	getOptimizedDeferredModule
+} = require("./runtime/MakeDeferredNamespaceObjectRuntime");
+const { equals } = require("./util/ArrayHelpers");
+const compileBooleanMatcher = require("./util/compileBooleanMatcher");
+const memoize = require("./util/memoize");
+const { propertyAccess } = require("./util/property");
+const { forEachRuntime, subtractRuntime } = require("./util/runtime");
+
+const getHarmonyImportDependency = memoize(() =>
+	require("./dependencies/HarmonyImportDependency")
+);
+const getImportDependency = memoize(() =>
+	require("./dependencies/ImportDependency")
+);
+
+/** @typedef {import("./config/defaults").OutputNormalizedWithDefaults} OutputOptions */
+/** @typedef {import("./AsyncDependenciesBlock")} AsyncDependenciesBlock */
+/** @typedef {import("./Chunk")} Chunk */
+/** @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 */
 
-module.exports = class RuntimeTemplate {
-	constructor(outputOptions, requestShortener) {
-		this.outputOptions = outputOptions || {};
+/**
+ * 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("")}`;
+
+/**
+ * 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})`;
+}
+
+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) {
+		this.compilation = compilation;
+		this.outputOptions = /** @type {OutputOptions} */ (outputOptions || {});
 		this.requestShortener = requestShortener;
+		this.globalObject =
+			/** @type {string} */
+			(getGlobalObject(outputOptions.globalObject));
+		this.contentHashReplacement = "X".repeat(outputOptions.hashDigestLength);
+	}
+
+	isIIFE() {
+		return this.outputOptions.iife;
+	}
+
+	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
+		);
+	}
+
+	/**
+	 * 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). An SSR host reads it from `globalThis`; it is keyed by the
+	 * style/chunk identifier and namespaced by `output.uniqueName`.
+	 * @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__"
+		);
+		return `(${this.assignOr(`globalThis[${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;
+	}
+
+	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;
+	}
+
+	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}"`;
 	}
 
 	/**
-	 * Add a comment
+	 * 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
+	 * @returns {string} the promise expression
+	 */
+	moduleNamespacePromise({
+		chunkGraph,
+		block,
+		module,
+		request,
+		message,
+		strict,
+		weak,
+		dependency,
+		runtimeRequirements
+	}) {
 		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
 		});
 
-		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 +855,311 @@ 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 getHarmonyImportDependency() ||
+				dependency instanceof getImportDependency()) &&
+			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}`;
 	}
 
-	importStatement({ update, module, request, importVar, originModule }) {
+	/**
+	 * 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);
+	}
+
+	/**
+	 * 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 getHarmonyImportDependency() ||
+				dependency instanceof getImportDependency()) &&
+			ImportPhaseUtils.isDefer(dependency.phase) &&
+			!(/** @type {BuildMeta} */ (module.buildMeta).async);
+
+		if (isModuleDeferred) {
+			/** @type {Set} */
+			const outgoingAsyncModules = getOutgoingAsyncModules(moduleGraph, module);
 
-		if (!exportsType && !originModule.buildMeta.strictHarmonyModule) {
-			content += `/* harmony import */ ${optDeclaration}${importVar}_default = /*#__PURE__*/__webpack_require__.n(${importVar});\n`;
+			importContent = `/* deferred harmony import */ ${optDeclaration}${importVar} = ${getOptimizedDeferredModule(
+				moduleId,
+				exportsType,
+				Array.from(outgoingAsyncModules, (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
+	 * @returns {string} expression
+	 */
 	exportFromImport({
+		moduleGraph,
+		chunkGraph,
 		module,
 		request,
 		exportName,
@@ -224,97 +1167,318 @@ module.exports = class RuntimeTemplate {
 		asiSafe,
 		isCall,
 		callContext,
-		importVar
+		defaultInterop,
+		importVar,
+		initFragments,
+		runtime,
+		runtimeRequirements,
+		dependency
 	}) {
 		if (!module) {
 			return this.missingModule({
 				request
 			});
 		}
-		const exportsType = module.buildMeta && module.buildMeta.exportsType;
-
-		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`;
+		if (!Array.isArray(exportName)) {
+			exportName = exportName ? [exportName] : [];
+		}
+		const exportsType = module.getExportsType(
+			moduleGraph,
+			/** @type {BuildMeta} */
+			(originModule.buildMeta).strictHarmonyModule
+		);
+
+		const isModuleDeferred =
+			(dependency instanceof getHarmonyImportDependency() ||
+				dependency instanceof getImportDependency()) &&
+			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(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 (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})`;
-				}
+			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(used)}`;
+			if (isCall && callContext === false) {
+				return asiSafe
+					? `(0,${access})`
+					: asiSafe === false
+						? `;(0,${access})`
+						: `/*#__PURE__*/Object(${access})`;
 			}
 			return access;
-		} else {
-			return importVar;
 		}
+		if (isModuleDeferred) {
+			initFragments.push(
+				new InitFragment(
+					`${this.renderLet()} ${importVar}_deferred_namespace_cache;\n`,
+					InitFragment.STAGE_CONSTANTS,
+					-1,
+					`${importVar}_deferred_namespace_cache`
+				)
+			);
+
+			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}))`;
+		}
+		// 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;
 	}
 
-	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
+	 * @returns {string} expression
+	 */
+	blockPromise({ block, message, chunkGraph, runtimeRequirements }) {
+		if (!block) {
 			const comment = this.comment({
 				message
 			});
 			return `Promise.resolve(${comment.trim()})`;
 		}
-		const chunks = block.chunkGroup.chunks.filter(
-			chunk => !chunk.hasRuntime() && chunk.id !== null
+		const chunkGroup = chunkGraph.getBlockChunkGroup(block);
+		if (!chunkGroup || chunkGroup.chunks.length === 0) {
+			const comment = this.comment({
+				message
+			});
+			return `Promise.resolve(${comment.trim()})`;
+		}
+		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
 		});
 		if (chunks.length === 1) {
 			const chunkId = JSON.stringify(chunks[0].id);
-			return `__webpack_require__.e(${comment}${chunkId})`;
+			runtimeRequirements.add(RuntimeGlobals.ensureChunk);
+
+			const fetchPriority = chunkGroup.options.fetchPriority;
+
+			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)})`;
+			runtimeRequirements.add(RuntimeGlobals.ensureChunk);
+
+			const fetchPriority = chunkGroup.options.fetchPriority;
+
+			if (fetchPriority) {
+				runtimeRequirements.add(RuntimeGlobals.hasFetchPriority);
+			}
+
+			/**
+			 * Returns require chunk id code.
+			 * @param {Chunk} chunk chunk
+			 * @returns {string} require chunk id code
+			 */
+			const requireChunkId = (chunk) =>
+				`${RuntimeGlobals.ensureChunk}(${JSON.stringify(chunk.id)}${
+					fetchPriority ? `, ${JSON.stringify(fetchPriority)}` : ""
+				})`;
 			return `Promise.all(${comment.trim()}[${chunks
 				.map(requireChunkId)
 				.join(", ")}])`;
-		} else {
-			return `Promise.resolve(${comment.trim()})`;
 		}
+		return `Promise.resolve(${comment.trim()})`;
+	}
+
+	/**
+	 * 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
+	 * @returns {string} expression
+	 */
+	asyncModuleFactory({ block, chunkGraph, runtimeRequirements, request }) {
+		const dep = block.dependencies[0];
+		const module = chunkGraph.moduleGraph.getModule(dep);
+		const ensureChunk = this.blockPromise({
+			block,
+			message: "",
+			chunkGraph,
+			runtimeRequirements
+		});
+		const factory = this.returningFunction(
+			this.moduleRaw({
+				module,
+				chunkGraph,
+				request,
+				runtimeRequirements
+			})
+		);
+		return this.returningFunction(
+			ensureChunk.startsWith("Promise.resolve(")
+				? `${factory}`
+				: `${ensureChunk}.then(${this.returningFunction(factory)})`
+		);
 	}
 
-	onError() {
-		return "__webpack_require__.oe";
+	/**
+	 * 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);
 	}
 
-	defineEsModuleFlagStatement({ exportsArgument }) {
-		return `__webpack_require__.r(${exportsArgument});\n`;
+	/**
+	 * 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..4cdf0c23b0e
--- /dev/null
+++ b/lib/SelfModuleFactory.js
@@ -0,0 +1,35 @@
+/*
+	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) {
+		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..23cbc0d7252 100644
--- a/lib/SourceMapDevToolPlugin.js
+++ b/lib/SourceMapDevToolPlugin.js
@@ -2,81 +2,302 @@
 	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 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 */
 
-const validateOptions = require("schema-utils");
-const schema = require("../schemas/plugins/SourceMapDevToolPlugin.json");
+/**
+ * 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 basename = name => {
-	if (!name.includes("/")) return name;
-	return name.substr(name.lastIndexOf("/") + 1);
+const METACHARACTERS_REGEXP = /[-[\]\\/{}()*+?.^$|]/g;
+const CONTENT_HASH_DETECT_REGEXP = /\[contenthash(?::\w+)?\]/;
+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\/\/(.*)$/;
+
+/**
+ * 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;
+	});
 
-		validateOptions(schema, options || {}, "SourceMap DevTool Plugin");
+	return {
+		file,
+		source: /** @type {string} */ (source),
+		assetInfo,
+		sourceMap,
+		mapSource,
+		modules,
+		cacheItem
+	};
+};
+
+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]";
+		/** @type {DevtoolFallbackModuleFilenameTemplate} */
 		this.fallbackModuleFilenameTemplate =
 			options.fallbackModuleFilenameTemplate ||
 			"webpack://[namespace]/[resourcePath]?[hash]";
+		/** @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 +305,620 @@ 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 =
+										sourceMapFilename &&
+										CONTENT_HASH_DETECT_REGEXP.test(sourceMapFilename);
+
+									resetRegexpState(CONTENT_HASH_DETECT_REGEXP);
+
+									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..322ddda1c9b 100644
--- a/lib/Stats.js
+++ b/lib/Stats.js
@@ -2,1413 +2,92 @@
 	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) {
 		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..918ffd5952c 100644
--- a/lib/Template.js
+++ b/lib/Template.js
@@ -2,62 +2,119 @@
 	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;
 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
- * */
+ * 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 */
+
+/**
+ * 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
+ */
 
 /**
- * @typedef {function(Module, number): boolean} ModuleFilterPredicate
+ * Defines the module filter predicate type used by this module.
+ * @typedef {(module: Module) => boolean} ModuleFilterPredicate
  */
 
 /**
- * @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
+ * 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 +124,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 +157,108 @@ 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(/\n([^\n])/g, "\n\t$1");
 	}
 
 	/**
-	 *
-	 * @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");
+		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 +269,185 @@ 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 + colon + comma
+			objectOverhead += `${module.id}`.length + 2;
+		}
+		// number of commas, or when starting non-zero the length of Array(minId).concat()
+		const arrayOverhead = minId === 0 ? maxId : 16 + `${minId}`.length + 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;
-		}
-		if (
-			modules.length === 0 &&
-			(!removedModules || removedModules.length === 0)
-		) {
-			source.add("[]");
-			return source;
+	static renderChunkModules(renderContext, modules, renderModule, prefix = "") {
+		const { chunkGraph } = renderContext;
+		const source = new ConcatSource();
+		if (modules.length === 0) {
+			return null;
 		}
-		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..8120ebfb354 100644
--- a/lib/TemplatedPathPlugin.js
+++ b/lib/TemplatedPathPlugin.js
@@ -2,172 +2,569 @@
 	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 } = require("path");
+const util = require("util");
+const Chunk = require("./Chunk");
+const Module = require("./Module");
+const { 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").AssetInfo} AssetInfo */
+/** @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+)?$/.exec(content);
+			if (cm) kinds.add(cm[1]);
+		}
+	}
+	if (presentKindsCache.size >= PRESENT_KINDS_CACHE_MAX) {
+		presentKindsCache.clear();
+	}
+	presentKindsCache.set(path, kinds);
+	return kinds;
+};
+
+/** @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
+ */
+
+/**
+ * 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
+ * @returns {Replacer} hash replacer function
+ */
+const hashLength = (replacer, handler, assetInfo, hashName) => {
+	/** @type {Replacer} */
+	const fn = (match, arg, input) => {
+		/** @type {string} */
+		let result;
+		const length = arg && Number.parseInt(arg, 10);
+
+		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 (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) => 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;
+
+	/** @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
 	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"))
 	) {
-		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("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("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"
 		);
+
+		if (presentKinds.has("fullhash")) {
+			replacements.set("fullhash", hashReplacer);
+		}
+
+		// Legacy
+		if (presentKinds.has("hash")) {
+			replacements.set(
+				"hash",
+				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"
+				)
+			);
+		}
+		if (presentKinds.has("contenthash")) {
+			replacements.set(
+				"contenthash",
+				hashLength(
+					replacer(
+						data.contentHash ||
+							(contentHashType &&
+								chunk.contentHash &&
+								chunk.contentHash[contentHashType])
+					),
+					data.contentHashWithLength ||
+						("contentHashWithLength" in chunk && chunk.contentHashWithLength
+							? chunk.contentHashWithLength[
+									/** @type {string} */ (contentHashType)
+								]
+							: undefined),
+					assetInfo,
+					"contenthash"
+				)
 			);
+		}
+	}
+
+	// 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");
+		const needHash = presentKinds.has("hash");
 
-			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"
+			);
+			if (presentKinds.has("modulehash")) {
+				replacements.set("modulehash", moduleHashReplacer);
+			}
+		}
+		if (wantContentHash) {
+			contentHashReplacer = hashLength(
+				replacer(/** @type {string} */ (data.contentHash)),
+				undefined,
+				assetInfo,
+				"contenthash"
 			);
+			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+))?$/.exec(content);
+			if (!contentMatch) return match;
+			const [, kind, arg] = contentMatch;
+			const replacer = replacements.get(kind);
+			if (replacer !== undefined) {
+				return replacer(match, arg, /** @type {string} */ (path));
+			}
+		} 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;
+				}
+				return interpolate(path, data, assetInfo);
+			});
 		});
 	}
 }
 
 module.exports = TemplatedPathPlugin;
+module.exports.interpolate = interpolate;
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..4d1f6527978 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 = /** @type {DependencyLocation} */ (firstNode.loc);
+							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..7be81464a9b 100644
--- a/lib/WarnCaseSensitiveModulesPlugin.js
+++ b/lib/WarnCaseSensitiveModulesPlugin.js
@@ -2,35 +2,130 @@
 	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";
+		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..662b470fed3
--- /dev/null
+++ b/lib/WarnDeprecatedOptionPlugin.js
@@ -0,0 +1,60 @@
+/*
+	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) {
+		this.option = option;
+		this.value = value;
+		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";
+		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..3359dcf49cf 100644
--- a/lib/WarnNoModeSetPlugin.js
+++ b/lib/WarnNoModeSetPlugin.js
@@ -2,13 +2,38 @@
 	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";
+		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..42177c73d24 100644
--- a/lib/WatchIgnorePlugin.js
+++ b/lib/WatchIgnorePlugin.js
@@ -2,77 +2,80 @@
 	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) {
 		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 +84,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..e1d325ef5a6 100644
--- a/lib/Watching.js
+++ b/lib/Watching.js
@@ -2,175 +2,521 @@
 	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;
 		this.invalid = false;
+		/** @type {Callback} */
 		this.handler = handler;
+		/** @type {ErrorCallback[]} */
 		this.callbacks = [];
+		/** @type {ErrorCallback[] | undefined} */
+		this._closeCallbacks = undefined;
 		this.closed = false;
+		this.suspended = false;
+		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;
+		}
 		this.compiler = compiler;
-		this.running = true;
-		this.compiler.readRecords(err => {
-			if (err) return this._done(err);
-
-			this._go();
+		this.running = false;
+		this._initial = true;
+		this._invalidReported = true;
+		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();
 		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 +528,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..e5099417375
--- /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 = /** @type {DependencyLocation} */ (expr.loc);
+							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..1743f2019dd 100644
--- a/lib/WebpackOptionsApply.js
+++ b/lib/WebpackOptionsApply.js
@@ -2,373 +2,827 @@
 	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 WorkerPlugin = require("./dependencies/WorkerPlugin");
+
+const JavascriptModulesPlugin = require("./javascript/JavascriptModulesPlugin");
+const JavascriptParser = require("./javascript/JavascriptParser");
+
+const JsonModulesPlugin = require("./json/JsonModulesPlugin");
+
+const ChunkPrefetchPreloadPlugin = require("./prefetch/ChunkPrefetchPreloadPlugin");
+
+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.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}`);
+					}
 				}
-				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);
+
+				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);
+
+		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);
+		}
+
+		if (
+			!(
+				/** @type {typeof JavascriptParser & { __importPhasesExtended?: true }} */
+				(JavascriptParser).__importPhasesExtended
+			) &&
+			(options.experiments.deferImport || options.experiments.sourceImport)
+		) {
+			const importPhases = require("acorn-import-phases");
+
+			JavascriptParser.extend(importPhases({ source: true, defer: true }));
+			/** @type {typeof JavascriptParser & { __importPhasesExtended?: true }} */
+			(JavascriptParser).__importPhasesExtended = true;
+		}
+
 		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 WorkerPlugin(
+			options.output.workerChunkLoading,
+			options.output.workerWasmLoading,
+			options.output.module,
+			options.output.workerPublicPath
 		).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"
+			).apply(compiler);
+		}
+		if (options.optimization.innerGraph) {
+			const InnerGraphPlugin = require("./optimize/InnerGraphPlugin");
+
+			new InnerGraphPlugin().apply(compiler);
+		}
+
+		if (options.mode === "production") {
+			const CircularModulesPlugin = require("./CircularModulesPlugin");
+
+			new CircularModulesPlugin().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);
+
 			new DefinePlugin({
-				"process.env.NODE_ENV": JSON.stringify(options.optimization.nodeEnv)
+				"process.env.NODE_ENV": defValue,
+				"import.meta.env.NODE_ENV": defValue
 			}).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);
 		}
 
@@ -380,11 +834,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 +963,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..c1faff35414
--- /dev/null
+++ b/lib/asset/AssetBytesGenerator.js
@@ -0,0 +1,189 @@
+/*
+	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();
+
+		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..4501bc18b85
--- /dev/null
+++ b/lib/asset/AssetGenerator.js
@@ -0,0 +1,863 @@
+/*
+	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,
+	CSS_TYPE,
+	HTML_TYPE,
+	JAVASCRIPT_AND_ASSET_URL_TYPES,
+	JAVASCRIPT_TYPE,
+	JAVASCRIPT_TYPES,
+	NO_TYPES
+} = require("../ModuleSourceTypeConstants");
+const { ASSET_MODULE_TYPE } = require("../ModuleTypeConstants");
+const RuntimeGlobals = require("../RuntimeGlobals");
+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").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();
+
+			if (typeof content !== "string") {
+				encodedContent = content.toString("utf8");
+			}
+
+			encodedContent = encodeURIComponent(
+				/** @type {string} */
+				(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), "ascii");
+	} catch (_) {
+		return Buffer.from(content, "ascii");
+	}
+};
+
+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
+	 */
+	constructor(
+		moduleGraph,
+		dataUrlOptions,
+		filename,
+		publicPath,
+		outputPath,
+		emit
+	) {
+		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 {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 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
+	 * @returns {{ filename: string, originalFilename: string, assetInfo: AssetInfo }} info
+	 */
+	static getFilenameWithInfo(
+		module,
+		generatorOptions,
+		{ runtime, runtimeTemplate, chunkGraph },
+		contentHash
+	) {
+		const assetModuleFilename =
+			generatorOptions.filename ||
+			runtimeTemplate.outputOptions.assetModuleFilename;
+
+		const sourceFilename = AssetGenerator.getSourceFileName(
+			module,
+			runtimeTemplate
+		);
+		let { path: filename, info: assetInfo } =
+			runtimeTemplate.compilation.getAssetPathWithInfo(assetModuleFilename, {
+				module,
+				runtime,
+				filename: sourceFilename,
+				chunkGraph,
+				contentHash
+			});
+
+		const originalFilename = filename;
+
+		if (generatorOptions.outputPath) {
+			const { path: outputPath, info } =
+				runtimeTemplate.compilation.getAssetPathWithInfo(
+					generatorOptions.outputPath,
+					{
+						module,
+						runtime,
+						filename: sourceFilename,
+						chunkGraph,
+						contentHash
+					}
+				);
+			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
+	 * @returns {{ assetPath: string, assetInfo: AssetInfo }} asset path and info
+	 */
+	static getAssetPathWithInfo(
+		module,
+		generatorOptions,
+		{ runtime, runtimeTemplate, type, chunkGraph, runtimeRequirements },
+		filename,
+		assetInfo,
+		contentHash
+	) {
+		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
+				}
+			);
+			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
+				}
+			);
+			assetInfo = mergeAssetInfo(assetInfo, info);
+			assetPath = path + filename;
+		} else if (type === JAVASCRIPT_TYPE) {
+			// 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
+				);
+
+			if (data) {
+				data.set("filename", filename);
+			}
+
+			let { assetPath, assetInfo: newAssetInfo } =
+				AssetGenerator.getAssetPathWithInfo(
+					module,
+					{ publicPath: this.publicPath },
+					generateContext,
+					originalFilename,
+					assetInfo,
+					contentHash
+				);
+
+			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 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 (
+			(module.buildInfo &&
+				/** @type {AssetModuleBuildInfo} */ (module.buildInfo).dataUrl) ||
+			this.emit === false
+		) {
+			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;
+		}
+
+		if (sourceTypes.size > 0) {
+			if (
+				sourceTypes.has(JAVASCRIPT_TYPE) &&
+				(sourceTypes.has(CSS_TYPE) || sourceTypes.has(HTML_TYPE))
+			) {
+				return ASSET_AND_JAVASCRIPT_AND_ASSET_URL_TYPES;
+			} else if (sourceTypes.has(CSS_TYPE) || sourceTypes.has(HTML_TYPE)) {
+				return ASSET_AND_ASSET_URL_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;
+			const { path: filename, info } =
+				runtimeTemplate.compilation.getAssetPathWithInfo(
+					assetModuleFilename,
+					pathData
+				);
+			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..eca17841bab
--- /dev/null
+++ b/lib/asset/AssetModulesPlugin.js
@@ -0,0 +1,378 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Yuta Hiroto @hiroppy
+*/
+
+"use strict";
+
+const {
+	ASSET_MODULE_TYPE,
+	ASSET_MODULE_TYPE_BYTES,
+	ASSET_MODULE_TYPE_INLINE,
+	ASSET_MODULE_TYPE_RESOURCE,
+	ASSET_MODULE_TYPE_SOURCE
+} = require("../ModuleTypeConstants");
+const { compareModulesByFullName } = require("../util/comparators");
+const memoize = require("../util/memoize");
+
+/** @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("../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 = memoize(() => require("./AssetGenerator"));
+const getAssetParser = memoize(() => require("./AssetParser"));
+const getAssetSourceParser = memoize(() => require("./AssetSourceParser"));
+const getAssetBytesParser = memoize(() => require("./AssetBytesParser"));
+const getAssetSourceGenerator = memoize(() =>
+	require("./AssetSourceGenerator")
+);
+const getAssetBytesGenerator = memoize(() => require("./AssetBytesGenerator"));
+const getAssetModule = memoize(() => require("./AssetModule"));
+
+const type = ASSET_MODULE_TYPE;
+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) {
+		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 }) => {
+				const AssetModule = getAssetModule();
+				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) =>
+								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();
+
+						return new AssetParser(dataUrlCondition);
+					});
+				normalModuleFactory.hooks.createParser
+					.for(ASSET_MODULE_TYPE_INLINE)
+					.tap(PLUGIN_NAME, (_parserOptions) => {
+						const AssetParser = getAssetParser();
+
+						return new AssetParser(true);
+					});
+				normalModuleFactory.hooks.createParser
+					.for(ASSET_MODULE_TYPE_RESOURCE)
+					.tap(PLUGIN_NAME, (_parserOptions) => {
+						const AssetParser = getAssetParser();
+
+						return new AssetParser(false);
+					});
+				normalModuleFactory.hooks.createParser
+					.for(ASSET_MODULE_TYPE_SOURCE)
+					.tap(PLUGIN_NAME, (_parserOptions) => {
+						const AssetSourceParser = getAssetSourceParser();
+
+						return new AssetSourceParser();
+					});
+				normalModuleFactory.hooks.createParser
+					.for(ASSET_MODULE_TYPE_BYTES)
+					.tap(PLUGIN_NAME, (_parserOptions) => {
+						const AssetBytesParser = getAssetBytesParser();
+
+						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();
+
+							return new AssetGenerator(
+								compilation.moduleGraph,
+								dataUrl,
+								filename,
+								publicPath,
+								outputPath,
+								generatorOptions.emit !== false
+							);
+						});
+				}
+				normalModuleFactory.hooks.createGenerator
+					.for(ASSET_MODULE_TYPE_SOURCE)
+					.tap(PLUGIN_NAME, () => {
+						const AssetSourceGenerator = getAssetSourceGenerator();
+
+						return new AssetSourceGenerator(compilation.moduleGraph);
+					});
+
+				normalModuleFactory.hooks.createGenerator
+					.for(ASSET_MODULE_TYPE_BYTES)
+					.tap(PLUGIN_NAME, () => {
+						const AssetBytesGenerator = getAssetBytesGenerator();
+
+						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();
+									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
+										);
+									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;
+				});
+
+				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")
+							}
+						);
+					}
+				);
+			}
+		);
+	}
+}
+
+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..b5d585c84d8
--- /dev/null
+++ b/lib/asset/AssetSourceGenerator.js
@@ -0,0 +1,188 @@
+/*
+	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();
+
+		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/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..ee96bf8e8dc
--- /dev/null
+++ b/lib/async-modules/AwaitDependenciesInitFragment.js
@@ -0,0 +1,94 @@
+/*
+	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
+	 */
+	constructor(dependencies) {
+		super(
+			undefined,
+			InitFragment.STAGE_ASYNC_DEPENDENCIES,
+			0,
+			"await-dependencies"
+		);
+		/** @type {Dependencies} */
+		this.dependencies = dependencies;
+	}
+
+	/**
+	 * 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);
+	}
+
+	/**
+	 * 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(", ");
+
+		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 ? (await __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 ? (await __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/buildChunkGraph.js b/lib/buildChunkGraph.js
new file mode 100644
index 00000000000..fc4774c6a03
--- /dev/null
+++ b/lib/buildChunkGraph.js
@@ -0,0 +1,1484 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const ModuleGraphConnection = require("./ModuleGraphConnection");
+const AsyncDependencyToInitialChunkError = require("./errors/AsyncDependencyToInitialChunkError");
+const { getEntryRuntime, mergeRuntime } = require("./util/runtime");
+
+/** @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 AsyncDependencyToInitialChunkError(
+								/** @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();
+				}
+				for (let i = skipConnectionBuffer.length - 1; i >= 0; i--) {
+					skippedModuleConnections.add(skipConnectionBuffer[i]);
+				}
+				skipConnectionBuffer.length = 0;
+			}
+			if (skipBuffer.length > 0) {
+				let { skippedItems } = chunkGroupInfo;
+				if (skippedItems === undefined) {
+					chunkGroupInfo.skippedItems = skippedItems = new Set();
+				}
+				for (let i = skipBuffer.length - 1; i >= 0; i--) {
+					skippedItems.add(skipBuffer[i]);
+				}
+				skipBuffer.length = 0;
+			}
+			if (queueBuffer.length > 0) {
+				for (let i = queueBuffer.length - 1; i >= 0; i--) {
+					queue.push(queueBuffer[i]);
+				}
+				queueBuffer.length = 0;
+			}
+		}
+
+		// 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;
+
+			for (const availableModules of availableModulesToBeMerged) {
+				if (minAvailableModules === undefined) {
+					minAvailableModules = availableModules;
+				} else {
+					minAvailableModules &= availableModules;
+				}
+			}
+
+			const changed = minAvailableModules !== cachedMinAvailableModules;
+
+			availableModulesToBeMerged.length = 0;
+			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..ef83c25f596
--- /dev/null
+++ b/lib/bun/BunTargetPlugin.js
@@ -0,0 +1,48 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author sheo13666q @sheo13666q
+*/
+
+"use strict";
+
+const ExternalsPlugin = require("../ExternalsPlugin");
+const NodeTargetPlugin = require("../node/NodeTargetPlugin");
+
+/** @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.
+// cspell:word pnpapi
+const coreModules = new Set(
+	NodeTargetPlugin.builtins.filter(
+		(builtin) => typeof builtin === "string" && builtin !== "pnpapi"
+	)
+);
+
+// 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..12b110e2f55
--- /dev/null
+++ b/lib/cache/AddBuildDependenciesPlugin.js
@@ -0,0 +1,33 @@
+/*
+	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) {
+		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..344032467ba
--- /dev/null
+++ b/lib/cache/AddManagedPathsPlugin.js
@@ -0,0 +1,41 @@
+/*
+	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) {
+		this.managedPaths = new Set(managedPaths);
+		this.immutablePaths = new Set(immutablePaths);
+		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..8c515fa33c8
--- /dev/null
+++ b/lib/cache/IdleFileCachePlugin.js
@@ -0,0 +1,243 @@
+/*
+	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
+	) {
+		this.strategy = strategy;
+		this.idleTimeout = idleTimeout;
+		this.idleTimeoutForInitialStore = idleTimeoutForInitialStore;
+		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..df652e1b371
--- /dev/null
+++ b/lib/cache/MemoryCachePlugin.js
@@ -0,0 +1,57 @@
+/*
+	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);
+				if (cacheEntry === null) {
+					return null;
+				} else if (cacheEntry !== undefined) {
+					return cacheEntry.etag === etag ? cacheEntry.data : null;
+				}
+				gotHandlers.push((result, callback) => {
+					if (result === undefined) {
+						cache.set(identifier, null);
+					} else {
+						cache.set(identifier, { etag, data: result });
+					}
+					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..33c9661fe82
--- /dev/null
+++ b/lib/cache/MemoryWithGcCachePlugin.js
@@ -0,0 +1,144 @@
+/*
+	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 }) {
+		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);
+				if (cacheEntry === null) {
+					return null;
+				} else if (cacheEntry !== undefined) {
+					return cacheEntry.etag === etag ? cacheEntry.data : null;
+				}
+				const oldCacheEntry = oldCache.get(identifier);
+				if (oldCacheEntry !== undefined) {
+					const cacheEntry = oldCacheEntry.entry;
+					if (cacheEntry === null) {
+						oldCache.delete(identifier);
+						cache.set(identifier, cacheEntry);
+						return null;
+					}
+					if (cacheEntry.etag !== etag) return null;
+					oldCache.delete(identifier);
+					cache.set(identifier, cacheEntry);
+					return cacheEntry.data;
+				}
+				gotHandlers.push((result, callback) => {
+					if (result === undefined) {
+						cache.set(identifier, null);
+					} else {
+						cache.set(identifier, { etag, data: result });
+					}
+					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..2edaa96eb01
--- /dev/null
+++ b/lib/cache/PackFileCacheStrategy.js
@@ -0,0 +1,1629 @@
+/*
+	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 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 */
+
+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;
+			}
+		}
+	}
+
+	/**
+	 * 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;
+			}
+		}
+		if (
+			Date.now() - /** @type {PackItemInfo} */ (oldest).lastAccess >
+			this.maxAge
+		) {
+			const loc = /** @type {PackItemInfo} */ (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._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) {
+		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"=} 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 {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;
+		/** @type {false | "gzip" | "brotli" | undefined} */
+		this.compression = compression;
+		/** @type {string} */
+		this._extension =
+			compression === "brotli"
+				? ".pack.br"
+				: compression === "gzip"
+					? ".pack.gz"
+					: ".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)
+					);
+					return this.fileSerializer
+						.serialize(content, {
+							filename: `${this.cacheLocation}/index${this._extension}`,
+							extension: `${this._extension}`,
+							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)
+							);
+						})
+						.catch((err) => {
+							this.logger.timeEnd("store pack");
+							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);
+			}));
+	}
+
+	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..6a0327b7c09
--- /dev/null
+++ b/lib/cache/ResolverCachePlugin.js
@@ -0,0 +1,459 @@
+/*
+	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) {
+		this.result = result;
+		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} */
+		let fileSystemInfo;
+		/** @type {SnapshotOptions | undefined} */
+		let snapshotOptions;
+		let realResolves = 0;
+		let cachedResolves = 0;
+		let cacheInvalidResolves = 0;
+		let concurrentResolves = 0;
+		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;
+					fileSystemInfo.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;
+									fileSystemInfo.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..b334bcb40a4
--- /dev/null
+++ b/lib/cache/mergeEtags.js
@@ -0,0 +1,73 @@
+/*
+	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) {
+		this.a = a;
+		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..57387946518
--- /dev/null
+++ b/lib/cli.js
@@ -0,0 +1,975 @@
+/*
+	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;
+
+	const isDisabled = "NO_COLOR" in env || argv.includes("--no-color");
+	const isForced = "FORCE_COLOR" in env || argv.includes("--color");
+	const isWindows = platform === "win32";
+	const isDumbTerminal = env.TERM === "dumb";
+
+	const isCompatibleTerminal = tty.isatty(1) && env.TERM && !isDumbTerminal;
+
+	const isCI =
+		"CI" in env &&
+		("GITHUB_ACTIONS" in env || "GITLAB_CI" in env || "CIRCLECI" in env);
+
+	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..95110fdf3a4
--- /dev/null
+++ b/lib/config/browserslistTargetHandler.js
@@ -0,0 +1,489 @@
+/*
+	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").EcmaTargetProperties} EcmaTargetProperties */
+/** @typedef {import("./target").PlatformTargetProperties} PlatformTargetProperties */
+
+// [[C:]/path/to/config][:env]
+const inputRx = /^(?:((?:[A-Z]:)?[/\\].*?))?(?::(.+?))?$/i;
+
+/**
+ * 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) => {
+	// 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} 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) =>
+		browsers.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];
+		});
+	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]
+		}),
+		/* eslint-enable camelcase */
+		browser: browserProperty,
+		electron: false,
+		node: nodeProperty,
+		nwjs: false,
+		web: browserProperty,
+		webworker: false,
+
+		document: browserProperty,
+		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..b379baf41b3
--- /dev/null
+++ b/lib/config/defaults.js
@@ -0,0 +1,2448 @@
+/*
+	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 { cleverMerge } = require("../util/cleverMerge");
+const {
+	getDefaultTarget,
+	getTargetProperties,
+	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").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").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"])
+			);
+		}
+	}
+
+	F(
+		options,
+		"devtool",
+		() =>
+			/** @type {Devtool} */ (
+				development
+					? [
+							options.experiments.css
+								? {
+										type: "css",
+										use: "source-map"
+									}
+								: undefined,
+							{
+								type: "javascript",
+								use: "eval"
+							}
+						].filter(Boolean)
+					: false
+			)
+	);
+
+	D(options, "watch", false);
+	D(options, "profile", false);
+	D(options, "parallelism", 100);
+	D(options, "recordsInputPath", false);
+	D(options, "recordsOutputPath", false);
+
+	applyExperimentsDefaults(options.experiments, {
+		production,
+		development,
+		targetProperties
+	});
+
+	const futureDefaults =
+		/** @type {NonNullable} */
+		(options.experiments.futureDefaults);
+
+	F(options, "validate", () => !(futureDefaults === true && production));
+
+	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 {NonNullable} */
+			(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 {NonNullable} */
+			(options.experiments.asyncWebAssembly),
+		css:
+			/** @type {NonNullable} */
+			(options.experiments.css),
+		html:
+			/** @type {NonNullable} */
+			(options.experiments.html),
+		typescript:
+			/** @type {NonNullable} */
+			(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
+	});
+
+	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 }
+	);
+
+	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 {NonNullable} */
+			(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 {NonNullable} */
+				(options.experiments.css),
+			html:
+				/** @type {NonNullable} */
+				(options.experiments.html),
+			typescript:
+				/** @type {NonNullable} */
+				(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
+ * @returns {void}
+ */
+const applyExperimentsDefaults = (
+	experiments,
+	{ production, development, targetProperties }
+) => {
+	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);
+	// 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 : undefined));
+	F(experiments, "html", () => (experiments.futureDefaults ? true : undefined));
+	F(experiments, "typescript", () =>
+		experiments.futureDefaults ? true : undefined
+	);
+
+	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, "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
+		);
+
+		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
+			);
+		}
+
+		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
+) => {
+	let body =
+		originalTag.slice(0, srcStartInTag) +
+		newUrl +
+		originalTag.slice(srcEndInTag);
+
+	// Strip dangerous-to-copy attributes from the cloned tag — currently
+	// just `integrity`, which is content-specific and would be wrong for a
+	// different chunk's file. The match handles all three quoting styles
+	// (`"…"`, `'…'`, unquoted) and the bare-attribute form. The `includes`
+	// gate skips the regex engine for the common no-SRI tag.
+	// TODO: emit a correct per-chunk `integrity` once a core SRI output
+	// option exists, instead of dropping it.
+	if (body.includes("integrity")) {
+		body = body.replace(
+			/\s+integrity(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+))?(?=[\s/>])/gi,
+			""
+		);
+	}
+
+	if (elementKind === "script-module") {
+		if (/\stype\s*=/i.test(body)) {
+			body = body.replace(
+				/(\stype\s*=\s*)(?:"[^"]*"|'[^']*'|[^\s>]+)/i,
+				'$1"module"'
+			);
+		} else {
+			body = body.replace(/^` is a void element — no closing tag. ``.
+	return elementKind === "modulepreload" || elementKind === "stylesheet"
+		? body
+		: `${body}`;
+};
+
+HtmlEntryDependency.Template = class HtmlEntryDependencyTemplate extends (
+	ModuleDependency.Template
+) {
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @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 { runtimeTemplate } = templateContext;
+		const dep = /** @type {HtmlEntryDependency} */ (dependency);
+		const compilation = runtimeTemplate.compilation;
+		const { chunkGraph } = compilation;
+		const { crossOriginLoading } = compilation.outputOptions;
+		const entrypoint = /** @type {Entrypoint | undefined} */ (
+			compilation.entrypoints.get(dep.entryName)
+		);
+
+		if (!entrypoint) {
+			source.replace(dep.range[0], dep.range[1] - 1, "data:,");
+			return;
+		}
+
+		const orderedChunks = getEntrypointChunksInLoadOrder(entrypoint);
+		const entryChunk = orderedChunks[orderedChunks.length - 1];
+		const isStylesheet = dep.elementKind === "stylesheet";
+
+		// Rewrite src/href to a chunk-URL sentinel (resolved by renderManifest):
+		// `.css` for ``, `.js` for everything else.
+		const entryContentHashType = isStylesheet ? "css" : "javascript";
+		const entryUrl = HtmlGenerator.makeChunkUrlSentinel(
+			entryChunk,
+			entryContentHashType
+		);
+		source.replace(dep.range[0], dep.range[1] - 1, entryUrl);
+
+		if (dep.tagStart < 0 || dep.tagOpenEnd <= dep.tagStart) {
+			return;
+		}
+
+		// The browser must load every chunk the entry needs, not just the
+		// entry chunk. For ``), so a
+		// real native tag is synthesized instead. Decided at parse time from the
+		// tag name (`dep.tagIsNative`) rather than re-parsing the source text.
+		const tagIsNative = dep.tagIsNative;
+
+		// `crossorigin` to mirror `output.crossOriginLoading` onto every injected
+		// tag. Empty when the option is off or the originating tag already set
+		// `crossorigin` (author value wins, flagged at parse time). Mirrors
+		// webpack's runtime, which sets `crossOrigin` on chunk-loading scripts,
+		// and matches Vite, which emits it on every injected script/stylesheet.
+		const crossOrigin =
+			crossOriginLoading && !dep.hasOwnCrossOrigin
+				? ` crossorigin="${crossOriginLoading}"`
+				: "";
+		const tagNameEndInTag = dep.tagNameEnd - dep.tagStart;
+
+		// Mirror it onto the entry tag too (siblings get it via the builders
+		// below), inserted right after the tag name — its parse-time offset — so
+		// it sits alongside any `type="module"` the parser injected.
+		if (tagIsNative && crossOrigin && dep.tagNameEnd >= 0) {
+			source.insert(dep.tagNameEnd, crossOrigin);
+		}
+
+		/**
+		 * @param {Chunk} chunk chunk to emit a sibling tag for
+		 * @param {"javascript" | "css"} kind content type slice of the chunk to emit
+		 * @returns {string} a single sibling tag's HTML
+		 */
+		const buildSibling = (chunk, kind) => {
+			const url = HtmlGenerator.makeChunkUrlSentinel(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 `` block in an HTML module. The
+ * tag's body is bundled as its own entry chunk — the same pipeline that
+ * processes ``)
+	 * @param {string} entryName name of the entry the inline JS is bundled into
+	 * @param {string=} category dependency category used for resolving and grouping
+	 */
+	constructor(request, insertPos, contentRange, entryName, category) {
+		super(request);
+		this.insertPos = insertPos;
+		this.contentRange = contentRange;
+		this.range = contentRange;
+		this.entryName = entryName;
+		/** @type {string} */
+		this._category = category || "commonjs";
+	}
+
+	get type() {
+		return "html inline script";
+	}
+
+	get category() {
+		return this._category;
+	}
+
+	/**
+	 * Serializes this instance into the provided serializer context.
+	 * @param {ObjectSerializerContext} context context
+	 */
+	serialize(context) {
+		context
+			.write(this.insertPos)
+			.write(this.contentRange)
+			.write(this.entryName)
+			.write(this._category);
+		super.serialize(context);
+	}
+
+	/**
+	 * Restores this instance from the provided deserializer context.
+	 * @param {ObjectDeserializerContext} context context
+	 */
+	deserialize(context) {
+		this.insertPos = context.read();
+		const c1 = context.rest;
+		this.contentRange = c1.read();
+		this.range = this.contentRange;
+		const c2 = c1.rest;
+		this.entryName = c2.read();
+		const c3 = c2.rest;
+		this._category = c3.read();
+		super.deserialize(c3.rest);
+	}
+}
+
+HtmlInlineScriptDependency.Template = class HtmlInlineScriptDependencyTemplate extends (
+	ModuleDependency.Template
+) {
+	/**
+	 * Applies the plugin by registering its hooks on the compiler.
+	 * @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 { runtimeTemplate } = templateContext;
+		const dep = /** @type {HtmlInlineScriptDependency} */ (dependency);
+		const compilation = runtimeTemplate.compilation;
+		const entrypoint = /** @type {Entrypoint | undefined} */ (
+			compilation.entrypoints.get(dep.entryName)
+		);
+
+		/** @type {string} */
+		let url = "data:,";
+
+		if (entrypoint) {
+			const chunk = /** @type {Chunk} */ (entrypoint.getEntrypointChunk());
+			// Defer chunk-URL substitution to renderManifest — chunk hashes aren't ready yet.
+			url = HtmlGenerator.makeChunkUrlSentinel(chunk, "javascript");
+		}
+
+		// Insert ` src="…"` right after `} ObjectDeserializerContext */
+/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */
+/** @typedef {import("../util/Hash")} Hash */
+/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
+
+const TRAILING_WHITESPACE_REGEXP = /\s+$/;
+// 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
+// while avoiding two extra full-string scans + intermediate strings.
+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 inline CSS in an HTML module — either a ``
+ * block (a stylesheet) or an element's `style="..."` attribute (a CSS
+ * block's contents). The content is fed into webpack's CSS pipeline as a
+ * virtual CSS module with `exportType: "text"` so `url()` and `\@import`
+ * references are resolved relative to the HTML file. At render time the
+ * original content range is replaced with the processed CSS text read from
+ * the CSS module's code generation data.
+ */
+class HtmlInlineStyleDependency extends ModuleDependency {
+	/**
+	 * Creates an instance of HtmlInlineStyleDependency.
+	 * @param {string} request virtual request resolving to the inline CSS (data URI)
+	 * @param {Range} range range of the inline CSS content (between ``, or the `style` attribute value)
+	 * @param {boolean=} attribute true when the source is a `style="..."` attribute (a block's contents) rather than a `(31)
+		const source = "";
+		const firstText = "abc";
+		const secondText = "def";
+		const firstStart = source.indexOf(firstText); // 7
+		const secondStart = source.indexOf(
+			secondText,
+			firstStart + firstText.length
+		); // 20
+		/** @type {EXPECTED_OBJECT[]} */
+		const dependencies = [];
+		const module = /** @type {EXPECTED_ANY} */ ({
+			resource: path.resolve(__dirname, "index.html"),
+			buildInfo: {},
+			buildMeta: {},
+			identifier() {
+				return this.resource;
+			},
+			addPresentationalDependency() {},
+			addDependency(/** @type {EXPECTED_OBJECT} */ dependency) {
+				dependencies.push(dependency);
+			},
+			addCodeGenerationDependency() {}
+		});
+
+		buildHtmlAst.mockReturnValue({
+			type: NodeType.Document,
+			children: [
+				{
+					type: NodeType.Element,
+					tagName: "style",
+					namespace: 0,
+					attributes: [],
+					children: [
+						{
+							type: NodeType.Text,
+							data: firstText,
+							start: firstStart,
+							end: firstStart + firstText.length
+						},
+						{
+							type: NodeType.Comment,
+							data: " X ",
+							start: firstStart + firstText.length,
+							end: secondStart
+						},
+						{
+							type: NodeType.Text,
+							data: secondText,
+							start: secondStart,
+							end: secondStart + secondText.length
+						}
+					],
+					selfClosing: false,
+					start: 0,
+					end: source.length,
+					tagEnd: source.indexOf(">") + 1,
+					nameEnd: " d instanceof HtmlInlineStyleDependency
+		);
+		expect(styleDeps).toHaveLength(1);
+
+		const dep = styleDeps[0];
+		// range[0] must be the start of the FIRST text child (7), not
+		// the start of the second (20) — the regression the fix targets.
+		expect(dep.range[0]).toBe(firstStart);
+		// range[1] must reach the end of the LAST text child.
+		expect(dep.range[1]).toBe(secondStart + secondText.length);
+	});
+
+	describe("applyTemplate", () => {
+		/**
+		 * @returns {EXPECTED_ANY} module double with dependency sets + diagnostics
+		 */
+		const templateModule = () => {
+			/** @type {EXPECTED_OBJECT[]} */
+			const warnings = [];
+			/** @type {EXPECTED_OBJECT[]} */
+			const errors = [];
+			return {
+				resource: path.resolve(__dirname, "index.html"),
+				buildInfo: /** @type {Record} */ ({
+					fileDependencies: new Set(),
+					contextDependencies: new Set(),
+					missingDependencies: new Set()
+				}),
+				addWarning(/** @type {EXPECTED_OBJECT} */ warning) {
+					warnings.push(warning);
+				},
+				addError(/** @type {EXPECTED_OBJECT} */ error) {
+					errors.push(error);
+				},
+				warnings,
+				errors
+			};
+		};
+
+		it("is a no-op without a template option", () => {
+			const parser = new HtmlParser({});
+			expect(parser.applyTemplate("

x

", templateModule())).toBe( + "

x

" + ); + }); + + it("exposes working dependency and diagnostic callbacks", () => { + const module = templateModule(); + const parser = new HtmlParser({ + template: (source, ctx) => { + ctx.addDependency("/dep"); + ctx.addContextDependency("/ctx"); + ctx.addMissingDependency("/missing"); + ctx.addBuildDependency("/build"); + ctx.emitWarning("warn-string"); + ctx.emitWarning(new Error("warn-error")); + ctx.emitError("error-string"); + ctx.emitError(new Error("error-error")); + return `${source}!`; + } + }); + + const out = parser.applyTemplate("

", module); + + expect(out).toBe("

!"); + expect([...module.buildInfo.fileDependencies]).toContain("/dep"); + expect([...module.buildInfo.contextDependencies]).toContain("/ctx"); + expect([...module.buildInfo.missingDependencies]).toContain("/missing"); + // addBuildDependency lazily creates the LazySet. + expect(module.buildInfo.buildDependencies).toBeDefined(); + expect([...module.buildInfo.buildDependencies]).toContain("/build"); + // Both string and Error arguments are wrapped/passed through. + expect(module.warnings).toHaveLength(2); + expect(module.errors).toHaveLength(2); + }); + + it("throws when the template does not return a string", () => { + const parser = new HtmlParser({ + template: () => /** @type {EXPECTED_ANY} */ (42) + }); + expect(() => parser.applyTemplate("

", templateModule())).toThrow( + "must return a string" + ); + }); + }); + + it("warns on a malformed webpackIgnore magic comment", () => { + const source = ""; + const { module, warnings } = makeModule(); + buildHtmlAst.mockReturnValue({ + type: NodeType.Document, + children: [ + { + type: NodeType.Comment, + data: " webpackIgnore: ) ", + start: 0, + end: source.length + } + ] + }); + + new HtmlParser({}).parse(source, makeState(module)); + + expect(warnings).toHaveLength(1); + expect(warnings[0]).toBeInstanceOf(CommentCompilationWarning); + }); + + it("warns when webpackIgnore is not a boolean", () => { + const source = ""; + const { module, warnings } = makeModule(); + buildHtmlAst.mockReturnValue({ + type: NodeType.Document, + children: [ + { + type: NodeType.Comment, + data: " webpackIgnore: 5 ", + start: 0, + end: source.length + } + ] + }); + + new HtmlParser({}).parse(source, makeState(module)); + + expect(warnings).toHaveLength(1); + expect(warnings[0]).toBeInstanceOf(UnsupportedFeatureWarning); + }); + + it("does not emit a dependency for a whitespace-only inline "; + const { module, dependencies } = makeModule(); + buildHtmlAst.mockReturnValue({ + type: NodeType.Document, + children: [ + { + type: NodeType.Element, + tagName: "style", + namespace: 0, + attributes: [], + children: [{ type: NodeType.Text, data: " ", start: 7, end: 10 }], + selfClosing: false, + start: 0, + end: source.length, + tagEnd: 7, + nameEnd: " d instanceof HtmlInlineStyleDependency) + ).toHaveLength(0); + }); + + it("accepts a Buffer source and strips a leading BOM", () => { + const { module } = makeModule(); + buildHtmlAst.mockReturnValue({ + type: NodeType.Document, + children: [] + }); + + new HtmlParser({}).parse(Buffer.from("

"), makeState(module)); + expect(buildHtmlAst).toHaveBeenCalledWith("
"); + + new HtmlParser({}).parse("
", makeState(module)); + expect(buildHtmlAst).toHaveBeenLastCalledWith("
"); + }); + + it("throws when given a preparsed (object) source", () => { + const { module } = makeModule(); + expect(() => + new HtmlParser({}).parse( + /** @type {EXPECTED_ANY} */ ({}), + makeState(module) + ) + ).toThrow("webpackAst is unexpected"); + }); + + it("ignores a magic comment that has no webpackIgnore key", () => { + const source = ""; + const { module, warnings } = makeModule(); + buildHtmlAst.mockReturnValue({ + type: NodeType.Document, + children: [ + { + type: NodeType.Comment, + data: " webpackPreload: true ", + start: 0, + end: source.length + } + ] + }); + + new HtmlParser({}).parse(source, makeState(module)); + expect(warnings).toHaveLength(0); + }); + + it("does not emit a dependency for an empty inline "; + const { module, dependencies } = makeModule(); + buildHtmlAst.mockReturnValue({ + type: NodeType.Document, + children: [ + { + type: NodeType.Element, + tagName: "style", + namespace: 0, + attributes: [], + children: [], + selfClosing: false, + start: 0, + end: source.length, + tagEnd: 7, + nameEnd: " d instanceof HtmlInlineStyleDependency) + ).toHaveLength(0); + }); + + // Build a `"], + [""] + ])( + "drops a single-quoted/unquoted type=module for classic output (%s)", + (source) => { + const { module, presentationalDependencies } = makeModule(); + buildHtmlAst.mockReturnValue(scriptWithType(source)); + + new HtmlParser({}).parse(source, makeState(module)); + + // A presentational ConstDependency is added to remove `type="module"`. + expect(presentationalDependencies.length).toBeGreaterThan(0); + } + ); + + describe("source extraction", () => { + // Feed the real tree builder so these exercise genuine offsets/namespaces. + const realBuildHtmlAst = + /** @type {typeof import("../lib/html/syntax")} */ ( + jest.requireActual("../lib/html/syntax") + ).buildHtmlAst; + + /** + * @param {string} source html + * @returns {string[]} the requests of the emitted HtmlSourceDependency-s + */ + const sourceRequests = (source) => { + const { module, dependencies } = makeModule(); + buildHtmlAst.mockReturnValue(realBuildHtmlAst(source)); + new HtmlParser({}).parse(source, makeState(module)); + return dependencies + .filter((d) => d instanceof HtmlSourceDependency) + .map((d) => /** @type {EXPECTED_ANY} */ (d).request); + }; + + it("extracts external url() in SVG presentation attributes, skipping local/empty", () => { + expect( + sourceRequests( + '' + ) + ).toEqual(["./g.svg#x", "./g.svg#y"]); + }); + + it("ignores SVG presentation attributes that are valueless or carry no url()", () => { + // `fill="red"` has no url(); `stroke` is valueless — both are skipped. + expect(sourceRequests('')).toEqual( + [] + ); + }); + + it("maps offsets through entities in an SVG presentation url()", () => { + expect( + sourceRequests('') + ).toEqual(["./a&b.svg#z"]); + }); + + it("extracts SVG paint-server / reference element href values", () => { + expect( + sourceRequests( + '' + ) + ).toEqual(["./d.svg#g", "./d.svg#f"]); + }); + + it("extracts legacy and obsolete source attributes, skipping a non-ref ", () => { + const requests = sourceRequests( + '' + + '' + + '' + ); + expect(requests).toEqual( + expect.arrayContaining([ + "./i.png", + "./t.png", + "./c.bin", + "./p.bin", + "./a.class", + "./o.ser", + "./m.png" + ]) + ); + expect(requests).toHaveLength(7); + expect(requests).not.toContain("./skip"); + }); + }); +}); diff --git a/test/Integration.test.js b/test/Integration.test.js deleted file mode 100644 index a09f0c8e3ca..00000000000 --- a/test/Integration.test.js +++ /dev/null @@ -1,104 +0,0 @@ -"use strict"; - -const path = require("path"); -const webpack = require("../lib/webpack"); - -describe("Integration", () => { - jest.setTimeout(10000); - it("should compile library1", done => { - webpack( - { - mode: "production", - entry: "library1", - bail: true, - context: path.join(__dirname, "browsertest"), - output: { - pathinfo: true, - path: path.join(__dirname, "browsertest", "js"), - filename: "library1.js", - library: "library1" - } - }, - (err, stats) => { - if (err) throw err; - expect(stats.hasErrors()).toBe(false); - expect(stats.hasWarnings()).toBe(false); - done(); - } - ); - }); - it("should compile library2", done => { - webpack( - { - mode: "production", - entry: "library2", - context: path.join(__dirname, "browsertest"), - output: { - pathinfo: true, - path: path.join(__dirname, "browsertest", "js"), - filename: "library2.js", - publicPath: "js/", - library: "library2" - }, - bail: true, - module: { - rules: [ - { - test: /extra2\.js/, - loader: "raw!extra!val?cacheable", - enforce: "post" - } - ] - }, - amd: { - fromOptions: true - }, - optimization: { - minimize: false - }, - plugins: [ - new webpack.optimize.LimitChunkCountPlugin({ - maxChunks: 1 - }), - new webpack.DefinePlugin({ - "typeof CONST_TYPEOF": JSON.stringify("typeof"), - CONST_TRUE: true, - CONST_FALSE: false, - CONST_FUNCTION: function() { - return "ok"; - }, - CONST_NUMBER: 123, - CONST_NUMBER_EXPR: "1*100+23", - CONST_OBJECT: { - A: 1, - B: JSON.stringify("B"), - C: function() { - return "C"; - } - } - }), - function() { - this.hooks.normalModuleFactory.tap("IntegrationTest", nmf => { - nmf.hooks.afterResolve.tapAsync( - "IntegrationTest", - (data, callback) => { - data.resource = data.resource.replace( - /extra\.js/, - "extra2.js" - ); - setTimeout(() => callback(null, data), 50); - } - ); - }); - } - ] - }, - (err, stats) => { - if (err) throw err; - expect(stats.hasErrors()).toBe(false); - expect(stats.hasWarnings()).toBe(false); - done(); - } - ); - }); -}); diff --git a/test/JavascriptParser.unittest.js b/test/JavascriptParser.unittest.js new file mode 100644 index 00000000000..9e6488da90c --- /dev/null +++ b/test/JavascriptParser.unittest.js @@ -0,0 +1,1051 @@ +"use strict"; + +/* eslint-disable no-unused-expressions, no-unassigned-vars, func-names */ + +// cspell:ignore fghsub notry fghsub notry notry this's ijksub this's ijksub fghsub fghsub notry ijksub ijksub strrring strrring strr strrring strrring strr Sstrrringy strone stronetwo stronetwothree stronetwo stronetwothree stronetwothreefour onetwo onetwo twothree twothree twothree threefour onetwo onetwo threefour threefour fourfive startstrmid igmy igmyi igmya +const BasicEvaluatedExpression = require("../lib/javascript/BasicEvaluatedExpression"); +const JavascriptParser = require("../lib/javascript/JavascriptParser"); + +describe("JavascriptParser", () => { + /* eslint-disable no-unused-vars */ + /** @type {EXPECTED_ANY} */ let abc; + /** @type {EXPECTED_ANY} */ let cde; + /** @type {EXPECTED_ANY} */ let fgh; + /** @type {EXPECTED_ANY} */ let memberExpr; + /** @type {EXPECTED_ANY} */ let ijk; + /** @type {EXPECTED_ANY} */ let xyz; + const testCases = /** @type {EXPECTED_ANY} */ ({ + "call ident": [ + function () { + abc("test"); + }, + { + abc: ["test"] + } + ], + "call member": [ + function () { + cde.abc("membertest"); + }, + { + cdeabc: ["membertest"] + } + ], + "call member using bracket notation": [ + function () { + // eslint-disable-next-line dot-notation + cde["abc"]("membertest"); + }, + { + cdeabc: ["membertest"] + } + ], + "call inner member": [ + function () { + cde.ddd.abc("inner"); + }, + { + cdedddabc: ["inner"] + } + ], + "call inner member using bracket notation": [ + function () { + // eslint-disable-next-line dot-notation + cde.ddd["abc"]("inner"); + }, + { + cdedddabc: ["inner"] + } + ], + expression: [ + function () { + fgh; + }, + { + fgh: [""] + } + ], + "expression sub": [ + function () { + fgh.sub; + }, + { + fghsub: ["notry"] + } + ], + "member expression": [ + function () { + // @ts-expect-error + test[memberExpr]; + + // @ts-expect-error + test[+memberExpr]; // eslint-disable-line no-implicit-coercion + }, + { + expressions: ["memberExpr", "memberExpr"] + } + ], + "in function definition": [ + function () { + (function (abc, cde, fgh) { + // @ts-expect-error + abc("test"); + // @ts-expect-error + cde.abc("test"); + // @ts-expect-error + cde.ddd.abc("test"); + fgh; + // @ts-expect-error + fgh.sub; + })(); + }, + {} + ], + "const definition": [ + function () { + // eslint-disable-next-line one-var + let abc, cde, fgh; + // @ts-expect-error + abc("test"); + // @ts-expect-error + cde.abc("test"); + // @ts-expect-error + cde.ddd.abc("test"); + fgh; + // @ts-expect-error + fgh.sub; + }, + {} + ], + "var definition": [ + function () { + // eslint-disable-next-line one-var + let abc, cde, fgh; + // @ts-expect-error + abc("test"); + // @ts-expect-error + cde.abc("test"); + // @ts-expect-error + cde.ddd.abc("test"); + fgh; + // @ts-expect-error + fgh.sub; + }, + {} + ], + "function definition": [ + function () { + function abc() {} + + function cde() {} + + function fgh() {} + // @ts-expect-error + abc("test"); + // @ts-expect-error + cde.abc("test"); + // @ts-expect-error + cde.ddd.abc("test"); + fgh; + // @ts-expect-error + fgh.sub; + }, + {} + ], + "class definition": [ + function () { + class memberExpr { + cde() { + abc("cde"); + } + + static fgh() { + abc("fgh"); + fgh(); + } + } + }, + { + abc: ["cde", "fgh"], + fgh: ["memberExpr"] + } + ], + "in try": [ + function () { + try { + fgh.sub; + fgh; + + // @ts-expect-error + function test(ttt) { + fgh.sub; + fgh; + } + } catch (err) { + fgh.sub; + fgh; + } + }, + { + fghsub: ["try", "notry", "notry"], + fgh: ["test", "test ttt", "test err"] + } + ], + "renaming with const": [ + function () { + const xyz = abc; + xyz("test"); + }, + { + abc: ["test"] + } + ], + "renaming with var": [ + function () { + const xyz = abc; + xyz("test"); + }, + { + abc: ["test"] + } + ], + "renaming with assignment": [ + function () { + const xyz = abc; + xyz("test"); + }, + { + abc: ["test"] + } + ], + "renaming with IIFE": [ + function () { + // @ts-expect-error + !(function (xyz) { + xyz("test"); + })(abc); + }, + { + abc: ["test"] + } + ], + "renaming arguments with IIFE (called)": [ + function () { + // @ts-expect-error + !function (xyz) { + xyz("test"); + }.call(fgh, abc); + }, + { + abc: ["test"], + fgh: [""] + } + ], + "renaming this's properties with IIFE (called)": [ + function () { + // @ts-expect-error + !function () { + // @ts-expect-error + this.sub; + }.call(ijk); + }, + { + ijksub: ["test"] + } + ], + "renaming this's properties with nested IIFE (called)": [ + function () { + // @ts-expect-error + !function () { + // @ts-expect-error + !function () { + // @ts-expect-error + this.sub; + // @ts-expect-error + }.call(this); + }.call(ijk); + }, + { + ijksub: ["test"] + } + ], + "new Foo(...)": [ + function () { + // eslint-disable-next-line new-cap, no-new + new xyz("membertest"); + }, + { + xyz: ["membertest"] + } + ], + "spread calls/literals": [ + function () { + const xyz = [...abc("xyz"), cde]; + Math.max(...fgh); + }, + { + abc: ["xyz"], + fgh: ["xyz"] + } + ] + }); + + /* eslint-enable no-unused-vars */ + + for (const name of Object.keys(testCases)) { + it(`should parse ${name}`, () => { + let source = /** @type {Record} */ (testCases)[ + name + ][0].toString(); + source = source.slice(13, -1).trim(); + const state = /** @type {Record} */ (testCases)[ + name + ][1]; + + const testParser = new JavascriptParser( + /** @type {"auto"} */ (/** @type {unknown} */ ({})) + ); + testParser.hooks.canRename + .for("abc") + .tap("JavascriptParserTest", (_expr) => true); + testParser.hooks.canRename + .for("ijk") + .tap("JavascriptParserTest", (_expr) => true); + testParser.hooks.call.for("abc").tap("JavascriptParserTest", (expr) => { + if (!testParser.state.abc) testParser.state.abc = []; + testParser.state.abc.push( + testParser.parseString( + /** @type {import("estree").Expression} */ (expr.arguments[0]) + ) + ); + return true; + }); + testParser.hooks.call + .for("cde.abc") + .tap("JavascriptParserTest", (expr) => { + if (!testParser.state.cdeabc) testParser.state.cdeabc = []; + testParser.state.cdeabc.push( + testParser.parseString( + /** @type {import("estree").Expression} */ (expr.arguments[0]) + ) + ); + return true; + }); + testParser.hooks.call + .for("cde.ddd.abc") + .tap("JavascriptParserTest", (expr) => { + if (!testParser.state.cdedddabc) testParser.state.cdedddabc = []; + testParser.state.cdedddabc.push( + testParser.parseString( + /** @type {import("estree").Expression} */ (expr.arguments[0]) + ) + ); + return true; + }); + testParser.hooks.expression + .for("fgh") + .tap("JavascriptParserTest", (_expr) => { + if (!testParser.state.fgh) testParser.state.fgh = []; + testParser.state.fgh.push( + [...testParser.scope.definitions.asSet()].join(" ") + ); + return true; + }); + testParser.hooks.expression + .for("fgh.sub") + .tap("JavascriptParserTest", (_expr) => { + if (!testParser.state.fghsub) testParser.state.fghsub = []; + testParser.state.fghsub.push( + testParser.scope.inTry ? "try" : "notry" + ); + return true; + }); + testParser.hooks.expression + .for("ijk.sub") + .tap("JavascriptParserTest", (_expr) => { + if (!testParser.state.ijksub) testParser.state.ijksub = []; + testParser.state.ijksub.push("test"); + return true; + }); + testParser.hooks.expression + .for("memberExpr") + .tap("JavascriptParserTest", (expr) => { + if (!testParser.state.expressions) testParser.state.expressions = []; + testParser.state.expressions.push( + /** @type {import("estree").Identifier} */ (expr).name + ); + return true; + }); + testParser.hooks.new.for("xyz").tap("JavascriptParserTest", (expr) => { + if (!testParser.state.xyz) testParser.state.xyz = []; + testParser.state.xyz.push( + testParser.parseString( + /** @type {import("estree").Expression} */ (expr.arguments[0]) + ) + ); + return true; + }); + const actual = testParser.parse( + source, + /** @type {import("../lib/Parser").ParserState} */ ( + /** @type {unknown} */ ({}) + ) + ); + expect(typeof actual).toBe("object"); + expect(actual).toEqual(state); + }); + } + + it("should parse comments", () => { + const source = "//comment1\n/*comment2*/"; + const state = [ + { + type: "Line", + value: "comment1" + }, + { + type: "Block", + value: "comment2" + } + ]; + + const testParser = new JavascriptParser( + /** @type {"auto"} */ (/** @type {unknown} */ ({})) + ); + + testParser.hooks.program.tap("JavascriptParserTest", (ast, comments) => { + if (!testParser.state.comments) testParser.state.comments = comments; + return true; + }); + + const actual = testParser.parse( + source, + /** @type {import("../lib/Parser").ParserState} */ ( + /** @type {unknown} */ ({}) + ) + ); + expect(typeof actual).toBe("object"); + expect(typeof actual.comments).toBe("object"); + for (const [index, element] of actual.comments.entries()) { + expect(typeof element.type).toBe("string"); + expect(typeof element.value).toBe("string"); + expect(element.type).toBe(state[index].type); + expect(element.value).toBe(state[index].value); + } + }); + + describe("expression evaluation", () => { + /** + * @param {string} source source + * @returns {import("../lib/javascript/BasicEvaluatedExpression")} the evaluated expression + */ + function evaluateInParser(source) { + const parser = new JavascriptParser(); + parser.hooks.call.for("test").tap("JavascriptParserTest", (expr) => { + parser.state.result = parser.evaluateExpression(expr.arguments[0]); + }); + parser.hooks.evaluateIdentifier + .for("aString") + .tap("JavascriptParserTest", (expr) => + new BasicEvaluatedExpression() + .setString("aString") + .setRange( + /** @type {import("../lib/javascript/JavascriptParser").Range} */ ( + expr.range + ) + ) + ); + parser.hooks.evaluateIdentifier + .for("b.Number") + .tap("JavascriptParserTest", (expr) => + new BasicEvaluatedExpression() + .setNumber(123) + .setRange( + /** @type {import("../lib/javascript/JavascriptParser").Range} */ ( + expr.range + ) + ) + ); + return parser.parse( + `test(${source});`, + /** @type {import("../lib/Parser").ParserState} */ ( + /** @type {unknown} */ ({}) + ) + ).result; + } + + const testCases = { + true: "bool=true", + false: "bool=false", + "!true": "bool=false", + "!false": "bool=true", + '"strrring"': "string=strrring", + '"strr" + "ring"': "string=strrring", + '"s" + ("trr" + "rin") + "g"': "string=strrring", + "'S' + (\"strr\" + \"ring\") + 'y'": "string=Sstrrringy", + "/abc/": "regExp=/abc/", + 1: "number=1", + "1 + 3": "number=4", + "3 - 1": "number=2", + "2 * 3": "number=6", + "8 / 2": "number=4", + "2 ** 3": "number=8", + "12 & 5": "number=4", + "12 | 5": "number=13", + "12 ^ 5": "number=9", + "9 >>> 2": "number=2", + "9 >> 2": "number=2", + "9 << 2": "number=36", + "~3": "number=-4", + "1 == 1": "bool=true", + "1 === 1": "bool=true", + "3 != 1": "bool=true", + "3 !== 1": "bool=true", + "3 == 1": "bool=false", + "3 === 1": "bool=false", + "1 != 1": "bool=false", + "1 !== 1": "bool=false", + 100.25: "number=100.25", + "!100.25": "bool=false", + "!+100.25": "bool=false", + "!-100.25": "bool=false", + 0: "number=0", + "!0": "bool=true", + "!-0": "bool=true", + "!+0": "bool=true", + "20n": "bigint=20", + "10n + 10n": "bigint=20", + "10n - 5n": "bigint=5", + "10n * 5n": "bigint=50", + "10n / 5n": "bigint=2", + "5n ** 2n": "bigint=25", + "5n == 5n": "bool=true", + "5n === 5n": "bool=true", + "5n != 5n": "bool=false", + "5n !== 5n": "bool=false", + "5n != 1n": "bool=true", + "5n !== 1n": "bool=true", + "5n & 3n": "bigint=1", + "5n | 2n": "bigint=7", + "5n ^ 2n": "bigint=7", + "5n >> 2n": "bigint=1", + "5n << 2n": "bigint=20", + "null == null": "bool=true", + "null === null": "bool=true", + "null != null": "bool=false", + "null !== null": "bool=false", + "true === false": "bool=false", + "false !== false": "bool=false", + "true == true": "bool=true", + "false != true": "bool=true", + "!'a'": "bool=false", + "!''": "bool=true", + "!null": "bool=true", + "'pre' + a": "wrapped=['pre' string=pre]+[null]", + "a + 'post'": "wrapped=[null]+['post' string=post]", + "'pre' + a + 'post'": "wrapped=['pre' string=pre]+['post' string=post]", + "1 + a + 2": "", + "1 + a + 'post'": "wrapped=[null]+['post' string=post]", + "'' + 1 + a + 2": "wrapped=['' + 1 string=1]+[2 string=2]", + "'' + 1 + a + 2 + 3": "wrapped=['' + 1 string=1]+[2 + 3 string=23]", + "'' + 1 + a + (2 + 3)": "wrapped=['' + 1 string=1]+[2 + 3 string=5]", + "'pre' + (1 + a) + (2 + 3)": + "wrapped=['pre' string=pre]+[2 + 3 string=5]", + "a ? 'o1' : 'o2'": "options=['o1' string=o1],['o2' string=o2]", + "a ? 'o1' : b ? 'o2' : 'o3'": + "options=['o1' string=o1],['o2' string=o2],['o3' string=o3]", + "a ? (b ? 'o1' : 'o2') : 'o3'": + "options=['o1' string=o1],['o2' string=o2],['o3' string=o3]", + "a ? (b ? 'o1' : 'o2') : c ? 'o3' : 'o4'": + "options=['o1' string=o1],['o2' string=o2],['o3' string=o3],['o4' string=o4]", + "a ? 'o1' : b ? 'o2' : c ? 'o3' : 'o4'": + "options=['o1' string=o1],['o2' string=o2],['o3' string=o3],['o4' string=o4]", + "a ? 'o1' : b ? b : c ? 'o3' : c": + "options=['o1' string=o1],[b],['o3' string=o3],[c]", + "['i1', 'i2', 3, a, b ? 4 : 5]": + "items=['i1' string=i1],['i2' string=i2],[3 number=3],[a],[b ? 4 : 5 options=[4 number=4],[5 number=5]]", + "typeof 'str'": "string=string", + "typeof aString": "string=string", + "typeof b.Number": "string=number", + "typeof b['Number']": "string=number", + "typeof b[Number]": "", + "typeof true": "string=boolean", + "typeof null": "string=object", + "typeof 1": "string=number", + "typeof 1n": "string=bigint", + "b.Number": "number=123", + "b['Number']": "number=123", + "b[Number]": "", + "'str'.concat()": "string=str", + "'str'.concat('one')": "string=strone", + "'str'.concat('one').concat('two')": "string=stronetwo", + "'str'.concat('one').concat('two', 'three')": "string=stronetwothree", + "'str'.concat('one', 'two')": "string=stronetwo", + "'str'.concat('one', 'two').concat('three')": "string=stronetwothree", + "'str'.concat('one', 'two').concat('three', 'four')": + "string=stronetwothreefour", + "'str'.concat('one', obj)": "wrapped=['str' string=str]+[null]", + "'str'.concat('one', obj).concat()": "wrapped=['str' string=str]+[null]", + "'str'.concat('one', obj, 'two')": + "wrapped=['str' string=str]+['two' string=two]", + "'str'.concat('one', obj, 'two').concat()": + "wrapped=['str' string=str]+['two' string=two]", + "'str'.concat('one', obj, 'two').concat('three')": + "wrapped=['str' string=str]+['three' string=three]", + "'str'.concat(obj)": "wrapped=['str' string=str]+[null]", + "'str'.concat(obj).concat()": "wrapped=['str' string=str]+[null]", + "'str'.concat(obj).concat('one', 'two')": + "wrapped=['str' string=str]+['one', 'two' string=onetwo]", + "'str'.concat(obj).concat(obj, 'one')": + "wrapped=['str' string=str]+['one' string=one]", + "'str'.concat(obj).concat(obj, 'one', 'two')": + "wrapped=['str' string=str]+['one', 'two' string=onetwo]", + "'str'.concat(obj).concat('one', obj, 'one')": + "wrapped=['str' string=str]+['one' string=one]", + "'str'.concat(obj).concat('one', obj, 'two', 'three')": + "wrapped=['str' string=str]+['two', 'three' string=twothree]", + "'str'.concat(obj, 'one')": + "wrapped=['str' string=str]+['one' string=one]", + "'str'.concat(obj, 'one').concat()": + "wrapped=['str' string=str]+['one' string=one]", + "'str'.concat(obj, 'one').concat('two', 'three')": + "wrapped=['str' string=str]+['two', 'three' string=twothree]", + "'str'.concat(obj, 'one').concat(obj, 'two', 'three')": + "wrapped=['str' string=str]+['two', 'three' string=twothree]", + "'str'.concat(obj, 'one').concat('two', obj, 'three')": + "wrapped=['str' string=str]+['three' string=three]", + "'str'.concat(obj, 'one').concat('two', obj, 'three', 'four')": + "wrapped=['str' string=str]+['three', 'four' string=threefour]", + "'str'.concat(obj, 'one', 'two')": + "wrapped=['str' string=str]+['one', 'two' string=onetwo]", + "'str'.concat(obj, 'one', 'two').concat()": + "wrapped=['str' string=str]+['one', 'two' string=onetwo]", + "'str'.concat(obj, 'one', 'two').concat('three', 'four')": + "wrapped=['str' string=str]+['three', 'four' string=threefour]", + "'str'.concat(obj, 'one', 'two').concat(obj, 'three', 'four')": + "wrapped=['str' string=str]+['three', 'four' string=threefour]", + "'str'.concat(obj, 'one', 'two').concat('three', obj, 'four')": + "wrapped=['str' string=str]+['four' string=four]", + "'str'.concat(obj, 'one', 'two').concat('three', obj, 'four', 'five')": + "wrapped=['str' string=str]+['four', 'five' string=fourfive]", + // eslint-disable-next-line no-template-curly-in-string + "`start${obj}mid${obj2}end`": + "template=[start string=start],[mid string=mid],[end string=end]", + // eslint-disable-next-line no-template-curly-in-string + "`start${'str'}mid${obj2}end`": + // eslint-disable-next-line no-template-curly-in-string + "template=[start${'str'}mid string=startstrmid],[end string=end]", + // eslint-disable-next-line no-template-curly-in-string + "`a${x}` === `b${x}`": "bool=false", + // eslint-disable-next-line no-template-curly-in-string + "`${x}a` === `${x}b`": "bool=false", + // eslint-disable-next-line no-template-curly-in-string + "`${a}${b}` === `a${b}`": "", + // eslint-disable-next-line no-template-curly-in-string + "`${a}${b}` === `${a}b`": "", + "'abc'.slice(1)": "string=bc", + "'abcdef'.slice(2, 5)": "string=cde", + "'abcdef'.substring(2, 3)": "string=c", + "'abcdef'.substring(2, 3, 4)": "", + "'abc'[\"slice\"](1)": "string=bc", + "'abc'[slice](1)": "", + "'1,2+3'.split(/[,+]/)": "array=[1],[2],[3]", + "'1,2+3'.split(expr)": "", + "'a' + (expr + 'c')": "wrapped=['a' string=a]+['c' string=c]", + "1 + 'a'": "string=1a", + "'a' + 1": "string=a1", + "'a' + expr + 1": "wrapped=['a' string=a]+[1 string=1]" + }; + + for (const key of Object.keys(testCases)) { + /** + * @param {import("../lib/javascript/BasicEvaluatedExpression")} evalExpr eval expr + * @returns {string} result + */ + function evalExprToString(evalExpr) { + if (!evalExpr) { + return "null"; + } + const result = []; + if (evalExpr.isString()) result.push(`string=${evalExpr.string}`); + if (evalExpr.isNumber()) result.push(`number=${evalExpr.number}`); + if (evalExpr.isBigInt()) result.push(`bigint=${evalExpr.bigint}`); + if (evalExpr.isBoolean()) result.push(`bool=${evalExpr.bool}`); + if (evalExpr.isRegExp()) result.push(`regExp=${evalExpr.regExp}`); + if (evalExpr.isConditional()) { + result.push( + `options=[${/** @type {import("../lib/javascript/BasicEvaluatedExpression")[]} */ (evalExpr.options).map(evalExprToString).join("],[")}]` + ); + } + if (evalExpr.isArray()) { + result.push( + `items=[${/** @type {import("../lib/javascript/BasicEvaluatedExpression")[]} */ (evalExpr.items).map(evalExprToString).join("],[")}]` + ); + } + if (evalExpr.isConstArray()) { + result.push( + `array=[${/** @type {(string | number | boolean | null | RegExp | bigint)[]} */ (evalExpr.array).join("],[")}]` + ); + } + if (evalExpr.isTemplateString()) { + result.push( + `template=[${/** @type {import("../lib/javascript/BasicEvaluatedExpression")[]} */ (evalExpr.quasis).map(evalExprToString).join("],[")}]` + ); + } + if (evalExpr.isWrapped()) { + result.push( + `wrapped=[${evalExprToString(/** @type {import("../lib/javascript/BasicEvaluatedExpression")} */ (evalExpr.prefix))}]+[${evalExprToString( + /** @type {import("../lib/javascript/BasicEvaluatedExpression")} */ ( + evalExpr.postfix + ) + )}]` + ); + } + if (evalExpr.range) { + const start = evalExpr.range[0] - 5; + const end = evalExpr.range[1] - 5; + return ( + key.slice(start, end) + + (result.length > 0 ? ` ${result.join(" ")}` : "") + ); + } + return result.join(" "); + } + + it(`should eval ${key}`, () => { + const evalExpr = evaluateInParser(key); + expect(evalExprToString(evalExpr)).toBe( + /** @type {Record} */ (testCases)[key] + ? `${key} ${/** @type {Record} */ (testCases)[key]}` + : key + ); + }); + } + }); + + describe("async/await support", () => { + describe("should accept", () => { + const cases = { + "async function": "async function x() {}", + "async arrow function": "async () => {}", + "await expression": "async function x(y) { await y }", + "await iteration": "async function f() { for await (x of xs); }" + }; + const parser = new JavascriptParser(); + for (const name of Object.keys(cases)) { + const expr = /** @type {Record} */ (cases)[name]; + + it(name, () => { + const actual = parser.parse( + expr, + /** @type {import("../lib/Parser").ParserState} */ ( + /** @type {unknown} */ ({}) + ) + ); + expect(typeof actual).toBe("object"); + }); + } + }); + + describe("should parse await", () => { + const cases = { + require: [ + "async function x() { await require('y'); }", + { + param: "y" + } + ], + import: [ + "async function x() { const y = await import('z'); }", + { + param: "z" + } + ] + }; + + const parser = new JavascriptParser(); + parser.hooks.call.for("require").tap("JavascriptParserTest", (expr) => { + const param = parser.evaluateExpression(expr.arguments[0]); + parser.state.param = param.string; + }); + parser.hooks.importCall.tap("JavascriptParserTest", (expr) => { + const param = parser.evaluateExpression(expr.source); + parser.state.param = param.string; + }); + + for (const name of Object.keys(cases)) { + it(name, () => { + const actual = parser.parse( + /** @type {Record} */ (cases)[name][0], + /** @type {import("../lib/Parser").ParserState} */ ( + /** @type {unknown} */ ({}) + ) + ); + expect(actual).toEqual( + /** @type {Record} */ (cases)[name][1] + ); + }); + } + }); + }); + + describe("object rest/spread support", () => { + describe("should accept", () => { + const cases = { + "object spread": "({...obj})", + "object rest": "({...obj} = foo)" + }; + for (const name of Object.keys(cases)) { + const expr = /** @type {Record} */ (cases)[name]; + + it(name, () => { + const actual = JavascriptParser._parse( + expr, + /** @type {import("../lib/javascript/JavascriptParser").InternalParseOptions} */ ( + /** @type {unknown} */ ({}) + ) + ); + expect(typeof actual).toBe("object"); + }); + } + }); + + it("should collect definitions from identifiers introduced in object patterns", () => { + /** @type {EXPECTED_ANY} */ + let definitions; + + const parser = new JavascriptParser(); + + parser.hooks.statement.tap("JavascriptParserTest", (_expr) => { + definitions = parser.scope.definitions; + return true; + }); + + parser.parse( + "const { a, ...rest } = { a: 1, b: 2 };", + /** @type {import("../lib/Parser").ParserState} */ ( + /** @type {unknown} */ ({}) + ) + ); + + expect(definitions.has("a")).toBe(true); + expect(definitions.has("rest")).toBe(true); + }); + }); + + describe("parse calculated string", () => { + describe("should work", () => { + const cases = { + 123: { + code: "123", + result: { + code: false, + conditional: false, + range: [0, 3], + value: "123" + } + }, + "'test'": { + code: "'test'", + result: { + code: false, + conditional: false, + range: [0, 6], + value: "test" + } + }, + "'test' + 'test'": { + code: "'test' + 'test'", + result: { + code: false, + conditional: false, + range: [0, 15], + value: "testtest" + } + }, + "myVar + 'test'": { + code: "myVar + 'test'", + result: { + code: true, + conditional: false, + range: undefined, + value: "" + } + }, + "'test' + myVar": { + code: "'test' + myVar", + result: { + code: true, + conditional: false, + range: [0, 6], + value: "test" + } + }, + "true ? 'one' : 'two'": { + code: "true ? 'one' : 'two'", + result: { + code: true, + conditional: [ + { + code: false, + conditional: false, + range: [7, 12], + value: "one" + }, + { + code: false, + conditional: false, + range: [15, 20], + value: "two" + } + ], + range: undefined, + value: "" + } + }, + "true ? true ? 'one' : 'two' : true ? 'three': 'four'": { + code: "true ? true ? 'one' : 'two' : true ? 'three': 'four'", + result: { + code: true, + conditional: [ + { + code: false, + conditional: false, + range: [14, 19], + value: "one" + }, + { + code: false, + conditional: false, + range: [22, 27], + value: "two" + }, + { + code: false, + conditional: false, + range: [37, 44], + value: "three" + }, + { + code: false, + conditional: false, + range: [46, 52], + value: "four" + } + ], + range: undefined, + value: "" + } + } + }; + for (const name of Object.keys(cases)) { + const expr = /** @type {Record} */ (cases)[name]; + + it(name, () => { + const parser = new JavascriptParser(); + const { ast } = JavascriptParser._parse( + expr.code, + /** @type {import("../lib/javascript/JavascriptParser").InternalParseOptions} */ ({ + ranges: true + }) + ); + expect(typeof ast).toBe("object"); + expect( + parser.parseCalculatedString( + /** @type {import("estree").Expression} */ ( + /** @type {EXPECTED_ANY} */ (ast.body[0]).expression + ) + ) + ).toEqual(expr.result); + }); + } + }); + }); + + describe("BasicEvaluatedExpression", () => { + /** @type [string, boolean][] */ + const tests = [ + ...["i", "g", "m", "y"].reduce((acc, flag) => { + acc.push([flag, true]); + acc.push([flag + flag, false]); + return acc; + }, /** @type {[string, boolean][]} */ ([])), + ["", true], + ["igm", true], + ["igmy", true], + ["igmyi", false], + ["igmya", false], + ["ai", false], + ["ia", false] + ]; + + for (const [suite, expected] of tests) { + it(`BasicEvaluatedExpression.isValidRegExpFlags(${JSON.stringify( + suite + )})`, () => { + expect(BasicEvaluatedExpression.isValidRegExpFlags(suite)).toBe( + expected + ); + }); + } + }); + + describe("new import call (acorn-import-phases)", () => { + beforeAll(() => { + const parser = + /** @type {typeof JavascriptParser & { __importPhasesExtended?: true }} */ + (JavascriptParser); + if (!parser.__importPhasesExtended) { + JavascriptParser.extend( + require("acorn-import-phases")({ source: true, defer: true }) + ); + parser.__importPhasesExtended = true; + } + }); + + /** + * @param {string} source source + * @returns {Error | null} thrown error, if any + */ + function parse(source) { + try { + new JavascriptParser().parse( + source, + /** @type {import("../lib/Parser").ParserState} */ ( + /** @type {unknown} */ ({ source }) + ) + ); + return null; + } catch (err) { + return /** @type {Error} */ (err); + } + } + + // `import.defer(...)`/`import.source(...)` are CallExpressions, so they + // cannot be the operand of `new`, including with member access (#21212). + for (const source of [ + 'new import.defer("x");', + 'new import.defer("x").prop;', + 'new import.defer("x").a.b;', + 'new import.source("x").prop;' + ]) { + it(`rejects ${JSON.stringify(source)}`, () => { + const err = parse(source); + expect(err).toBeInstanceOf(SyntaxError); + expect(/** @type {Error} */ (err).message).toBe( + "import call cannot be the target of `new`" + ); + }); + } + + // Parenthesized forms and non-`new` member access stay valid. + for (const source of [ + 'new (import.defer("x")).prop;', + 'import.defer("x").then(() => {});' + ]) { + it(`accepts ${JSON.stringify(source)}`, () => { + expect(parse(source)).toBeNull(); + }); + } + }); +}); diff --git a/test/LazySet.unittest.js b/test/LazySet.unittest.js new file mode 100644 index 00000000000..9507a42f780 --- /dev/null +++ b/test/LazySet.unittest.js @@ -0,0 +1,24 @@ +"use strict"; + +const LazySet = require("../lib/util/LazySet"); + +describe("LazySet", () => { + it("addAll", () => { + const a = new Set(["a"]); + const sut = new LazySet(a); + const empty = new LazySet([]); + expect(sut.size).toBe(1); + sut.addAll(empty); + expect(sut._toDeepMerge).toStrictEqual([]); + expect(sut.size).toBe(1); + const b = new Set(["b"]); + sut.addAll(b); + expect(sut._toMerge).toContain(b); + expect(sut.size).toBe(2); + const c = new LazySet(["c"]); + sut.addAll(c); + expect(sut._toDeepMerge).toContain(c); + expect(sut.size).toBe(3); + expect(sut._toDeepMerge).toStrictEqual([]); + }); +}); diff --git a/test/LocalModulesHelpers.unittest.js b/test/LocalModulesHelpers.unittest.js index b858697073a..374e490ee2f 100644 --- a/test/LocalModulesHelpers.unittest.js +++ b/test/LocalModulesHelpers.unittest.js @@ -1,61 +1,65 @@ -/* globals describe, it */ "use strict"; -const LocalModulesHelpers = require("../lib/dependencies/LocalModulesHelpers"); +const { + addLocalModule, + getLocalModule +} = require("../lib/dependencies/LocalModulesHelpers"); describe("LocalModulesHelpers", () => { describe("addLocalModule", () => { it("returns a module var without special characters", () => { - const state = { - module: "module_sample", - localModules: ["first", "second"] - }; - const localModule = LocalModulesHelpers.addLocalModule( - state, - "local_module_sample" - ); + const state = + /** @type {import("../lib/javascript/JavascriptParser").JavascriptParserState} */ ( + /** @type {unknown} */ ({ + localModules: ["first", "second"] + }) + ); + const localModule = addLocalModule(state, "local_module_sample"); expect(localModule).toBeInstanceOf(Object); expect(localModule).toMatchObject({ - module: "module_sample", name: "local_module_sample", idx: 2, used: false }); - expect(state.localModules.length).toBe(3); + expect(state.localModules).toHaveLength(3); }); }); describe("getLocalModule", () => { it("returns `null` if names information doesn't match", () => { - const state = { - module: "module_sample", - localModules: [ - { - name: "first" - }, - { - name: "second" - } - ] - }; - expect( - LocalModulesHelpers.getLocalModule(state, "local_module_sample") - ).toBe(null); + const state = + /** @type {import("../lib/javascript/JavascriptParser").JavascriptParserState} */ ( + /** @type {unknown} */ ({ + module: "module_sample", + localModules: [ + { + name: "first" + }, + { + name: "second" + } + ] + }) + ); + expect(getLocalModule(state, "local_module_sample")).toBeNull(); }); it("returns local module information", () => { - const state = { - module: "module_sample", - localModules: [ - { - name: "first" - }, - { - name: "second" - } - ] - }; - expect(LocalModulesHelpers.getLocalModule(state, "first")).toEqual({ + const state = + /** @type {import("../lib/javascript/JavascriptParser").JavascriptParserState} */ ( + /** @type {unknown} */ ({ + module: "module_sample", + localModules: [ + { + name: "first" + }, + { + name: "second" + } + ] + }) + ); + expect(getLocalModule(state, "first")).toEqual({ name: "first" }); }); diff --git a/test/MemoryLimitTestCases.test.js b/test/MemoryLimitTestCases.test.js new file mode 100644 index 00000000000..c87881aa1ac --- /dev/null +++ b/test/MemoryLimitTestCases.test.js @@ -0,0 +1,169 @@ +"use strict"; + +require("./helpers/warmup-webpack"); + +const path = require("path"); +const fs = require("graceful-fs"); +const rimraf = require("rimraf"); +const webpack = require(".."); +const captureStdio = require("./helpers/captureStdio"); +const expectNoDeprecations = require("./helpers/expectNoDeprecations"); + +const toMiB = (/** @type {number} */ bytes) => + `${Math.round(bytes / 1024 / 1024)}MiB`; +const base = path.join(__dirname, "memoryLimitCases"); +const outputBase = path.join(__dirname, "js", "memoryLimit"); +const tests = fs + .readdirSync(base) + .filter( + (testName) => + fs.existsSync(path.join(base, testName, "index.js")) || + fs.existsSync(path.join(base, testName, "webpack.config.js")) + ) + .filter((testName) => { + const testDirectory = path.join(base, testName); + const filterPath = path.join(testDirectory, "test.filter.js"); + if (fs.existsSync(filterPath) && !require(filterPath)()) { + // eslint-disable-next-line jest/no-disabled-tests, jest/valid-describe-callback + describe.skip(testName, () => it("filtered")); + + return false; + } + return true; + }); + +/** @typedef {{ toString(): string, toStringRaw(): string, restore(): void, data: string[], reset(): void }} CapturedStdio */ + +expectNoDeprecations(); + +describe("MemoryLimitTestCases", () => { + jest.setTimeout(40000); + /** @type {CapturedStdio} */ + let stderr; + + beforeEach(() => { + stderr = captureStdio(process.stderr, true); + if (global.gc) { + global.gc(); + global.gc(); + } + }); + + afterEach(() => { + stderr.restore(); + }); + + for (const testName of tests) { + /** @type {{ heapSizeLimitBytes: number, validate?: (stats: import("../").Stats, stderr: string) => void }} */ + let testConfig = { + heapSizeLimitBytes: 250 * 1024 * 1024 + }; + try { + // try to load a test file + testConfig = Object.assign( + testConfig, + require(path.join(base, testName, "test.config.js")) + ); + } catch (_err) { + // ignored + } + const size = toMiB(testConfig.heapSizeLimitBytes); + + // eslint-disable-next-line no-loop-func + it(`should build ${JSON.stringify(testName)} with heap limit of ${size}`, (done) => { + const outputDirectory = path.join(outputBase, testName); + rimraf.sync(outputDirectory); + fs.mkdirSync(outputDirectory, { recursive: true }); + /** @type {import("../").Configuration} */ + let options = { + mode: "development", + entry: "./index", + output: { + filename: "bundle.js" + } + }; + if (fs.existsSync(path.join(base, testName, "webpack.config.js"))) { + options = require(path.join(base, testName, "webpack.config.js")); + } + + const resolvedOptions = /** @type {import("../").Configuration[]} */ ( + Array.isArray(options) ? options : [options] + ); + for (const options of resolvedOptions) { + if (!options.context) options.context = path.join(base, testName); + if (!options.output) options.output = options.output || {}; + if (!options.output.path) options.output.path = outputDirectory; + if (!options.plugins) options.plugins = []; + if (!options.optimization) options.optimization = {}; + if (options.optimization.minimize === undefined) { + options.optimization.minimize = false; + } + } + const heapSizeStart = process.memoryUsage().heapUsed; + const c = webpack(options); + const cAny = /** @type {EXPECTED_ANY} */ (c); + const compilers = /** @type {import("../").Compiler[]} */ ( + cAny.compilers ? cAny.compilers : [c] + ); + for (const c of compilers) { + const ifs = /** @type {NonNullable} */ ( + c.inputFileSystem + ); + c.inputFileSystem = Object.create(ifs); + /** @type {NonNullable} */ ( + c.inputFileSystem + ).readFile = function readFile() { + // eslint-disable-next-line prefer-rest-params + const args = Array.prototype.slice.call(arguments); + const callback = args.pop(); + // eslint-disable-next-line no-useless-call + /** @type {EXPECTED_ANY} */ ( + /** @type {NonNullable} */ (ifs).readFile + ).apply(ifs, [ + ...args, + ( + /** @type {Error | null} */ err, + /** @type {Buffer | undefined} */ result + ) => { + if (err) return callback(err); + if (!/\.(?:js|json|txt)$/.test(args[0])) { + return callback(null, result); + } + callback( + null, + /** @type {Buffer} */ (result) + .toString("utf8") + .replace(/\r/g, "") + ); + } + ]); + }; + } + c.run((err, _stats) => { + if (err) return done(err); + const stats = /** @type {import("../").Stats} */ (_stats); + expect(stats.hasErrors()).toBe(testName.endsWith("error")); + if (!testName.endsWith("error") && stats.hasErrors()) { + return done( + new Error( + stats.toString({ + all: false, + errors: true, + errorStack: true, + errorDetails: true + }) + ) + ); + } + const heapUsed = process.memoryUsage().heapUsed - heapSizeStart; + if (heapUsed > testConfig.heapSizeLimitBytes) { + return done( + new Error(`Out of memory limit with ${toMiB(heapUsed)} heap used`) + ); + } + if (testConfig.validate) testConfig.validate(stats, stderr.toString()); + done(); + }); + }); + } +}); diff --git a/test/ModuleBuildError.unittest.js b/test/ModuleBuildError.unittest.js new file mode 100644 index 00000000000..7f1f9d2e2a3 --- /dev/null +++ b/test/ModuleBuildError.unittest.js @@ -0,0 +1,56 @@ +"use strict"; + +const ModuleBuildError = require("../lib/errors/ModuleBuildError"); + +describe("ModuleBuildError", () => { + it("is an error with the right name", () => { + const err = new ModuleBuildError(new Error("boom")); + expect(err).toBeInstanceOf(Error); + expect(err.name).toBe("ModuleBuildError"); + }); + + it("keeps the V8 stack (which already starts with the message)", () => { + const inner = new Error("v8 boom"); + const err = new ModuleBuildError(inner); + // V8 `.stack` includes the message, so the whole stack is appended. + expect(err.message).toContain("v8 boom"); + expect(err.message).toContain(inner.stack); + }); + + it("leads with `name: message` when the stack omits the message (JSC)", () => { + const err = new ModuleBuildError({ + name: "TypeError", + message: "jsc boom", + stack: "doStuff@file.js:1:1\nglobal code@file.js:2:2" + }); + expect(err.message).toContain("TypeError: jsc boom"); + // The frames-only stack is not appended in this branch. + expect(err.message).not.toContain("file.js:1:1"); + }); + + it("uses just the message when the stack omits it and there is no name", () => { + const err = new ModuleBuildError({ + name: "", + message: "no-name boom", + stack: "@file.js:1:1" + }); + expect(err.message).toContain("no-name boom"); + expect(err.message).not.toContain("TypeError"); + }); + + it("moves the stack to details when hideStack is set", () => { + const err = new ModuleBuildError({ + name: "Error", + message: "hidden boom", + stack: "Error: hidden boom\n at file.js:1:1", + hideStack: true + }); + expect(err.message).toContain("hidden boom"); + expect(err.details).toContain("file.js:1:1"); + }); + + it("prefixes the source with `from` when provided", () => { + const err = new ModuleBuildError(new Error("boom"), { from: "my-loader" }); + expect(err.message).toContain("Module build failed (from my-loader):"); + }); +}); diff --git a/test/ModuleDependencyError.unittest.js b/test/ModuleDependencyError.unittest.js index 3e54fd79c7f..5262416c7a9 100644 --- a/test/ModuleDependencyError.unittest.js +++ b/test/ModuleDependencyError.unittest.js @@ -1,9 +1,10 @@ "use strict"; const path = require("path"); -const ModuleDependencyError = require("../lib/ModuleDependencyError"); +const ModuleDependencyError = require("../lib/errors/ModuleDependencyError"); describe("ModuleDependencyError", () => { + /** @type {{ error?: Error, moduleDependencyError?: InstanceType }} */ let env; beforeEach(() => { @@ -14,9 +15,13 @@ describe("ModuleDependencyError", () => { beforeEach(() => { env.error = new Error("Error Message"); env.moduleDependencyError = new ModuleDependencyError( - "myModule", + /** @type {import("../lib/Module")} */ ( + /** @type {unknown} */ ("myModule") + ), env.error, - "Location" + /** @type {import("../lib/Dependency").DependencyLocation} */ ( + /** @type {unknown} */ ("Location") + ) ); }); @@ -25,29 +30,52 @@ describe("ModuleDependencyError", () => { }); it("has a name property", () => { - expect(env.moduleDependencyError.name).toBe("ModuleDependencyError"); + expect(env.moduleDependencyError).toBeDefined(); + expect( + /** @type {NonNullable} */ ( + env.moduleDependencyError + ).name + ).toBe("ModuleDependencyError"); }); it("has a message property", () => { - expect(env.moduleDependencyError.message).toBe("Error Message"); + expect( + /** @type {NonNullable} */ ( + env.moduleDependencyError + ).message + ).toBe("Error Message"); }); it("has a loc property", () => { - expect(env.moduleDependencyError.loc).toBe("Location"); + expect( + /** @type {NonNullable} */ ( + env.moduleDependencyError + ).loc + ).toBe("Location"); }); it("has a details property", () => { - expect(env.moduleDependencyError.details).toMatch( - path.join("test", "ModuleDependencyError.unittest.js:") - ); + expect( + /** @type {NonNullable} */ ( + env.moduleDependencyError + ).details + ).toMatch(path.join("test", "ModuleDependencyError.unittest.js:")); }); it("has an module property", () => { - expect(env.moduleDependencyError.module).toBe("myModule"); + expect( + /** @type {NonNullable} */ ( + env.moduleDependencyError + ).module + ).toBe("myModule"); }); it("has an error property", () => { - expect(env.moduleDependencyError.error).toBe(env.error); + expect( + /** @type {NonNullable} */ ( + env.moduleDependencyError + ).error + ).toBe(env.error); }); }); }); diff --git a/test/ModuleReason.unittest.js b/test/ModuleReason.unittest.js deleted file mode 100644 index 56a6cc25c01..00000000000 --- a/test/ModuleReason.unittest.js +++ /dev/null @@ -1,60 +0,0 @@ -"use strict"; - -const Module = require("../lib/Module"); -const Chunk = require("../lib/Chunk"); -const Dependency = require("../lib/Dependency"); -const ModuleReason = require("../lib/ModuleReason"); - -describe("ModuleReason", () => { - let myModule; - let myDependency; - let myModuleReason; - let myChunk; - let myChunk2; - - beforeEach(() => { - myModule = new Module(); - myDependency = new Dependency(); - myChunk = new Chunk("chunk-test", "module-test", "loc-test"); - myChunk2 = new Chunk("chunk-test", "module-test", "loc-test"); - - myModuleReason = new ModuleReason(myModule, myDependency); - }); - - describe("hasChunk", () => { - it("returns false when chunk is not present", () => { - expect(myModuleReason.hasChunk(myChunk)).toBe(false); - }); - - it("returns true when chunk is present", () => { - myModuleReason.module.addChunk(myChunk); - expect(myModuleReason.hasChunk(myChunk)).toBe(true); - }); - }); - - describe("rewriteChunks", () => { - it("if old chunk is present, it is replaced with new chunks", () => { - myModuleReason.module.addChunk(myChunk); - myModuleReason.rewriteChunks(myChunk, [myChunk2]); - - expect(myModuleReason.hasChunk(myChunk)).toBe(false); - expect(myModuleReason.hasChunk(myChunk2)).toBe(true); - }); - - it("if old chunk is not present, new chunks are not added", () => { - myModuleReason.rewriteChunks(myChunk, [myChunk2]); - - expect(myModuleReason.hasChunk(myChunk)).toBe(false); - expect(myModuleReason.hasChunk(myChunk2)).toBe(false); - }); - - it("if already rewritten chunk is present, it is replaced with new chunks", () => { - myModuleReason.module.addChunk(myChunk); - myModuleReason.rewriteChunks(myChunk, [myChunk2]); - myModuleReason.rewriteChunks(myChunk2, [myChunk]); - - expect(myModuleReason.hasChunk(myChunk)).toBe(true); - expect(myModuleReason.hasChunk(myChunk2)).toBe(false); - }); - }); -}); diff --git a/test/MultiCompiler.test.js b/test/MultiCompiler.test.js index 92ff2dc6848..272338258aa 100644 --- a/test/MultiCompiler.test.js +++ b/test/MultiCompiler.test.js @@ -1,160 +1,827 @@ "use strict"; -/* globals describe it */ +require("./helpers/warmup-webpack"); + const path = require("path"); -const MemoryFs = require("memory-fs"); -const webpack = require("../"); - -const createMultiCompiler = () => { - const compiler = webpack([ - { - context: path.join(__dirname, "fixtures"), - entry: "./a.js" - }, - { - context: path.join(__dirname, "fixtures"), - entry: "./b.js" - } - ]); - compiler.outputFileSystem = new MemoryFs(); +const { Volume, createFsFromVolume } = require("memfs"); +const webpack = require(".."); +const expectNoDeprecations = require("./helpers/expectNoDeprecations"); + +/** + * @param {import("../").MultiCompilerOptions=} options options + * @returns {import("../").MultiCompiler} compiler + */ +const createMultiCompiler = (options) => { + const compiler = /** @type {import("../").MultiCompiler} */ ( + webpack( + /** @type {import("../").MultiConfiguration} */ ( + Object.assign( + [ + { + name: "a", + context: path.join(__dirname, "fixtures"), + entry: "./a.js" + }, + { + name: "b", + context: path.join(__dirname, "fixtures"), + entry: "./b.js" + } + ], + options + ) + ) + ) + ); + compiler.outputFileSystem = /** @type {import("../").OutputFileSystem} */ ( + /** @type {unknown} */ (createFsFromVolume(new Volume())) + ); + compiler.watchFileSystem = + /** @type {import("../lib/util/fs").WatchFileSystem} */ ({ + watch: (_a, _b, _c, _d, _e, _f, _g) => + /** @type {import("../lib/util/fs").Watcher} */ ( + /** @type {unknown} */ (undefined) + ) + }); return compiler; }; -describe("MultiCompiler", function() { - jest.setTimeout(20000); +describe("MultiCompiler", () => { + expectNoDeprecations(); - it("should trigger 'run' for each child compiler", done => { + it("should trigger 'run' for each child compiler", (done) => { const compiler = createMultiCompiler(); let called = 0; compiler.hooks.run.tap("MultiCompiler test", () => called++); - compiler.run(err => { + compiler.run((err) => { if (err) { throw err; - } else { - expect(called).toBe(2); - done(); } + expect(called).toBe(2); + compiler.close(done); }); }); - it("should trigger 'watchRun' for each child compiler", done => { + it("should trigger 'watchRun' for each child compiler", (done) => { const compiler = createMultiCompiler(); let called = 0; compiler.hooks.watchRun.tap("MultiCompiler test", () => called++); - const watcher = compiler.watch(1000, err => { - if (err) { - throw err; - } else { - watcher.close(); + compiler.watch( + /** @type {import("../declarations/WebpackOptions").WatchOptions} */ ( + /** @type {unknown} */ (1000) + ), + (err) => { + if (err) { + throw err; + } expect(called).toBe(2); - done(); + compiler.close(done); } - }); + ); }); - it("should not be run twice at a time (run)", function(done) { + it("should not be running twice at a time (run)", (done) => { const compiler = createMultiCompiler(); - compiler.run((err, stats) => { + compiler.run((err, _stats) => { if (err) return done(err); }); - compiler.run((err, stats) => { - if (err) return done(); + compiler.run((err, _stats) => { + if (err) { + compiler.close(done); + } }); }); - it("should not be run twice at a time (watch)", function(done) { + + it("should not be running twice at a time (watch)", (done) => { const compiler = createMultiCompiler(); - const watcher = compiler.watch({}, (err, stats) => { + compiler.watch({}, (err, _stats) => { if (err) return done(err); }); - compiler.watch({}, (err, stats) => { - if (err) return watcher.close(done); + compiler.watch({}, (err, _stats) => { + if (err) { + compiler.close(done); + } }); }); - it("should not be run twice at a time (run - watch)", function(done) { + + it("should not be running twice at a time (run - watch)", (done) => { const compiler = createMultiCompiler(); - compiler.run((err, stats) => { + compiler.run((err, _stats) => { if (err) return done(err); }); - compiler.watch({}, (err, stats) => { - if (err) return done(); + compiler.watch({}, (err, _stats) => { + if (err) { + compiler.close(done); + } }); }); - it("should not be run twice at a time (watch - run)", function(done) { + + it("should not be running twice at a time (watch - run)", (done) => { const compiler = createMultiCompiler(); - let watcher; - watcher = compiler.watch({}, (err, stats) => { + compiler.watch({}, (err, _stats) => { if (err) return done(err); }); - compiler.run((err, stats) => { - if (err) return watcher.close(done); - }); - }); - it("should not be run twice at a time (instance cb)", function(done) { - const compiler = webpack( - { - context: __dirname, - mode: "production", - entry: "./c", - output: { - path: "/", - filename: "bundle.js" - } - }, - () => {} + compiler.run((err, _stats) => { + if (err) { + compiler.close(done); + } + }); + }); + + it("should not be running twice at a time (instance cb)", (done) => { + const compiler = /** @type {import("../").Compiler} */ ( + webpack( + { + context: __dirname, + mode: "production", + entry: "./c", + output: { + path: "/", + filename: "bundle.js" + } + }, + () => {} + ) ); - compiler.outputFileSystem = new MemoryFs(); - compiler.run((err, stats) => { - if (err) return done(); + compiler.outputFileSystem = /** @type {import("../").OutputFileSystem} */ ( + /** @type {unknown} */ (createFsFromVolume(new Volume())) + ); + compiler.run((err, _stats) => { + if (err) { + compiler.close(done); + } }); }); - it("should run again correctly after first compilation", function(done) { + + it("should run again correctly after first compilation", (done) => { const compiler = createMultiCompiler(); - compiler.run((err, stats) => { + compiler.run((err, _stats) => { if (err) return done(err); - compiler.run((err, stats) => { + compiler.run((err, _stats) => { if (err) return done(err); - done(); + compiler.close(done); }); }); }); - it("should watch again correctly after first compilation", function(done) { + + it("should release per-child compilation memory as each child finishes (#15521)", (done) => { const compiler = createMultiCompiler(); compiler.run((err, stats) => { if (err) return done(err); + for (const childStats of /** @type {import("../").MultiStats} */ (stats) + .stats) { + const compilation = childStats.compilation; + // codeGenerationResults: only used during seal/emit, dropped. + expect( + /** @type {import("../").CodeGenerationResults} */ ( + compilation.codeGenerationResults + ).map.size + ).toBe(0); + // Stats must still be usable on the slimmed compilation. + expect(typeof childStats.toJson().hash).toBe("string"); + } + compiler.close(done); + }); + }); - let watcher; - watcher = compiler.watch({}, (err, stats) => { - if (err) return done(err); - watcher.close(done); + it("should release a finished child's codeGenerationResults before a dependent sibling runs (#15521)", (done) => { + const compiler = /** @type {import("../").MultiCompiler} */ ( + webpack( + /** @type {import("../").MultiConfiguration} */ ( + Object.assign( + [ + { + name: "a", + context: path.join(__dirname, "fixtures"), + entry: "./a.js" + }, + { + name: "b", + context: path.join(__dirname, "fixtures"), + entry: "./b.js", + dependencies: ["a"] + } + ], + { parallelism: 1 } + ) + ) + ) + ); + compiler.outputFileSystem = /** @type {import("../").OutputFileSystem} */ ( + /** @type {unknown} */ (createFsFromVolume(new Volume())) + ); + compiler.watchFileSystem = + /** @type {import("../lib/util/fs").WatchFileSystem} */ ({ + watch: (_a, _b, _c, _d, _e, _f, _g) => + /** @type {import("../lib/util/fs").Watcher} */ ( + /** @type {unknown} */ (undefined) + ) }); + const [a, b] = compiler.compilers; + /** @type {import("../").Compilation | undefined} */ + let aCompilation; + a.hooks.done.tap("test", (stats) => { + aCompilation = stats.compilation; + }); + // With dependencies + parallelism 1, b only starts after a is fully + // done (including a's afterDone release tap). Capture a's map size at + // that point: it must already be cleared while b is about to build. + /** @type {number | undefined} */ + let aMapSizeWhenBStarts; + b.hooks.run.tap("test", () => { + aMapSizeWhenBStarts = /** @type {import("../").CodeGenerationResults} */ ( + /** @type {import("../").Compilation} */ (aCompilation) + .codeGenerationResults + ).map.size; + }); + compiler.run((err) => { + if (err) return done(err); + expect(aMapSizeWhenBStarts).toBe(0); + compiler.close(done); }); }); - it("should run again correctly after first closed watch", function(done) { + + it("should watch again correctly after first compilation", (done) => { const compiler = createMultiCompiler(); - const watching = compiler.watch({}, (err, stats) => { + compiler.run((err, _stats) => { if (err) return done(err); + + compiler.watch({}, (err, _stats) => { + if (err) return done(err); + compiler.close(done); + }); }); + }); + + it("should run again correctly after first closed watch", (done) => { + const compiler = createMultiCompiler(); + const watching = /** @type {import("../lib/MultiWatching")} */ ( + /** @type {unknown} */ ( + compiler.watch({}, (err, _stats) => { + if (err) return done(err); + }) + ) + ); watching.close(() => { - compiler.run((err, stats) => { + compiler.run((err, _stats) => { if (err) return done(err); - done(); + compiler.close(done); }); }); }); - it("should watch again correctly after first closed watch", function(done) { + + it("should watch again correctly after first closed watch", (done) => { const compiler = createMultiCompiler(); - const watching = compiler.watch({}, (err, stats) => { - if (err) return done(err); - }); + const watching = /** @type {import("../lib/MultiWatching")} */ ( + /** @type {unknown} */ ( + compiler.watch({}, (err, _stats) => { + if (err) return done(err); + }) + ) + ); watching.close(() => { - let watcher; - watcher = compiler.watch({}, (err, stats) => { + compiler.watch({}, (err, _stats) => { if (err) return done(err); - watcher.close(done); + compiler.close(done); }); }); }); + + it("should respect parallelism and dependencies for running", (done) => { + const compiler = createMultiCompiler( + /** @type {import("../").MultiCompilerOptions} */ ({ + parallelism: 1, + 2: { + name: "c", + context: path.join(__dirname, "fixtures"), + entry: "./a.js", + dependencies: ["d", "e"] + }, + 3: { + name: "d", + context: path.join(__dirname, "fixtures"), + entry: "./a.js" + }, + 4: { + name: "e", + context: path.join(__dirname, "fixtures"), + entry: "./a.js" + } + }) + ); + /** @type {string[]} */ + const events = []; + for (const c of compiler.compilers) { + c.hooks.run.tap("test", () => { + events.push(`${c.name} run`); + }); + c.hooks.done.tap("test", () => { + events.push(`${c.name} done`); + }); + } + compiler.run((_err, _stats) => { + expect(events.join(" ")).toBe( + "a run a done b run b done d run d done e run e done c run c done" + ); + compiler.close(done); + }); + }); + + it("should respect parallelism and dependencies for watching", (done) => { + const compiler = /** @type {import("../").MultiCompiler} */ ( + webpack( + /** @type {import("../").MultiConfiguration} */ ( + Object.assign( + [ + { + name: "a", + mode: "development", + context: path.join(__dirname, "fixtures"), + entry: "./a.js", + dependencies: ["b", "c"] + }, + { + name: "b", + mode: "development", + context: path.join(__dirname, "fixtures"), + entry: "./b.js" + }, + { + name: "c", + mode: "development", + context: path.join(__dirname, "fixtures"), + entry: "./a.js" + } + ], + { parallelism: 1 } + ) + ) + ) + ); + compiler.outputFileSystem = /** @type {import("../").OutputFileSystem} */ ( + /** @type {unknown} */ (createFsFromVolume(new Volume())) + ); + /** @type {((...args: EXPECTED_ANY[]) => void)[]} */ + const watchCallbacks = []; + /** @type {((...args: EXPECTED_ANY[]) => void)[]} */ + const watchCallbacksUndelayed = []; + compiler.watchFileSystem = + /** @type {import("../lib/util/fs").WatchFileSystem} */ ({ + watch( + files, + directories, + missing, + startTime, + options, + callback, + callbackUndelayed + ) { + watchCallbacks.push(callback); + watchCallbacksUndelayed.push(callbackUndelayed); + return /** @type {import("../lib/util/fs").Watcher} */ ( + /** @type {unknown} */ (undefined) + ); + } + }); + /** @type {string[]} */ + const events = []; + for (const c of compiler.compilers) { + c.hooks.invalid.tap("test", () => { + events.push(`${c.name} invalid`); + }); + c.hooks.watchRun.tap("test", () => { + events.push(`${c.name} run`); + }); + c.hooks.done.tap("test", () => { + events.push(`${c.name} done`); + }); + } + + let update = 0; + compiler.watch({}, (err, stats) => { + if (err) return done(err); + const info = () => + /** @type {import("../").MultiStats} */ (stats).toString({ + preset: "summary", + version: false + }); + switch (update++) { + case 0: + expect(info()).toMatchInlineSnapshot(` + "a: + a compiled successfully + + b: + b compiled successfully + + c: + c compiled successfully" + `); + expect(compiler.compilers[0].modifiedFiles).toBeUndefined(); + expect(compiler.compilers[0].removedFiles).toBeUndefined(); + expect(events).toMatchInlineSnapshot(` + Array [ + "b run", + "b done", + "c run", + "c done", + "a run", + "a done", + ] + `); + events.length = 0; + // wait until watching begins + setTimeout(() => { + watchCallbacksUndelayed[0](); + watchCallbacks[0](null, new Map(), new Map(), new Set(), new Set()); + }, 100); + break; + case 1: + expect(info()).toMatchInlineSnapshot(` + "a: + a compiled successfully + + b: + b compiled successfully" + `); + expect(compiler.compilers[1].modifiedFiles).toEqual(new Set()); + expect(compiler.compilers[1].removedFiles).toEqual(new Set()); + expect(events).toMatchInlineSnapshot(` + Array [ + "b invalid", + "b run", + "b done", + "a invalid", + "a run", + "a done", + ] + `); + watchCallbacksUndelayed[2](); + watchCallbacks[2](null, new Map(), new Map(), new Set(), new Set()); + break; + case 2: + expect(info()).toMatchInlineSnapshot(` + "a: + a compiled successfully" + `); + expect(events).toMatchInlineSnapshot(` + Array [ + "b invalid", + "b run", + "b done", + "a invalid", + "a run", + "a done", + "a invalid", + "a run", + "a done", + ] + `); + events.length = 0; + watchCallbacksUndelayed[0](); + watchCallbacksUndelayed[1](); + watchCallbacks[0](null, new Map(), new Map(), new Set(), new Set()); + watchCallbacks[1](null, new Map(), new Map(), new Set(), new Set()); + break; + case 3: + expect(info()).toMatchInlineSnapshot(` + "a: + a compiled successfully + + b: + b compiled successfully + + c: + c compiled successfully" + `); + expect(events).toMatchInlineSnapshot(` + Array [ + "b invalid", + "c invalid", + "b run", + "b done", + "c run", + "c done", + "a invalid", + "a run", + "a done", + ] + `); + events.length = 0; + compiler.close(done); + break; + default: + done(new Error("unexpected")); + } + }); + }); + + it("should respect parallelism when using invalidate", (done) => { + const compiler = /** @type {import("../").MultiCompiler} */ ( + webpack( + /** @type {import("../").MultiConfiguration} */ ( + /** @type {unknown} */ ( + Object.assign( + [ + { + name: "a", + mode: "development", + entry: { a: "./a.js" }, + context: path.join(__dirname, "fixtures") + }, + { + name: "b", + mode: "development", + entry: { b: "./b.js" }, + context: path.join(__dirname, "fixtures") + } + ], + { parallelism: 1 } + ) + ) + ) + ) + ); + + /** @type {string[]} */ + const events = []; + for (const c of compiler.compilers) { + c.hooks.invalid.tap("test", () => { + events.push(`${c.name} invalid`); + }); + c.hooks.watchRun.tap("test", () => { + events.push(`${c.name} run`); + }); + c.hooks.done.tap("test", () => { + events.push(`${c.name} done`); + }); + } + + compiler.watchFileSystem = { + watch: /** @type {import("../lib/util/fs").WatchMethod} */ ( + /** @type {unknown} */ (/** @type {() => void} */ (() => {})) + ) + }; + compiler.outputFileSystem = /** @type {import("../").OutputFileSystem} */ ( + /** @type {unknown} */ (createFsFromVolume(new Volume())) + ); + + let state = 0; + const watching = /** @type {import("../lib/MultiWatching")} */ ( + /** @type {unknown} */ ( + compiler.watch({}, (error) => { + if (error) { + done(error); + return; + } + if (state !== 0) return; + state++; + + expect(events).toMatchInlineSnapshot(` + Array [ + "a run", + "a done", + "b run", + "b done", + ] + `); + events.length = 0; + + watching.invalidate((err) => { + try { + if (err) return done(err); + + expect(events).toMatchInlineSnapshot(` + Array [ + "a invalid", + "b invalid", + "a run", + "a done", + "b run", + "b done", + ] + `); + events.length = 0; + expect(state).toBe(1); + setTimeout(() => { + compiler.close(done); + }, 1000); + } catch (err) { + console.error(err); + done(err); + } + }); + }) + ) + ); + }, 2000); + + it("should respect dependencies when using invalidate", (done) => { + const compiler = /** @type {import("../").MultiCompiler} */ ( + webpack([ + { + name: "a", + mode: "development", + entry: { a: "./a.js" }, + context: path.join(__dirname, "fixtures"), + dependencies: ["b"] + }, + { + name: "b", + mode: "development", + entry: { b: "./b.js" }, + context: path.join(__dirname, "fixtures") + } + ]) + ); + + /** @type {string[]} */ + const events = []; + for (const c of compiler.compilers) { + c.hooks.invalid.tap("test", () => { + events.push(`${c.name} invalid`); + }); + c.hooks.watchRun.tap("test", () => { + events.push(`${c.name} run`); + }); + c.hooks.done.tap("test", () => { + events.push(`${c.name} done`); + }); + } + + compiler.watchFileSystem = { + watch: /** @type {import("../lib/util/fs").WatchMethod} */ ( + /** @type {unknown} */ (/** @type {() => void} */ (() => {})) + ) + }; + compiler.outputFileSystem = /** @type {import("../").OutputFileSystem} */ ( + /** @type {unknown} */ (createFsFromVolume(new Volume())) + ); + + let state = 0; + const watching = /** @type {import("../lib/MultiWatching")} */ ( + /** @type {unknown} */ ( + compiler.watch({}, (error) => { + if (error) { + done(error); + return; + } + if (state !== 0) return; + state++; + + expect(events).toMatchInlineSnapshot(` + Array [ + "b run", + "b done", + "a run", + "a done", + ] + `); + events.length = 0; + + watching.invalidate((err) => { + try { + if (err) return done(err); + + expect(events).toMatchInlineSnapshot(` + Array [ + "a invalid", + "b invalid", + "b run", + "b done", + "a run", + "a done", + ] + `); + events.length = 0; + expect(state).toBe(1); + setTimeout(() => { + compiler.close(done); + }, 1000); + } catch (err) { + console.error(err); + done(err); + } + }); + }) + ) + ); + }, 2000); + + it("shouldn't hang when invalidating watchers", (done) => { + const entriesA = /** @type {Record} */ ({ a: "./a.js" }); + const entriesB = /** @type {Record} */ ({ b: "./b.js" }); + const compiler = /** @type {import("../").MultiCompiler} */ ( + webpack([ + { + name: "a", + mode: "development", + entry: () => entriesA, + context: path.join(__dirname, "fixtures") + }, + { + name: "b", + mode: "development", + entry: () => entriesB, + context: path.join(__dirname, "fixtures") + } + ]) + ); + + compiler.watchFileSystem = { + watch: /** @type {import("../lib/util/fs").WatchMethod} */ ( + /** @type {unknown} */ (/** @type {() => void} */ (() => {})) + ) + }; + compiler.outputFileSystem = /** @type {import("../").OutputFileSystem} */ ( + /** @type {unknown} */ (createFsFromVolume(new Volume())) + ); + + const watching = /** @type {import("../lib/MultiWatching")} */ ( + /** @type {unknown} */ ( + compiler.watch({}, (error) => { + if (error) { + done(error); + return; + } + + entriesA.b = "./b.js"; + entriesB.a = "./a.js"; + + watching.invalidate((err) => { + if (err) return done(err); + compiler.close(done); + }); + }) + ) + ); + }, 2000); + + it("shouldn't hang when invalidating during build", (done) => { + const compiler = /** @type {import("../").MultiCompiler} */ ( + webpack( + /** @type {import("../").MultiConfiguration} */ ( + Object.assign([ + { + name: "a", + mode: "development", + context: path.join(__dirname, "fixtures"), + entry: "./a.js" + }, + { + name: "b", + mode: "development", + context: path.join(__dirname, "fixtures"), + entry: "./b.js", + dependencies: ["a"] + } + ]) + ) + ) + ); + compiler.outputFileSystem = /** @type {import("../").OutputFileSystem} */ ( + /** @type {unknown} */ (createFsFromVolume(new Volume())) + ); + /** @type {((...args: EXPECTED_ANY[]) => void)[]} */ + const watchCallbacks = []; + /** @type {((...args: EXPECTED_ANY[]) => void)[]} */ + const watchCallbacksUndelayed = []; + let firstRun = true; + compiler.watchFileSystem = + /** @type {import("../lib/util/fs").WatchFileSystem} */ ({ + watch( + files, + directories, + missing, + startTime, + options, + callback, + callbackUndelayed + ) { + watchCallbacks.push(callback); + watchCallbacksUndelayed.push(callbackUndelayed); + if ( + firstRun && + /** @type {Set} */ (files).has( + path.join(__dirname, "fixtures", "a.js") + ) + ) { + process.nextTick(() => { + callback(null, new Map(), new Map(), new Set(), new Set()); + }); + firstRun = false; + } + return /** @type {import("../lib/util/fs").Watcher} */ ( + /** @type {unknown} */ (undefined) + ); + } + }); + compiler.watch({}, (err, _stats) => { + if (err) return done(err); + compiler.close(done); + }); + }); }); diff --git a/test/MultiItemCache.unittest.js b/test/MultiItemCache.unittest.js new file mode 100644 index 00000000000..6577f10914a --- /dev/null +++ b/test/MultiItemCache.unittest.js @@ -0,0 +1,81 @@ +"use strict"; + +const Cache = require("../lib/Cache"); +const { ItemCacheFacade, MultiItemCache } = require("../lib/CacheFacade"); + +// TODO JSC (Bun) words the "not a function" TypeError differently than V8. +const itSkipBun = process.versions.bun ? it.skip : it; + +describe("MultiItemCache", () => { + itSkipBun("throws when getting items from an empty Cache", () => { + const multiItemCache = new MultiItemCache(generateItemCaches(0)); + expect(() => + multiItemCache.get( + /** @type {EXPECTED_ANY} */ ( + (/** @type {unknown} */ _) => /** @type {() => unknown} */ (_)() + ) + ) + ).toThrow(/_ is not a function/); + }); + + it("returns the single ItemCacheFacade when passed an array of length 1", () => { + const itemCaches = generateItemCaches(1); + const multiItemCache = new MultiItemCache(itemCaches); + expect(multiItemCache).toBe(itemCaches[0]); + }); + + it("retrieves items from the underlying Cache when get is called", () => { + const itemCaches = generateItemCaches(10); + const multiItemCache = new MultiItemCache(itemCaches); + const callback = ( + /** @type {Error | null | undefined} */ err, + /** @type {unknown} */ res + ) => { + expect(err).toBeNull(); + expect(res).toBeInstanceOf(Object); + }; + for (let i = 0; i < 10; ++i) { + multiItemCache.get(callback); + } + }); + + it("can get() a large number of items without exhausting the stack", () => { + const itemCaches = generateItemCaches(10000, () => undefined); + const multiItemCache = new MultiItemCache(itemCaches); + let callbacks = 0; + const callback = ( + /** @type {Error | null | undefined} */ err, + /** @type {unknown} */ res + ) => { + expect(err).toBeNull(); + expect(res).toBeUndefined(); + ++callbacks; + }; + multiItemCache.get(callback); + expect(callbacks).toBe(1); + }); + + /** + * @param {number} howMany how many generation + * @param {() => EXPECTED_ANY=} dataGenerator data generator fn + * @returns {EXPECTED_ANY[]} cache facades + */ + function generateItemCaches(howMany, dataGenerator) { + const ret = []; + for (let i = 0; i < howMany; ++i) { + const name = `ItemCache${i}`; + const tag = `ItemTag${i}`; + const dataGen = dataGenerator || (() => ({ name: tag })); + const cache = new Cache(); + cache.hooks.get.tapAsync( + "DataReturner", + (_identifier, _etag, _gotHandlers, callback) => { + callback(undefined, dataGen()); + } + ); + const itemCache = new ItemCacheFacade(cache, name, tag); + ret[i] = itemCache; + } + return ret; + } +}); diff --git a/test/MultiStats.test.js b/test/MultiStats.test.js new file mode 100644 index 00000000000..3a637c4c6fa --- /dev/null +++ b/test/MultiStats.test.js @@ -0,0 +1,323 @@ +"use strict"; + +require("./helpers/warmup-webpack"); + +const { Volume, createFsFromVolume } = require("memfs"); +const expectNoDeprecations = require("./helpers/expectNoDeprecations"); + +/** + * @param {import("../").Configuration | import("../").MultiConfiguration} options options + * @returns {Promise} stats + */ +const compile = (options) => + /** @type {Promise} */ ( + new Promise((resolve, reject) => { + const webpack = require(".."); + + const compiler = /** @type {import("../").MultiCompiler} */ ( + /** @type {unknown} */ (webpack(/** @type {EXPECTED_ANY} */ (options))) + ); + compiler.outputFileSystem = /** @type {EXPECTED_ANY} */ ( + createFsFromVolume(new Volume()) + ); + compiler.run((err, stats) => { + if (err) { + reject(err); + } else { + resolve(/** @type {import("../").MultiStats} */ (stats)); + } + }); + }) + ); + +describe("MultiStats", () => { + expectNoDeprecations(); + + it("should create JSON of children stats", async () => { + const stats = await compile([ + { + context: __dirname, + entry: "./fixtures/a" + }, + { + context: __dirname, + entry: "./fixtures/b" + } + ]); + + const statsObject = stats.toJson(); + expect(statsObject).toEqual( + expect.objectContaining({ children: expect.any(Array) }) + ); + expect(statsObject.children).toHaveLength(2); + }); + + it("should work with a boolean value", async () => { + const stats = await compile([ + { + context: __dirname, + entry: "./fixtures/a" + }, + { + context: __dirname, + entry: "./fixtures/b" + } + ]); + + expect(stats.toJson(false)).toMatchInlineSnapshot(` + Object { + "children": Array [ + Object { + "name": undefined, + }, + Object { + "name": undefined, + }, + ], + } + `); + expect(stats.toString(false)).toMatchInlineSnapshot('""'); + }); + + it("should work with a string value", async () => { + const stats = await compile([ + { + context: __dirname, + entry: "./fixtures/a" + }, + { + context: __dirname, + entry: "./fixtures/b" + } + ]); + + expect(stats.toJson("none")).toMatchInlineSnapshot(` + Object { + "children": Array [ + Object { + "name": undefined, + }, + Object { + "name": undefined, + }, + ], + } + `); + expect(stats.toString("none")).toMatchInlineSnapshot('""'); + }); + + it("should work with an object value", async () => { + const stats = await compile([ + { + context: __dirname, + entry: "./fixtures/a" + }, + { + context: __dirname, + entry: "./fixtures/b" + } + ]); + + expect( + stats.toJson({ + all: false, + version: false, + errorsCount: true, + warningsCount: true + }) + ).toMatchInlineSnapshot(` + Object { + "children": Array [ + Object { + "errorsCount": 0, + "name": undefined, + "warningsCount": 1, + }, + Object { + "errorsCount": 0, + "name": undefined, + "warningsCount": 1, + }, + ], + "errorsCount": 0, + "warningsCount": 2, + } + `); + expect( + stats.toString({ + all: false, + version: false, + errorsCount: true, + warningsCount: true + }) + ).toMatchInlineSnapshot(` + "webpack compiled with 1 warning + + webpack compiled with 1 warning" + `); + }); + + it("should work with a boolean value for each children", async () => { + const stats = await compile([ + { + context: __dirname, + entry: "./fixtures/a" + }, + { + context: __dirname, + entry: "./fixtures/b" + } + ]); + + const statsOptions = { + children: [false, false] + }; + + expect(stats.toJson(statsOptions)).toMatchInlineSnapshot(` + Object { + "children": Array [ + Object { + "name": undefined, + }, + Object { + "name": undefined, + }, + ], + } + `); + expect(stats.toString(statsOptions)).toMatchInlineSnapshot('""'); + }); + + it("should work with a string value for each children", async () => { + const stats = await compile([ + { + context: __dirname, + entry: "./fixtures/a" + }, + { + context: __dirname, + entry: "./fixtures/b" + } + ]); + + const statsOptions = /** @type {import("../").StatsOptions} */ ( + /** @type {unknown} */ ({ + children: ["none", "none"] + }) + ); + + expect(stats.toJson(statsOptions)).toMatchInlineSnapshot(` + Object { + "children": Array [ + Object { + "name": undefined, + }, + Object { + "name": undefined, + }, + ], + } + `); + expect(stats.toString(statsOptions)).toMatchInlineSnapshot('""'); + }); + + it("should work with an object value for each children", async () => { + const stats = await compile([ + { + context: __dirname, + entry: "./fixtures/a" + }, + { + context: __dirname, + entry: "./fixtures/b" + } + ]); + + const statsOptions = { + children: [ + { + all: false, + publicPath: true, + version: false, + errorsCount: true, + warningsCount: true + }, + { + all: false, + version: false, + errorsCount: true, + warningsCount: true + } + ] + }; + + expect(stats.toJson(statsOptions)).toMatchInlineSnapshot(` + Object { + "children": Array [ + Object { + "errorsCount": 0, + "name": undefined, + "publicPath": "auto", + "warningsCount": 1, + }, + Object { + "errorsCount": 0, + "name": undefined, + "warningsCount": 1, + }, + ], + "errorsCount": 0, + "warningsCount": 2, + } + `); + expect(stats.toString(statsOptions)).toMatchInlineSnapshot(` + "PublicPath: auto + webpack compiled with 1 warning + + webpack compiled with 1 warning" + `); + }); + + it("should work with an mixed values for each children", async () => { + const stats = await compile([ + { + context: __dirname, + entry: "./fixtures/a" + }, + { + context: __dirname, + entry: "./fixtures/b" + } + ]); + + const statsOptions = { + children: [ + false, + { + all: false, + version: false, + errorsCount: true, + warningsCount: true + } + ] + }; + + expect(stats.toJson(statsOptions)).toMatchInlineSnapshot(` + Object { + "children": Array [ + Object { + "name": undefined, + }, + Object { + "errorsCount": 0, + "name": undefined, + "warningsCount": 1, + }, + ], + } + `); + expect(stats.toString(statsOptions)).toMatchInlineSnapshot( + '"webpack compiled with 1 warning"' + ); + }); +}); diff --git a/test/MultiStats.unittest.js b/test/MultiStats.unittest.js deleted file mode 100644 index 6137dc3c6c5..00000000000 --- a/test/MultiStats.unittest.js +++ /dev/null @@ -1,265 +0,0 @@ -"use strict"; - -const packageJSON = require("../package.json"); -const MultiStats = require("../lib/MultiStats"); - -const createStat = overrides => { - return Object.assign( - { - hash: "foo", - compilation: { - name: "bar" - }, - hasErrors: () => false, - hasWarnings: () => false, - toJson: () => - Object.assign( - { - hash: "foo", - version: "version", - warnings: [], - errors: [] - }, - overrides - ) - }, - overrides - ); -}; - -describe("MultiStats", () => { - let packageVersion, stats, myMultiStats, result; - - beforeEach(() => { - packageVersion = packageJSON.version; - packageJSON.version = "1.2.3"; - }); - - afterEach(() => { - packageJSON.version = packageVersion; - }); - - describe("created", () => { - beforeEach(() => { - stats = [ - createStat({ - hash: "abc123" - }), - createStat({ - hash: "xyz890" - }) - ]; - myMultiStats = new MultiStats(stats); - }); - - it("creates a hash string", () => { - expect(myMultiStats.hash).toBe("abc123xyz890"); - }); - }); - - describe("hasErrors", () => { - describe("when both have errors", () => { - beforeEach(() => { - stats = [ - createStat({ - hasErrors: () => true - }), - createStat({ - hasErrors: () => true - }) - ]; - myMultiStats = new MultiStats(stats); - }); - - it("returns true", () => { - expect(myMultiStats.hasErrors()).toBe(true); - }); - }); - - describe("when one has an error", () => { - beforeEach(() => { - stats = [ - createStat({ - hasErrors: () => true - }), - createStat() - ]; - myMultiStats = new MultiStats(stats); - }); - - it("returns true", () => { - expect(myMultiStats.hasErrors()).toBe(true); - }); - }); - - describe("when none have errors", () => { - beforeEach(() => { - stats = [createStat(), createStat()]; - myMultiStats = new MultiStats(stats); - }); - - it("returns false", () => { - expect(myMultiStats.hasErrors()).toBe(false); - }); - }); - }); - - describe("hasWarnings", () => { - describe("when both have warnings", () => { - beforeEach(() => { - stats = [ - createStat({ - hasWarnings: () => true - }), - createStat({ - hasWarnings: () => true - }) - ]; - myMultiStats = new MultiStats(stats); - }); - - it("returns true", () => { - expect(myMultiStats.hasWarnings()).toBe(true); - }); - }); - - describe("when one has a warning", () => { - beforeEach(() => { - stats = [ - createStat({ - hasWarnings: () => true - }), - createStat() - ]; - myMultiStats = new MultiStats(stats); - }); - - it("returns true", () => { - expect(myMultiStats.hasWarnings()).toBe(true); - }); - }); - - describe("when none have warnings", () => { - beforeEach(() => { - stats = [createStat(), createStat()]; - myMultiStats = new MultiStats(stats); - }); - - it("returns false", () => { - expect(myMultiStats.hasWarnings()).toBe(false); - }); - }); - }); - - describe("toJson", () => { - beforeEach(() => { - stats = [ - createStat({ - hash: "abc123", - compilation: { - name: "abc123-compilation" - }, - toJson: () => ({ - warnings: ["abc123-warning"], - errors: ["abc123-error"] - }) - }), - createStat({ - hash: "xyz890", - compilation: { - name: "xyz890-compilation" - }, - toJson: () => ({ - warnings: ["xyz890-warning-1", "xyz890-warning-2"], - errors: [] - }) - }) - ]; - }); - - it("returns plain object representation", () => { - myMultiStats = new MultiStats(stats); - result = myMultiStats.toJson({ - version: false, - hash: false - }); - expect(result).toEqual({ - errors: ["(abc123-compilation) abc123-error"], - warnings: [ - "(abc123-compilation) abc123-warning", - "(xyz890-compilation) xyz890-warning-1", - "(xyz890-compilation) xyz890-warning-2" - ], - children: [ - { - errors: ["abc123-error"], - name: "abc123-compilation", - warnings: ["abc123-warning"] - }, - { - errors: [], - name: "xyz890-compilation", - warnings: ["xyz890-warning-1", "xyz890-warning-2"] - } - ] - }); - }); - - it("returns plain object representation with json set to true", () => { - myMultiStats = new MultiStats(stats); - result = myMultiStats.toJson(true); - expect(result).toEqual({ - errors: ["(abc123-compilation) abc123-error"], - warnings: [ - "(abc123-compilation) abc123-warning", - "(xyz890-compilation) xyz890-warning-1", - "(xyz890-compilation) xyz890-warning-2" - ], - children: [ - { - warnings: ["abc123-warning"], - errors: ["abc123-error"], - name: "abc123-compilation" - }, - { - warnings: ["xyz890-warning-1", "xyz890-warning-2"], - errors: [], - name: "xyz890-compilation" - } - ] - }); - }); - }); - - describe("toString", () => { - beforeEach(() => { - stats = [ - createStat({ - hash: "abc123", - compilation: { - name: "abc123-compilation" - } - }), - createStat({ - hash: "xyz890", - compilation: { - name: "xyz890-compilation" - } - }) - ]; - myMultiStats = new MultiStats(stats); - result = myMultiStats.toString(); - }); - - it("returns string representation", () => { - expect(result).toEqual( - "Hash: abc123xyz890\n" + - "Version: webpack 1.2.3\n" + - "Child abc123-compilation:\n" + - " Hash: abc123\n" + - "Child xyz890-compilation:\n" + - " Hash: xyz890" - ); - }); - }); -}); diff --git a/test/MultiWatching.unittest.js b/test/MultiWatching.unittest.js index 0977f3cfad6..fe23f298db4 100644 --- a/test/MultiWatching.unittest.js +++ b/test/MultiWatching.unittest.js @@ -1,29 +1,42 @@ "use strict"; -const Tapable = require("tapable").Tapable; const SyncHook = require("tapable").SyncHook; const MultiWatching = require("../lib/MultiWatching"); -const createWatching = () => { - return { - invalidate: jest.fn(), - close: jest.fn() - }; -}; +/** @typedef {import("../lib/Watching")} Watching */ +/** @typedef {import("../lib/MultiCompiler")} MultiCompiler */ +/** + * @returns {Watching} watching + */ +const createWatching = () => + /** @type {Watching} */ ( + /** @type {unknown} */ ({ + invalidate: jest.fn(), + suspend: jest.fn(), + resume: jest.fn(), + close: jest.fn() + }) + ); + +/** + * @returns {MultiCompiler} compiler + */ const createCompiler = () => { const compiler = { hooks: { watchClose: new SyncHook([]) } }; - Tapable.addCompatLayer(compiler); - return compiler; + return /** @type {MultiCompiler} */ (compiler); }; describe("MultiWatching", () => { + /** @type {Watching[]} */ let watchings; + /** @type {MultiCompiler} */ let compiler; + /** @type {MultiWatching} */ let myMultiWatching; beforeEach(() => { @@ -38,15 +51,30 @@ describe("MultiWatching", () => { }); it("invalidates each watching", () => { - expect(watchings[0].invalidate.mock.calls.length).toBe(1); - expect(watchings[1].invalidate.mock.calls.length).toBe(1); + expect(watchings[0].invalidate).toHaveBeenCalledTimes(1); + expect(watchings[1].invalidate).toHaveBeenCalledTimes(1); + }); + }); + + describe("suspend", () => { + it("suspends each watching", () => { + myMultiWatching.suspend(); + expect(watchings[0].suspend).toHaveBeenCalledTimes(1); + expect(watchings[1].suspend).toHaveBeenCalledTimes(1); + }); + + it("resume each watching", () => { + myMultiWatching.resume(); + expect(watchings[0].resume).toHaveBeenCalledTimes(1); + expect(watchings[1].resume).toHaveBeenCalledTimes(1); }); }); describe("close", () => { + /** @type {jest.Mock} */ let callback; - const callClosedFinishedCallback = watching => { - watching.close.mock.calls[0][0](); + const callClosedFinishedCallback = (/** @type {Watching} */ watching) => { + /** @type {jest.Mock} */ (watching.close).mock.calls[0][0](); }; beforeEach(() => { @@ -55,14 +83,14 @@ describe("MultiWatching", () => { }); it("closes each watching", () => { - expect(watchings[0].close.mock.calls.length).toBe(1); - expect(watchings[1].close.mock.calls.length).toBe(1); + expect(watchings[0].close).toHaveBeenCalledTimes(1); + expect(watchings[1].close).toHaveBeenCalledTimes(1); }); it("calls callback after each watching has closed", () => { callClosedFinishedCallback(watchings[0]); callClosedFinishedCallback(watchings[1]); - expect(callback.mock.calls.length).toBe(1); + expect(callback).toHaveBeenCalledTimes(1); }); }); }); diff --git a/test/NodeTemplatePlugin.test.js b/test/NodeTemplatePlugin.test.js index 6080e6d0b42..27fd51b80c5 100644 --- a/test/NodeTemplatePlugin.test.js +++ b/test/NodeTemplatePlugin.test.js @@ -1,11 +1,17 @@ -/* global describe, it */ "use strict"; +require("./helpers/warmup-webpack"); + const path = require("path"); -const webpack = require("../lib/webpack"); +const expectNoDeprecations = require("./helpers/expectNoDeprecations"); + +// cspell:word nodetest +expectNoDeprecations(); describe("NodeTemplatePlugin", () => { - it("should compile and run a simple module", done => { + it("should compile and run a simple module", (done) => { + const webpack = require(".."); + webpack( { mode: "production", @@ -14,7 +20,7 @@ describe("NodeTemplatePlugin", () => { output: { path: path.join(__dirname, "js", "NodeTemplatePlugin"), filename: "result.js", - chunkFilename: "[hash].result.[id].js", + chunkFilename: "[fullhash].result.[id].js", library: "abc", libraryTarget: "commonjs" }, @@ -22,26 +28,40 @@ describe("NodeTemplatePlugin", () => { }, (err, stats) => { if (err) return err; - expect(stats.hasErrors()).toBe(false); - expect(stats.hasWarnings()).toBe(false); - // eslint-disable-next-line node/no-missing-require + expect(/** @type {import("../").Stats} */ (stats).hasErrors()).toBe( + false + ); + expect(/** @type {import("../").Stats} */ (stats).hasWarnings()).toBe( + false + ); + + // @ts-expect-error generated file does not exist at type-check time const result = require("./js/NodeTemplatePlugin/result").abc; + expect(result.nextTick).toBe(process.nextTick); expect(result.fs).toBe(require("fs")); - result.loadChunk(456, chunk => { - expect(chunk).toBe(123); - result.loadChunk(567, chunk => { - expect(chunk).toEqual({ - a: 1 - }); - done(); - }); - }); + result.loadChunk( + 456, + /** @param {unknown} chunk loaded chunk */ (chunk) => { + expect(chunk).toBe(123); + result.loadChunk( + 567, + /** @param {unknown} chunk loaded chunk */ (chunk) => { + expect(chunk).toEqual({ + a: 1 + }); + done(); + } + ); + } + ); } ); }); - it("should compile and run a simple module in single mode", done => { + it("should compile and run a simple module in single mode", (done) => { + const webpack = require(".."); + webpack( { mode: "production", @@ -50,7 +70,7 @@ describe("NodeTemplatePlugin", () => { output: { path: path.join(__dirname, "js", "NodeTemplatePluginSingle"), filename: "result2.js", - chunkFilename: "[hash].result2.[id].js", + chunkFilename: "[fullhash].result2.[id].js", library: "def", libraryTarget: "umd", auxiliaryComment: "test" @@ -64,22 +84,32 @@ describe("NodeTemplatePlugin", () => { }, (err, stats) => { if (err) return err; - expect(stats.hasErrors()).toBe(false); - // eslint-disable-next-line node/no-missing-require + expect(/** @type {import("../").Stats} */ (stats).hasErrors()).toBe( + false + ); + + // @ts-expect-error generated file does not exist at type-check time const result = require("./js/NodeTemplatePluginSingle/result2"); + expect(result.nextTick).toBe(process.nextTick); expect(result.fs).toBe(require("fs")); const sameTick = true; - result.loadChunk(456, chunk => { - expect(chunk).toBe(123); - expect(sameTick).toBe(true); - result.loadChunk(567, chunk => { - expect(chunk).toEqual({ - a: 1 - }); - done(); - }); - }); + result.loadChunk( + 456, + /** @param {unknown} chunk loaded chunk */ (chunk) => { + expect(chunk).toBe(123); + expect(sameTick).toBe(true); + result.loadChunk( + 567, + /** @param {unknown} chunk loaded chunk */ (chunk) => { + expect(chunk).toEqual({ + a: 1 + }); + done(); + } + ); + } + ); } ); }); diff --git a/test/NormalModule.unittest.js b/test/NormalModule.unittest.js index 329001bc23b..49378da2da3 100644 --- a/test/NormalModule.unittest.js +++ b/test/NormalModule.unittest.js @@ -1,63 +1,88 @@ -/* globals describe, it, beforeEach, afterEach */ "use strict"; -const NormalModule = require("../lib/NormalModule"); -const NullDependency = require("../lib/dependencies/NullDependency"); const SourceMapSource = require("webpack-sources").SourceMapSource; const OriginalSource = require("webpack-sources").OriginalSource; const RawSource = require("webpack-sources").RawSource; +const NormalModule = require("../lib/NormalModule"); +const HarmonyImportSideEffectDependency = require("../lib/dependencies/HarmonyImportSideEffectDependency"); + +/** @typedef {import("../lib/NormalModule").LoaderItem} LoaderItem */ +/** @typedef {import("../lib/Parser")} Parser */ +/** @typedef {import("../lib/Generator")} Generator */ +/** @typedef {import("../lib/ModuleGraph")} ModuleGraph */ +/** @typedef {import("../lib/dependencies/ImportPhase").ImportPhaseType} ImportPhaseType */ describe("NormalModule", () => { + /** @type {InstanceType} */ let normalModule; + /** @type {string} */ let request; + /** @type {string} */ let userRequest; + /** @type {string} */ let rawRequest; + /** @type {LoaderItem[]} */ let loaders; + /** @type {string} */ let resource; + /** @type {Parser} */ let parser; + beforeEach(() => { request = "/some/request"; userRequest = "/some/userRequest"; rawRequest = "some/rawRequest"; + /** @type {LoaderItem[]} */ loaders = []; resource = "/some/resource"; - parser = { - parse() {} - }; - normalModule = new NormalModule({ - type: "javascript/auto", - request, - userRequest, - rawRequest, - loaders, - resource, - parser, - generator: null, - resolveOptions: {} - }); + parser = /** @type {Parser} */ ( + /** @type {unknown} */ ({ + parse() {} + }) + ); + normalModule = new NormalModule( + /** @type {import("../lib/NormalModule").NormalModuleCreateData} */ ( + /** @type {unknown} */ ({ + type: "javascript/auto", + request, + userRequest, + rawRequest, + loaders, + resource, + parser, + generator: null, + resolveOptions: {} + }) + ) + ); normalModule.buildInfo = { cacheable: true }; + normalModule.useSimpleSourceMap = true; }); + describe("#identifier", () => { it("returns an identifier for this module", () => { expect(normalModule.identifier()).toBe(request); }); + it("returns an identifier from toString", () => { normalModule.debugId = 1000; - expect(normalModule.toString()).toBe("Module[1000]"); - normalModule.id = 1; - expect(normalModule.toString()).toBe("Module[1]"); + expect(normalModule.toString()).toBe("Module[1000: /some/request]"); }); }); describe("#readableIdentifier", () => { it("calls the given requestShortener with the user request", () => { const spy = jest.fn(); - normalModule.readableIdentifier({ - shorten: spy - }); - expect(spy.mock.calls.length).toBe(1); + normalModule.readableIdentifier( + /** @type {import("../lib/RequestShortener")} */ ( + /** @type {unknown} */ ({ + shorten: spy + }) + ) + ); + expect(spy).toHaveBeenCalledTimes(1); expect(spy.mock.calls[0][0]).toBe(userRequest); }); }); @@ -70,20 +95,26 @@ describe("NormalModule", () => { }) ).toBe("../userRequest"); }); + describe("given a userRequest containing loaders", () => { beforeEach(() => { userRequest = "/some/userRequest!/some/other/userRequest!/some/thing/is/off/here"; - normalModule = new NormalModule({ - type: "javascript/auto", - request, - userRequest, - rawRequest, - loaders, - resource, - parser - }); + normalModule = new NormalModule( + /** @type {import("../lib/NormalModule").NormalModuleCreateData} */ ( + /** @type {unknown} */ ({ + type: "javascript/auto", + request, + userRequest, + rawRequest, + loaders, + resource, + parser + }) + ) + ); }); + it("contextifies every path in the userRequest", () => { expect( normalModule.libIdent({ @@ -92,19 +123,25 @@ describe("NormalModule", () => { ).toBe("../userRequest!../other/userRequest!../thing/is/off/here"); }); }); + describe("given a userRequest containing query parameters", () => { it("ignores paths in query parameters", () => { + // cspell:word testpath userRequest = "F:\\some\\context\\loader?query=foo\\bar&otherPath=testpath/other"; - normalModule = new NormalModule({ - type: "javascript/auto", - request, - userRequest, - rawRequest, - loaders, - resource, - parser - }); + normalModule = new NormalModule( + /** @type {import("../lib/NormalModule").NormalModuleCreateData} */ ( + /** @type {unknown} */ ({ + type: "javascript/auto", + request, + userRequest, + rawRequest, + loaders, + resource, + parser + }) + ) + ); expect( normalModule.libIdent({ context: "F:\\some\\context" @@ -118,20 +155,27 @@ describe("NormalModule", () => { it("return the resource", () => { expect(normalModule.nameForCondition()).toBe(resource); }); + describe("given a resource containing a ?-sign", () => { const baseResource = "some/resource"; + beforeEach(() => { - resource = baseResource + "?some=query"; - normalModule = new NormalModule({ - type: "javascript/auto", - request, - userRequest, - rawRequest, - loaders, - resource, - parser - }); + resource = `${baseResource}?some=query`; + normalModule = new NormalModule( + /** @type {import("../lib/NormalModule").NormalModuleCreateData} */ ( + /** @type {unknown} */ ({ + type: "javascript/auto", + request, + userRequest, + rawRequest, + loaders, + resource, + parser + }) + ) + ); }); + it("return only the part before the ?-sign", () => { expect(normalModule.nameForCondition()).toBe(baseResource); }); @@ -139,242 +183,279 @@ describe("NormalModule", () => { }); describe("#createSourceForAsset", () => { + /** @type {string} */ let name; + /** @type {string} */ let content; + /** @type {string | (() => void)} */ let sourceMap; + beforeEach(() => { name = "some name"; content = "some content"; sourceMap = "some sourcemap"; }); + describe("given no sourcemap", () => { it("returns a RawSource", () => { - expect(normalModule.createSourceForAsset(name, content)).toBeInstanceOf( - RawSource - ); + expect( + normalModule.createSourceForAsset("/", name, content) + ).toBeInstanceOf(RawSource); }); }); + describe("given a string as the sourcemap", () => { it("returns a OriginalSource", () => { expect( - normalModule.createSourceForAsset(name, content, sourceMap) + normalModule.createSourceForAsset( + "/", + name, + content, + /** @type {string} */ (sourceMap) + ) ).toBeInstanceOf(OriginalSource); }); }); - describe("given a some other kind of sourcemap", () => { + + describe("given a some other kind of sourcemap (source maps disabled)", () => { beforeEach(() => { sourceMap = () => {}; + normalModule.useSimpleSourceMap = false; }); + it("returns a SourceMapSource", () => { expect( - normalModule.createSourceForAsset(name, content, sourceMap) - ).toBeInstanceOf(SourceMapSource); + normalModule.createSourceForAsset( + "/", + name, + content, + /** @type {string} */ (/** @type {unknown} */ (sourceMap)) + ) + ).toBeInstanceOf(RawSource); }); }); - }); - - describe("#originalSource", () => { - let expectedSource = "some source"; - beforeEach(() => { - normalModule._source = new RawSource(expectedSource); - }); - it("returns an original Source", () => { - expect(normalModule.originalSource()).toBe(normalModule._source); - }); - }); - - describe("#hasDependencies", () => { - it("returns true if has dependencies", () => { - normalModule.addDependency(new NullDependency()); - expect(normalModule.hasDependencies()).toBe(true); - }); - it("returns false if has dependencies", () => { - expect(normalModule.hasDependencies()).toBe(false); - }); - }); - describe("#needRebuild", () => { - let fileTimestamps; - let contextTimestamps; - let fileDependencies; - let contextDependencies; - let fileA; - let fileB; - - function setDeps(fileDependencies, contextDependencies) { - normalModule.buildInfo.fileDependencies = fileDependencies; - normalModule.buildInfo.contextDependencies = contextDependencies; - } - beforeEach(() => { - fileA = "fileA"; - fileB = "fileB"; - fileDependencies = [fileA, fileB]; - contextDependencies = [fileA, fileB]; - fileTimestamps = new Map([[fileA, 1], [fileB, 1]]); - contextTimestamps = new Map([[fileA, 1], [fileB, 1]]); - normalModule.buildTimestamp = 2; - setDeps(fileDependencies, contextDependencies); - }); - describe("given all timestamps are older than the buildTimestamp", () => { - it("returns false", () => { - expect( - normalModule.needRebuild(fileTimestamps, contextTimestamps) - ).toBe(false); - }); - }); - describe("given a file timestamp is newer than the buildTimestamp", () => { + describe("given a some other kind of sourcemap (simple source maps enabled)", () => { beforeEach(() => { - fileTimestamps.set(fileA, 3); + sourceMap = () => {}; }); - it("returns true", () => { + + it("returns a SourceMapSource", () => { expect( - normalModule.needRebuild(fileTimestamps, contextTimestamps) - ).toBe(true); + normalModule.createSourceForAsset( + "/", + name, + content, + /** @type {string} */ (/** @type {unknown} */ (sourceMap)) + ) + ).toBeInstanceOf(RawSource); }); }); - describe("given a no file timestamp exists", () => { + + describe("given a some other kind of sourcemap (source maps enabled)", () => { beforeEach(() => { - fileTimestamps = new Map(); + sourceMap = () => {}; + normalModule.useSourceMap = true; }); - it("returns true", () => { + + it("returns a SourceMapSource", () => { expect( - normalModule.needRebuild(fileTimestamps, contextTimestamps) - ).toBe(true); + normalModule.createSourceForAsset( + "/", + name, + content, + /** @type {string} */ (/** @type {unknown} */ (sourceMap)) + ) + ).toBeInstanceOf(SourceMapSource); }); }); - describe("given a context timestamp is newer than the buildTimestamp", () => { - beforeEach(() => { - contextTimestamps.set(fileA, 3); - }); - it("returns true", () => { - expect( - normalModule.needRebuild(fileTimestamps, contextTimestamps) - ).toBe(true); - }); + }); + + describe("#originalSource", () => { + const expectedSource = "some source"; + + beforeEach(() => { + /** @type {EXPECTED_ANY} */ (normalModule)._source = new RawSource( + expectedSource + ); }); - describe("given a no context timestamp exists", () => { - beforeEach(() => { - contextTimestamps = new Map(); - }); - it("returns true", () => { - expect( - normalModule.needRebuild(fileTimestamps, contextTimestamps) - ).toBe(true); - }); + + it("returns an original Source", () => { + expect(normalModule.originalSource()).toBe( + /** @type {EXPECTED_ANY} */ (normalModule)._source + ); }); }); describe("#applyNoParseRule", () => { + /** @type {string | RegExp} */ let rule; + /** @type {string} */ let content; + describe("given a string as rule", () => { beforeEach(() => { rule = "some-rule"; }); + describe("and the content starting with the string specified in rule", () => { beforeEach(() => { - content = rule + "some-content"; + content = `${rule}some-content`; }); + it("returns true", () => { - expect(normalModule.shouldPreventParsing(rule, content)).toBe(true); + expect( + /** @type {EXPECTED_ANY} */ (normalModule).shouldPreventParsing( + rule, + content + ) + ).toBe(true); }); }); + describe("and the content does not start with the string specified in rule", () => { beforeEach(() => { content = "some-content"; }); + it("returns false", () => { - expect(normalModule.shouldPreventParsing(rule, content)).toBe(false); + expect( + /** @type {EXPECTED_ANY} */ (normalModule).shouldPreventParsing( + rule, + content + ) + ).toBe(false); }); }); }); + describe("given a regex as rule", () => { beforeEach(() => { rule = /some-rule/; }); + describe("and the content matches the rule", () => { beforeEach(() => { - content = rule + "some-content"; + content = `${rule}some-content`; }); + it("returns true", () => { - expect(normalModule.shouldPreventParsing(rule, content)).toBe(true); + expect( + /** @type {EXPECTED_ANY} */ (normalModule).shouldPreventParsing( + rule, + content + ) + ).toBe(true); }); }); + describe("and the content does not match the rule", () => { beforeEach(() => { content = "some-content"; }); + it("returns false", () => { - expect(normalModule.shouldPreventParsing(rule, content)).toBe(false); + expect( + /** @type {EXPECTED_ANY} */ (normalModule).shouldPreventParsing( + rule, + content + ) + ).toBe(false); }); }); }); }); describe("#shouldPreventParsing", () => { + /** @type {jest.Mock} */ let applyNoParseRuleSpy; + beforeEach(() => { applyNoParseRuleSpy = jest.fn(); normalModule.applyNoParseRule = applyNoParseRuleSpy; }); + describe("given no noParseRule", () => { it("returns false", () => { - expect(normalModule.shouldPreventParsing()).toBe(false); - expect(applyNoParseRuleSpy.mock.calls.length).toBe(0); + expect( + /** @type {EXPECTED_ANY} */ (normalModule).shouldPreventParsing() + ).toBe(false); + expect(applyNoParseRuleSpy).not.toHaveBeenCalled(); }); }); + describe("given a noParseRule", () => { + /** @type {boolean} */ let returnValOfSpy; + beforeEach(() => { returnValOfSpy = true; applyNoParseRuleSpy.mockReturnValue(returnValOfSpy); }); + describe("that is a string", () => { it("calls and returns whatever applyNoParseRule returns", () => { - expect(normalModule.shouldPreventParsing("some rule")).toBe( - returnValOfSpy - ); - expect(applyNoParseRuleSpy.mock.calls.length).toBe(1); + expect( + /** @type {EXPECTED_ANY} */ (normalModule).shouldPreventParsing( + "some rule" + ) + ).toBe(returnValOfSpy); + expect(applyNoParseRuleSpy).toHaveBeenCalledTimes(1); }); }); + describe("that is a regex", () => { it("calls and returns whatever applyNoParseRule returns", () => { - expect(normalModule.shouldPreventParsing("some rule")).toBe( - returnValOfSpy - ); - expect(applyNoParseRuleSpy.mock.calls.length).toBe(1); + expect( + /** @type {EXPECTED_ANY} */ (normalModule).shouldPreventParsing( + "some rule" + ) + ).toBe(returnValOfSpy); + expect(applyNoParseRuleSpy).toHaveBeenCalledTimes(1); }); }); + describe("that is an array", () => { - describe("of strings and or regexs", () => { + describe("of strings and or regexps", () => { + /** @type {(string | RegExp)[]} */ let someRules; + beforeEach(() => { someRules = ["some rule", /some rule1/, "some rule2"]; }); + describe("and none of them match", () => { beforeEach(() => { returnValOfSpy = false; applyNoParseRuleSpy.mockReturnValue(returnValOfSpy); }); + it("returns false", () => { - expect(normalModule.shouldPreventParsing(someRules)).toBe( - returnValOfSpy - ); - expect(applyNoParseRuleSpy.mock.calls.length).toBe(3); + expect( + /** @type {EXPECTED_ANY} */ (normalModule).shouldPreventParsing( + someRules + ) + ).toBe(returnValOfSpy); + expect(applyNoParseRuleSpy).toHaveBeenCalledTimes(3); }); }); + describe("and the first of them matches", () => { beforeEach(() => { returnValOfSpy = true; applyNoParseRuleSpy.mockReturnValue(returnValOfSpy); }); + it("returns true", () => { - expect(normalModule.shouldPreventParsing(someRules)).toBe( - returnValOfSpy - ); - expect(applyNoParseRuleSpy.mock.calls.length).toBe(1); + expect( + /** @type {EXPECTED_ANY} */ (normalModule).shouldPreventParsing( + someRules + ) + ).toBe(returnValOfSpy); + expect(applyNoParseRuleSpy).toHaveBeenCalledTimes(1); }); }); + describe("and the last of them matches", () => { beforeEach(() => { returnValOfSpy = true; @@ -382,15 +463,324 @@ describe("NormalModule", () => { applyNoParseRuleSpy.mockReturnValueOnce(false); applyNoParseRuleSpy.mockReturnValue(true); }); + it("returns true", () => { - expect(normalModule.shouldPreventParsing(someRules)).toBe( - returnValOfSpy - ); - expect(applyNoParseRuleSpy.mock.calls.length).toBe(3); + expect( + /** @type {EXPECTED_ANY} */ (normalModule).shouldPreventParsing( + someRules + ) + ).toBe(returnValOfSpy); + expect(applyNoParseRuleSpy).toHaveBeenCalledTimes(3); }); }); }); }); }); }); + + describe("#getSideEffectsConnectionState", () => { + // Builds a synthetic linear chain of `count` side-effect-free modules + // linked by HarmonyImportSideEffectDependency. Walking the chain via + // the recursive form used 2 stack frames per module and overflowed on + // long chains (issue #20986). + /** + * @param {number} count chain length + * @returns {{ modules: InstanceType[], moduleGraph: ModuleGraph }} chain + */ + const buildChain = (count) => { + const modules = []; + for (let i = 0; i < count; i++) { + const mod = new NormalModule( + /** @type {import("../lib/NormalModule").NormalModuleCreateData} */ ( + /** @type {unknown} */ ({ + type: "javascript/auto", + request: `/m${i}`, + userRequest: `/m${i}`, + rawRequest: `m${i}`, + loaders: [], + resource: `/m${i}`, + parser: { parse() {} }, + generator: null, + resolveOptions: {} + }) + ) + ); + mod.buildMeta = { sideEffectFree: true }; + modules.push(mod); + } + const depToModule = new Map(); + for (let i = 0; i < count - 1; i++) { + const dep = new HarmonyImportSideEffectDependency( + `m${i + 1}`, + 0, + /** @type {ImportPhaseType} */ (/** @type {unknown} */ ("evaluation")) + ); + modules[i].dependencies = [dep]; + depToModule.set(dep, modules[i + 1]); + } + modules[count - 1].dependencies = []; + const moduleGraph = /** @type {ModuleGraph} */ ( + /** @type {unknown} */ ({ + /** + * @param {import("../lib/Dependency")} dep dependency + * @returns {import("../lib/Module") | null} module + */ + getModule: (dep) => depToModule.get(dep), + getOptimizationBailout: () => [] + }) + ); + return { modules, moduleGraph }; + }; + + it("handles deep linear chains without overflowing the stack", () => { + const { modules, moduleGraph } = buildChain(20000); + expect(modules[0].getSideEffectsConnectionState(moduleGraph)).toBe(false); + }); + + it("propagates a deep-chain bailout all the way back to the root", () => { + // 5000 modules is far beyond what the recursive walker would walk + // using V8 stack frames; the linear-chain peeling code path must + // still propagate a bailout deep in the tail back to module 0. + const { modules, moduleGraph } = buildChain(5000); + modules[4500].buildMeta = { sideEffectFree: false }; + expect(modules[0].getSideEffectsConnectionState(moduleGraph)).toBe(true); + expect(modules[0]._isEvaluatingSideEffects).toBe(false); + expect(modules[4499]._isEvaluatingSideEffects).toBe(false); + }); + + it("detects cycles in the side-effect graph", () => { + const { modules, moduleGraph } = buildChain(50); + // close the loop: last module's dep points to modules[0] + const lastDep = new HarmonyImportSideEffectDependency( + "m0", + 0, + /** @type {ImportPhaseType} */ (/** @type {unknown} */ ("evaluation")) + ); + modules[49].dependencies = [lastDep]; + const originalGetModule = moduleGraph.getModule; + moduleGraph.getModule = /** @type {ModuleGraph["getModule"]} */ ( + /** + * @param {import("../lib/Dependency")} dep dependency + * @returns {import("../lib/Module") | null} module + */ + (dep) => (dep === lastDep ? modules[0] : originalGetModule(dep)) + ); + // Cycles fold to `false` (the accumulator's identity) when all + // participating modules are side-effect free — same as the original + // recursive behavior. + expect(modules[0].getSideEffectsConnectionState(moduleGraph)).toBe(false); + expect(modules[0]._isEvaluatingSideEffects).toBe(false); + }); + + it("reports the bailout dep when a chain member has side effects", () => { + const { modules, moduleGraph } = buildChain(10); + // Make module 5 have side effects. + modules[5].buildMeta = { sideEffectFree: false }; + const bailouts = new Map(); + /** @type {EXPECTED_ANY} */ (moduleGraph).getOptimizationBailout = + /** + * @param {import("../lib/NormalModule")} mod module + * @returns {unknown[]} bailout list + */ + (mod) => { + if (!bailouts.has(mod)) bailouts.set(mod, []); + return bailouts.get(mod); + }; + expect(modules[0].getSideEffectsConnectionState(moduleGraph)).toBe(true); + // Each ancestor in the chain (modules 0..4) records the bailout once + // for the dep that triggered descent, matching the recursive baseline. + for (let i = 0; i < 5; i++) { + const list = bailouts.get(modules[i]); + expect(list).toHaveLength(1); + expect(list[0]()).toMatch( + /Dependency \(harmony side effect evaluation\)/ + ); + } + }); + + it("aggregates state across branching deps", () => { + // Diamond: root depends on two side-effect-free leaves. + /** + * @param {string} id module id + * @returns {InstanceType} module + */ + const make = (id) => { + const mod = new NormalModule( + /** @type {import("../lib/NormalModule").NormalModuleCreateData} */ ( + /** @type {unknown} */ ({ + type: "javascript/auto", + request: `/${id}`, + userRequest: `/${id}`, + rawRequest: id, + loaders: [], + resource: `/${id}`, + parser: { parse() {} }, + generator: null, + resolveOptions: {} + }) + ) + ); + mod.buildMeta = { sideEffectFree: true }; + mod.dependencies = []; + return mod; + }; + const root = make("root"); + const a = make("a"); + const b = make("b"); + const depA = new HarmonyImportSideEffectDependency( + "a", + 0, + /** @type {ImportPhaseType} */ (/** @type {unknown} */ ("evaluation")) + ); + const depB = new HarmonyImportSideEffectDependency( + "b", + 1, + /** @type {ImportPhaseType} */ (/** @type {unknown} */ ("evaluation")) + ); + root.dependencies = [depA, depB]; + const moduleGraph = /** @type {ModuleGraph} */ ( + /** @type {unknown} */ ({ + /** + * @param {import("../lib/Dependency")} dep dependency + * @returns {import("../lib/Module") | null} module + */ + getModule: (dep) => (dep === depA ? a : dep === depB ? b : null), + getOptimizationBailout: () => [] + }) + ); + expect(root.getSideEffectsConnectionState(moduleGraph)).toBe(false); + expect(root._isEvaluatingSideEffects).toBe(false); + expect(a._isEvaluatingSideEffects).toBe(false); + expect(b._isEvaluatingSideEffects).toBe(false); + }); + + it("handles a deep cyclic chain whose modules have extra non-recursive deps", () => { + // Mirrors the canonical #20986 reproduction: each module has a + // HarmonyImportSideEffectDependency to the next module plus + // several other deps (modelled here by ConstDependency which + // reports `false` from `getModuleEvaluationSideEffectsState`). + // The last module's SideEffectDep closes the loop back to + // module 0. Pre-fix this overflowed V8's stack at ~1300 modules + // because the linear-chain walker only recognized 1-dep modules. + const ConstDependency = require("../lib/dependencies/ConstDependency"); + + const N = 5000; + const modules = []; + for (let i = 0; i < N; i++) { + const mod = new NormalModule( + /** @type {import("../lib/NormalModule").NormalModuleCreateData} */ ( + /** @type {unknown} */ ({ + type: "javascript/auto", + request: `/m${i}`, + userRequest: `/m${i}`, + rawRequest: `m${i}`, + loaders: [], + resource: `/m${i}`, + parser: { parse() {} }, + generator: null, + resolveOptions: {} + }) + ) + ); + mod.buildMeta = { sideEffectFree: true }; + modules.push(mod); + } + const depToModule = new Map(); + for (let i = 0; i < N; i++) { + const sideDep = new HarmonyImportSideEffectDependency( + `m${(i + 1) % N}`, + 0, + /** @type {ImportPhaseType} */ (/** @type {unknown} */ ("evaluation")) + ); + modules[i].dependencies = [ + sideDep, + new ConstDependency("", [0, 0]), + new ConstDependency("", [0, 0]) + ]; + depToModule.set(sideDep, modules[(i + 1) % N]); + } + const moduleGraph = /** @type {ModuleGraph} */ ( + /** @type {unknown} */ ({ + /** + * @param {import("../lib/Dependency")} dep dependency + * @returns {import("../lib/Module") | null} module + */ + getModule: (dep) => depToModule.get(dep), + getOptimizationBailout: () => [] + }) + ); + expect(modules[0].getSideEffectsConnectionState(moduleGraph)).toBe(false); + }); + + it("falls back to iterative walk past the recursion limit on non-linear graphs", () => { + // Each module has two `HarmonyImportSideEffectDependency`s, so + // the linear-chain fast path can't apply: the module's first + // dep continues the chain, the second points to a shared + // side-effect-free leaf. The walker therefore enters the + // general for-loop, recurses one V8 frame per module, and + // must switch to `walkSideEffectsIterative` once depth crosses + // `SIDE_EFFECTS_RECURSION_LIMIT` (2000). 2500 modules is far + // enough past that boundary that any regression in the + // iterative fallback path will overflow V8's stack. + const N = 2500; + /** + * @param {string} id module id + * @returns {InstanceType} module + */ + const make = (id) => { + const mod = new NormalModule( + /** @type {import("../lib/NormalModule").NormalModuleCreateData} */ ( + /** @type {unknown} */ ({ + type: "javascript/auto", + request: `/${id}`, + userRequest: `/${id}`, + rawRequest: id, + loaders: [], + resource: `/${id}`, + parser: { parse() {} }, + generator: null, + resolveOptions: {} + }) + ) + ); + mod.buildMeta = { sideEffectFree: true }; + return mod; + }; + const leaf = make("leaf"); + leaf.dependencies = []; + const modules = []; + for (let i = 0; i < N; i++) modules.push(make(`m${i}`)); + const depToModule = new Map(); + for (let i = 0; i < N - 1; i++) { + const next = new HarmonyImportSideEffectDependency( + `m${i + 1}`, + 0, + /** @type {ImportPhaseType} */ (/** @type {unknown} */ ("evaluation")) + ); + const aside = new HarmonyImportSideEffectDependency( + "leaf", + 1, + /** @type {ImportPhaseType} */ (/** @type {unknown} */ ("evaluation")) + ); + modules[i].dependencies = [next, aside]; + depToModule.set(next, modules[i + 1]); + depToModule.set(aside, leaf); + } + modules[N - 1].dependencies = []; + const moduleGraph = /** @type {ModuleGraph} */ ( + /** @type {unknown} */ ({ + /** + * @param {import("../lib/Dependency")} dep dependency + * @returns {import("../lib/Module") | null} module + */ + getModule: (dep) => depToModule.get(dep), + getOptimizationBailout: () => [] + }) + ); + expect(modules[0].getSideEffectsConnectionState(moduleGraph)).toBe(false); + for (let i = 0; i < N; i++) { + expect(modules[i]._isEvaluatingSideEffects).toBe(false); + } + }); + }); }); diff --git a/test/NullDependency.unittest.js b/test/NullDependency.unittest.js deleted file mode 100644 index 958027bddd4..00000000000 --- a/test/NullDependency.unittest.js +++ /dev/null @@ -1,49 +0,0 @@ -"use strict"; - -const NullDependency = require("../lib/dependencies/NullDependency"); - -describe("NullDependency", () => { - let env; - - beforeEach(() => (env = {})); - - it("is a function", () => { - expect(NullDependency).toBeTypeOf("function"); - }); - - describe("when created", () => { - beforeEach(() => (env.nullDependency = new NullDependency())); - - it("has a null type", () => { - expect(env.nullDependency.type).toBe("null"); - }); - - it("has update hash function", () => { - expect(env.nullDependency.updateHash).toBeTypeOf("function"); - }); - - it("does not update hash", () => { - const hash = { - update: jest.fn() - }; - env.nullDependency.updateHash(hash); - expect(hash.update).not.toHaveBeenCalled(); - }); - }); - - describe("Template", () => { - it("is a function", () => { - expect(NullDependency.Template).toBeTypeOf("function"); - }); - - describe("when created", () => { - beforeEach(() => { - env.nullDependencyTemplate = new NullDependency.Template(); - }); - - it("has apply function", () => { - expect(env.nullDependencyTemplate.apply).toBeTypeOf("function"); - }); - }); - }); -}); diff --git a/test/Parser.unittest.js b/test/Parser.unittest.js deleted file mode 100644 index 3d3fdeeca8a..00000000000 --- a/test/Parser.unittest.js +++ /dev/null @@ -1,636 +0,0 @@ -"use strict"; - -const Parser = require("../lib/Parser"); -const BasicEvaluatedExpression = require("../lib/BasicEvaluatedExpression"); - -describe("Parser", () => { - /* eslint-disable no-undef */ - /* eslint-disable no-unused-vars */ - /* eslint-disable no-inner-declarations */ - const testCases = { - "call ident": [ - function() { - abc("test"); - }, - { - abc: ["test"] - } - ], - "call member": [ - function() { - cde.abc("membertest"); - }, - { - cdeabc: ["membertest"] - } - ], - "call member using bracket notation": [ - function() { - cde["abc"]("membertest"); - }, - { - cdeabc: ["membertest"] - } - ], - "call inner member": [ - function() { - cde.ddd.abc("inner"); - }, - { - cdedddabc: ["inner"] - } - ], - "call inner member using bracket notation": [ - function() { - cde.ddd["abc"]("inner"); - }, - { - cdedddabc: ["inner"] - } - ], - expression: [ - function() { - fgh; - }, - { - fgh: [""] - } - ], - "expression sub": [ - function() { - fgh.sub; - }, - { - fghsub: ["notry"] - } - ], - "member expression": [ - function() { - test[memberExpr]; - test[+memberExpr]; - }, - { - expressions: ["memberExpr", "memberExpr"] - } - ], - "in function definition": [ - function() { - (function(abc, cde, fgh) { - abc("test"); - cde.abc("test"); - cde.ddd.abc("test"); - fgh; - fgh.sub; - })(); - }, - {} - ], - "const definition": [ - function() { - let abc, cde, fgh; - abc("test"); - cde.abc("test"); - cde.ddd.abc("test"); - fgh; - fgh.sub; - }, - {} - ], - "var definition": [ - function() { - var abc, cde, fgh; - abc("test"); - cde.abc("test"); - cde.ddd.abc("test"); - fgh; - fgh.sub; - }, - {} - ], - "function definition": [ - function() { - function abc() {} - - function cde() {} - - function fgh() {} - abc("test"); - cde.abc("test"); - cde.ddd.abc("test"); - fgh; - fgh.sub; - }, - {} - ], - "class definition": [ - function() { - class memberExpr { - cde() { - abc("cde"); - } - static fgh() { - abc("fgh"); - fgh(); - } - } - }, - { - abc: ["cde", "fgh"], - fgh: ["memberExpr"] - } - ], - "in try": [ - function() { - try { - fgh.sub; - fgh; - - function test(ttt) { - fgh.sub; - fgh; - } - } catch (e) { - fgh.sub; - fgh; - } - }, - { - fghsub: ["try", "notry", "notry"], - fgh: ["test", "test ttt", "test e"] - } - ], - "renaming with const": [ - function() { - const xyz = abc; - xyz("test"); - }, - { - abc: ["test"] - } - ], - "renaming with var": [ - function() { - var xyz = abc; - xyz("test"); - }, - { - abc: ["test"] - } - ], - "renaming with assignment": [ - function() { - const xyz = abc; - xyz("test"); - }, - { - abc: ["test"] - } - ], - "renaming with IIFE": [ - function() { - !(function(xyz) { - xyz("test"); - })(abc); - }, - { - abc: ["test"] - } - ], - "renaming arguments with IIFE (called)": [ - function() { - !function(xyz) { - xyz("test"); - }.call(fgh, abc); - }, - { - abc: ["test"], - fgh: [""] - } - ], - "renaming this's properties with IIFE (called)": [ - function() { - !function() { - this.sub; - }.call(ijk); - }, - { - ijksub: ["test"] - } - ], - "renaming this's properties with nested IIFE (called)": [ - function() { - !function() { - !function() { - this.sub; - }.call(this); - }.call(ijk); - }, - { - ijksub: ["test"] - } - ], - "new Foo(...)": [ - function() { - new xyz("membertest"); - }, - { - xyz: ["membertest"] - } - ], - "spread calls/literals": [ - function() { - var xyz = [...abc("xyz"), cde]; - Math.max(...fgh); - }, - { - abc: ["xyz"], - fgh: ["xyz"] - } - ] - }; - /* eslint-enable no-undef */ - /* eslint-enable no-unused-vars */ - /* eslint-enable no-inner-declarations */ - - Object.keys(testCases).forEach(name => { - it("should parse " + name, () => { - let source = testCases[name][0].toString(); - source = source.substr(13, source.length - 14).trim(); - const state = testCases[name][1]; - - const testParser = new Parser({}); - testParser.hooks.canRename.tap("abc", "ParserTest", expr => true); - testParser.hooks.canRename.tap("ijk", "ParserTest", expr => true); - testParser.hooks.call.tap("abc", "ParserTest", expr => { - if (!testParser.state.abc) testParser.state.abc = []; - testParser.state.abc.push(testParser.parseString(expr.arguments[0])); - return true; - }); - testParser.hooks.call.tap("cde.abc", "ParserTest", expr => { - if (!testParser.state.cdeabc) testParser.state.cdeabc = []; - testParser.state.cdeabc.push(testParser.parseString(expr.arguments[0])); - return true; - }); - testParser.hooks.call.tap("cde.ddd.abc", "ParserTest", expr => { - if (!testParser.state.cdedddabc) testParser.state.cdedddabc = []; - testParser.state.cdedddabc.push( - testParser.parseString(expr.arguments[0]) - ); - return true; - }); - testParser.hooks.expression.tap("fgh", "ParserTest", expr => { - if (!testParser.state.fgh) testParser.state.fgh = []; - testParser.state.fgh.push( - Array.from(testParser.scope.definitions.asSet()).join(" ") - ); - return true; - }); - testParser.hooks.expression.tap("fgh.sub", "ParserTest", expr => { - if (!testParser.state.fghsub) testParser.state.fghsub = []; - testParser.state.fghsub.push(testParser.scope.inTry ? "try" : "notry"); - return true; - }); - testParser.hooks.expression.tap("ijk.sub", "ParserTest", expr => { - if (!testParser.state.ijksub) testParser.state.ijksub = []; - testParser.state.ijksub.push("test"); - return true; - }); - testParser.hooks.expression.tap("memberExpr", "ParserTest", expr => { - if (!testParser.state.expressions) testParser.state.expressions = []; - testParser.state.expressions.push(expr.name); - return true; - }); - testParser.hooks.new.tap("xyz", "ParserTest", expr => { - if (!testParser.state.xyz) testParser.state.xyz = []; - testParser.state.xyz.push(testParser.parseString(expr.arguments[0])); - return true; - }); - const actual = testParser.parse(source); - expect(typeof actual).toBe("object"); - expect(actual).toEqual(state); - }); - }); - - it("should parse comments", () => { - const source = "//comment1\n/*comment2*/"; - const state = [ - { - type: "Line", - value: "comment1" - }, - { - type: "Block", - value: "comment2" - } - ]; - - const testParser = new Parser({}); - - testParser.hooks.program.tap("ParserTest", (ast, comments) => { - if (!testParser.state.comments) testParser.state.comments = comments; - return true; - }); - - const actual = testParser.parse(source); - expect(typeof actual).toBe("object"); - expect(typeof actual.comments).toBe("object"); - actual.comments.forEach((element, index) => { - expect(typeof element.type).toBe("string"); - expect(typeof element.value).toBe("string"); - expect(element.type).toBe(state[index].type); - expect(element.value).toBe(state[index].value); - }); - }); - - describe("expression evaluation", () => { - function evaluateInParser(source) { - const parser = new Parser(); - parser.hooks.call.tap("test", "ParserTest", expr => { - parser.state.result = parser.evaluateExpression(expr.arguments[0]); - }); - parser.hooks.evaluateIdentifier.tap("aString", "ParserTest", expr => - new BasicEvaluatedExpression().setString("aString").setRange(expr.range) - ); - parser.hooks.evaluateIdentifier.tap("b.Number", "ParserTest", expr => - new BasicEvaluatedExpression().setNumber(123).setRange(expr.range) - ); - return parser.parse("test(" + source + ");").result; - } - - const testCases = { - '"strrring"': "string=strrring", - '"strr" + "ring"': "string=strrring", - '"s" + ("trr" + "rin") + "g"': "string=strrring", - "'S' + (\"strr\" + \"ring\") + 'y'": "string=Sstrrringy", - "/abc/": "regExp=/abc/", - "1": "number=1", - "1 + 3": "number=4", - "3 - 1": "number=2", - "2 * 3": "number=6", - "8 / 2": "number=4", - "2 ** 3": "number=8", - "12 & 5": "number=4", - "12 | 5": "number=13", - "12 ^ 5": "number=9", - "9 >>> 2": "number=2", - "9 >> 2": "number=2", - "9 << 2": "number=36", - "~3": "number=-4", - "1 == 1": "bool=true", - "1 === 1": "bool=true", - "3 != 1": "bool=true", - "3 !== 1": "bool=true", - "3 == 1": "bool=false", - "3 === 1": "bool=false", - "1 != 1": "bool=false", - "1 !== 1": "bool=false", - "true === false": "bool=false", - "false !== false": "bool=false", - "true == true": "bool=true", - "false != true": "bool=true", - "!'a'": "bool=false", - "!''": "bool=true", - "'pre' + a": "wrapped=['pre' string=pre]+[null]", - "a + 'post'": "wrapped=[null]+['post' string=post]", - "'pre' + a + 'post'": "wrapped=['pre' string=pre]+['post' string=post]", - "1 + a + 2": "", - "1 + a + 'post'": "wrapped=[null]+['post' string=post]", - "'' + 1 + a + 2": "wrapped=['' + 1 string=1]+[2 string=2]", - "'' + 1 + a + 2 + 3": "wrapped=['' + 1 string=1]+[2 + 3 string=23]", - "'' + 1 + a + (2 + 3)": "wrapped=['' + 1 string=1]+[2 + 3 string=5]", - "'pre' + (1 + a) + (2 + 3)": - "wrapped=['pre' string=pre]+[2 + 3 string=5]", - "a ? 'o1' : 'o2'": "options=['o1' string=o1],['o2' string=o2]", - "a ? 'o1' : b ? 'o2' : 'o3'": - "options=['o1' string=o1],['o2' string=o2],['o3' string=o3]", - "a ? (b ? 'o1' : 'o2') : 'o3'": - "options=['o1' string=o1],['o2' string=o2],['o3' string=o3]", - "a ? (b ? 'o1' : 'o2') : c ? 'o3' : 'o4'": - "options=['o1' string=o1],['o2' string=o2],['o3' string=o3],['o4' string=o4]", - "a ? 'o1' : b ? 'o2' : c ? 'o3' : 'o4'": - "options=['o1' string=o1],['o2' string=o2],['o3' string=o3],['o4' string=o4]", - "a ? 'o1' : b ? b : c ? 'o3' : c": - "options=['o1' string=o1],[b],['o3' string=o3],[c]", - "['i1', 'i2', 3, a, b ? 4 : 5]": - "items=['i1' string=i1],['i2' string=i2],[3 number=3],[a],[b ? 4 : 5 options=[4 number=4],[5 number=5]]", - "typeof 'str'": "string=string", - "typeof aString": "string=string", - "typeof b.Number": "string=number", - "typeof b['Number']": "string=number", - "typeof b[Number]": "", - "b.Number": "number=123", - "b['Number']": "number=123", - "b[Number]": "", - "'str'.concat()": "string=str", - "'str'.concat('one')": "string=strone", - "'str'.concat('one').concat('two')": "string=stronetwo", - "'str'.concat('one').concat('two', 'three')": "string=stronetwothree", - "'str'.concat('one', 'two')": "string=stronetwo", - "'str'.concat('one', 'two').concat('three')": "string=stronetwothree", - "'str'.concat('one', 'two').concat('three', 'four')": - "string=stronetwothreefour", - "'str'.concat('one', obj)": "wrapped=['str' string=str]+[null]", - "'str'.concat('one', obj).concat()": "wrapped=['str' string=str]+[null]", - "'str'.concat('one', obj, 'two')": - "wrapped=['str' string=str]+['two' string=two]", - "'str'.concat('one', obj, 'two').concat()": - "wrapped=['str' string=str]+['two' string=two]", - "'str'.concat('one', obj, 'two').concat('three')": - "wrapped=['str' string=str]+['three' string=three]", - "'str'.concat(obj)": "wrapped=['str' string=str]+[null]", - "'str'.concat(obj).concat()": "wrapped=['str' string=str]+[null]", - "'str'.concat(obj).concat('one', 'two')": - "wrapped=['str' string=str]+['one', 'two' string=onetwo]", - "'str'.concat(obj).concat(obj, 'one')": - "wrapped=['str' string=str]+['one' string=one]", - "'str'.concat(obj).concat(obj, 'one', 'two')": - "wrapped=['str' string=str]+['one', 'two' string=onetwo]", - "'str'.concat(obj).concat('one', obj, 'one')": - "wrapped=['str' string=str]+['one' string=one]", - "'str'.concat(obj).concat('one', obj, 'two', 'three')": - "wrapped=['str' string=str]+['two', 'three' string=twothree]", - "'str'.concat(obj, 'one')": - "wrapped=['str' string=str]+['one' string=one]", - "'str'.concat(obj, 'one').concat()": - "wrapped=['str' string=str]+['one' string=one]", - "'str'.concat(obj, 'one').concat('two', 'three')": - "wrapped=['str' string=str]+['two', 'three' string=twothree]", - "'str'.concat(obj, 'one').concat(obj, 'two', 'three')": - "wrapped=['str' string=str]+['two', 'three' string=twothree]", - "'str'.concat(obj, 'one').concat('two', obj, 'three')": - "wrapped=['str' string=str]+['three' string=three]", - "'str'.concat(obj, 'one').concat('two', obj, 'three', 'four')": - "wrapped=['str' string=str]+['three', 'four' string=threefour]", - "'str'.concat(obj, 'one', 'two')": - "wrapped=['str' string=str]+['one', 'two' string=onetwo]", - "'str'.concat(obj, 'one', 'two').concat()": - "wrapped=['str' string=str]+['one', 'two' string=onetwo]", - "'str'.concat(obj, 'one', 'two').concat('three', 'four')": - "wrapped=['str' string=str]+['three', 'four' string=threefour]", - "'str'.concat(obj, 'one', 'two').concat(obj, 'three', 'four')": - "wrapped=['str' string=str]+['three', 'four' string=threefour]", - "'str'.concat(obj, 'one', 'two').concat('three', obj, 'four')": - "wrapped=['str' string=str]+['four' string=four]", - "'str'.concat(obj, 'one', 'two').concat('three', obj, 'four', 'five')": - "wrapped=['str' string=str]+['four', 'five' string=fourfive]", - // eslint-disable-next-line no-template-curly-in-string - "`start${obj}mid${obj2}end`": - "template=[start string=start],[mid string=mid],[end string=end]", - // eslint-disable-next-line no-template-curly-in-string - "`start${'str'}mid${obj2}end`": - // eslint-disable-next-line no-template-curly-in-string - "template=[start${'str'}mid string=startstrmid],[end string=end]", - "'abc'.substr(1)": "string=bc", - "'abcdef'.substr(2, 3)": "string=cde", - "'abcdef'.substring(2, 3)": "string=c", - "'abcdef'.substring(2, 3, 4)": "", - "'abc'[\"substr\"](1)": "string=bc", - "'abc'[substr](1)": "", - "'1,2+3'.split(/[,+]/)": "array=[1],[2],[3]", - "'1,2+3'.split(expr)": "", - "'a' + (expr + 'c')": "wrapped=['a' string=a]+['c' string=c]", - "1 + 'a'": "string=1a", - "'a' + 1": "string=a1", - "'a' + expr + 1": "wrapped=['a' string=a]+[1 string=1]" - }; - - Object.keys(testCases).forEach(key => { - function evalExprToString(evalExpr) { - if (!evalExpr) { - return "null"; - } else { - const result = []; - if (evalExpr.isString()) result.push("string=" + evalExpr.string); - if (evalExpr.isNumber()) result.push("number=" + evalExpr.number); - if (evalExpr.isBoolean()) result.push("bool=" + evalExpr.bool); - if (evalExpr.isRegExp()) result.push("regExp=" + evalExpr.regExp); - if (evalExpr.isConditional()) - result.push( - "options=[" + - evalExpr.options.map(evalExprToString).join("],[") + - "]" - ); - if (evalExpr.isArray()) - result.push( - "items=[" + evalExpr.items.map(evalExprToString).join("],[") + "]" - ); - if (evalExpr.isConstArray()) - result.push("array=[" + evalExpr.array.join("],[") + "]"); - if (evalExpr.isTemplateString()) - result.push( - "template=[" + - evalExpr.quasis.map(evalExprToString).join("],[") + - "]" - ); - if (evalExpr.isWrapped()) - result.push( - "wrapped=[" + - evalExprToString(evalExpr.prefix) + - "]+[" + - evalExprToString(evalExpr.postfix) + - "]" - ); - if (evalExpr.range) { - const start = evalExpr.range[0] - 5; - const end = evalExpr.range[1] - 5; - return ( - key.substr(start, end - start) + - (result.length > 0 ? " " + result.join(" ") : "") - ); - } - return result.join(" "); - } - } - - it("should eval " + key, () => { - const evalExpr = evaluateInParser(key); - expect(evalExprToString(evalExpr)).toBe( - testCases[key] ? key + " " + testCases[key] : key - ); - }); - }); - }); - - describe("async/await support", () => { - describe("should accept", () => { - const cases = { - "async function": "async function x() {}", - "async arrow function": "async () => {}", - "await expression": "async function x(y) { await y }", - "await iteration": "async function f() { for await (x of xs); }" - }; - const parser = new Parser(); - Object.keys(cases).forEach(name => { - const expr = cases[name]; - it(name, () => { - const actual = parser.parse(expr); - expect(typeof actual).toBe("object"); - }); - }); - }); - describe("should parse await", () => { - const cases = { - require: [ - "async function x() { await require('y'); }", - { - param: "y" - } - ], - import: [ - "async function x() { const y = await import('z'); }", - { - param: "z" - } - ] - }; - - const parser = new Parser(); - parser.hooks.call.tap("require", "ParserTest", expr => { - const param = parser.evaluateExpression(expr.arguments[0]); - parser.state.param = param.string; - }); - parser.hooks.importCall.tap("ParserTest", expr => { - const param = parser.evaluateExpression(expr.arguments[0]); - parser.state.param = param.string; - }); - - Object.keys(cases).forEach(name => { - it(name, () => { - const actual = parser.parse(cases[name][0]); - expect(actual).toEqual(cases[name][1]); - }); - }); - }); - }); - - describe("object rest/spread support", () => { - describe("should accept", () => { - const cases = { - "object spread": "({...obj})", - "object rest": "({...obj} = foo)" - }; - Object.keys(cases).forEach(name => { - const expr = cases[name]; - it(name, () => { - const actual = Parser.parse(expr); - expect(typeof actual).toBe("object"); - }); - }); - }); - }); - - describe("optional catch binding support", () => { - describe("should accept", () => { - const cases = { - "optional binding": "try {} catch {}" - }; - Object.keys(cases).forEach(name => { - const expr = cases[name]; - it(name, () => { - const actual = Parser.parse(expr); - expect(typeof actual).toBe("object"); - }); - }); - }); - }); -}); diff --git a/test/PersistentCaching.test.js b/test/PersistentCaching.test.js new file mode 100644 index 00000000000..79cecf9202a --- /dev/null +++ b/test/PersistentCaching.test.js @@ -0,0 +1,307 @@ +"use strict"; + +require("./helpers/warmup-webpack"); + +const fs = require("fs"); +const path = require("path"); +const util = require("util"); +const vm = require("vm"); +const rimraf = require("rimraf"); +const expectNoDeprecations = require("./helpers/expectNoDeprecations"); +const supportsObjectHasOwn = require("./helpers/supportsObjectHasOwn"); +const supportsOptionalChaining = require("./helpers/supportsOptionalChaining"); + +const readdir = util.promisify(fs.readdir); +const writeFile = util.promisify(fs.writeFile); +const utimes = util.promisify(fs.utimes); +const mkdir = util.promisify(fs.mkdir); + +describe("Persistent Caching", () => { + expectNoDeprecations(); + + const tempPath = path.resolve(__dirname, "js", "persistent-caching"); + const outputPath = path.resolve(tempPath, "output"); + const cachePath = path.resolve(tempPath, "cache"); + const srcPath = path.resolve(tempPath, "src"); + + const config = { + mode: "none", + context: tempPath, + cache: { + type: "filesystem", + buildDependencies: { + // avoid rechecking build dependencies + // for performance + // this is already covered by another test case + defaultWebpack: [] + }, + cacheLocation: cachePath + }, + experiments: { + css: true + }, + resolve: { + alias: { + "image.png": false, + "image1.png": false + } + }, + target: "node", + output: { + library: { type: "commonjs-module", export: "default" }, + path: outputPath, + // bundles are executed in this Node.js process; avoid `?.` on Node < 14 + // and `Object.hasOwn` on Node < 16.9 + environment: { + optionalChaining: supportsOptionalChaining(), + hasOwn: supportsObjectHasOwn() + } + } + }; + + beforeEach((done) => { + rimraf(tempPath, done); + }); + + const updateSrc = async (/** @type {Record} */ data) => { + const ts = new Date(Date.now() - 10000); + await mkdir(srcPath, { recursive: true }); + for (const key of Object.keys(data)) { + const p = path.resolve(srcPath, key); + await writeFile(p, data[key]); + await utimes(p, ts, ts); + } + }; + + const compile = async (/** @type {EXPECTED_ANY} */ configAdditions = {}) => + new Promise((resolve, reject) => { + const webpack = require("../"); + + webpack( + /** @type {import("../").Configuration} */ ( + /** @type {unknown} */ ({ + ...config, + ...configAdditions, + cache: { + ...config.cache, + .../** @type {EXPECTED_ANY} */ (configAdditions).cache + } + }) + ), + ( + /** @type {Error | null} */ err, + /** @type {import("../").Stats | undefined} */ _stats + ) => { + if (err) return reject(err); + const stats = /** @type {import("../").Stats} */ (_stats); + if (stats.hasErrors()) { + return reject(stats.toString({ preset: "errors-only" })); + } + resolve(stats); + } + ); + }); + + const getCacheFileTimes = async () => { + const cacheFiles = (await readdir(cachePath)).sort(); + return new Map( + cacheFiles.map((f) => [ + f, + fs.statSync(path.join(cachePath, f)).mtime.toString() + ]) + ); + }; + + const execute = () => { + /** @type {Record} */ + const cache = {}; + const require = (/** @type {string} */ name) => { + if (cache[name]) return cache[name].exports; + if (!name.endsWith(".js")) name += ".js"; + const p = path.resolve(outputPath, name); + const source = fs.readFileSync(p, "utf8"); + const context = {}; + const fn = + /** @type {(require: (name: string) => EXPECTED_ANY, module: { exports: unknown }, exports: unknown) => void} */ ( + /** @type {EXPECTED_ANY} */ (vm.runInThisContext)( + `(function(require, module, exports) { ${source} })`, + context, + { + filename: p + } + ) + ); + const m = { exports: /** @type {unknown} */ ({}) }; + cache[name] = m; + fn(require, m, m.exports); + return m.exports; + }; + return require("./main"); + }; + + it("should compile fine (warmup)", async () => { + const data = { + "index.js": `import file from "./file.js"; +export default 40 + file; +`, + "file.js": "export default 2;" + }; + await updateSrc(data); + await compile(); + expect(execute()).toBe(42); + }, 100000); + + it("should merge multiple small files", async () => { + const files = Array.from({ length: 30 }).map((_, i) => `file${i}.js`); + const data = { + "index.js": ` +import * as style from "./style.modules.css"; + +${files.map((f, i) => `import f${i} from "./${f}";`).join("\n")} + +export default ${files.map((_, i) => `f${i}`).join(" + ")}; +export { style }; +`, + "style.modules.css": `.class { + color: red; + background: url('image.png'); +}` + }; + for (const file of files) { + /** @type {Record} */ (data)[file] = "export default 1;"; + } + await updateSrc(data); + await compile({ cache: { compression: false } }); + expect(execute()).toBe(30); + for (let i = 0; i < 30; i++) { + updateSrc({ + [files[i]]: "export default 2;", + "style.modules.css": `.class-${i} { color: red; background: url('image1.png'); }` + }); + await compile({ cache: { compression: false } }); + expect(execute()).toBe(31 + i); + } + const cacheFiles = await readdir(cachePath); + expect(cacheFiles.length).toBeLessThan(20); + expect(cacheFiles.length).toBeGreaterThan(10); + }, 120000); + + it("should optimize unused content", async () => { + const data = { + "a.js": 'import "react-dom";', + "b.js": 'import "acorn";', + "c.js": 'import "core-js";', + "d.js": 'import "date-fns";', + "e.js": 'import "lodash";' + }; + await updateSrc(data); + const c = (/** @type {string} */ items) => { + /** @type {Record} */ + const entry = {}; + for (const item of items) entry[item] = `./src/${item}.js`; + return compile({ entry, cache: { compression: false } }); + }; + await c("abcde"); + await c("abc"); + await c("cde"); + await c("acd"); + await c("bce"); + await c("abcde"); + const cacheFiles = await readdir(cachePath); + expect(cacheFiles.length).toBeGreaterThan(4); + }, 120000); + + it("should allow persistent caching of container related objects", async () => { + const data = { + "index.js": + "export default import('container/src/exposed').then(m => m.default);", + "exposed.js": "import lib from 'lib'; export default 21 + lib;", + "lib.js": "export default 20", + "lib2.js": "export default 21" + }; + await updateSrc(data); + + const webpack = require("../"); + + const configAdditions = { + plugins: [ + new webpack.container.ModuleFederationPlugin({ + name: "container", + library: { type: "commonjs-module" }, + exposes: ["./src/exposed"], + remotes: { + container: ["./no-container", "./container"] + }, + shared: { + lib: { + import: "./src/lib", + shareKey: "lib", + version: "1.2.0", + requiredVersion: "^1.0.0" + }, + "./src/lib2": { + shareKey: "lib", + version: "1.2.3" + } + } + }) + ] + }; + await compile(configAdditions); + await expect(execute()).resolves.toBe(42); + await updateSrc({ + "exposed.js": "module.exports = { ok: true };" + }); + await compile(configAdditions); + await expect(execute()).resolves.toEqual({ ok: true }); + }, 120000); + + it("should not overwrite cache files if readonly = true", async () => { + await updateSrc({ + "main.js": ` +import { sum } from 'lodash'; + +sum([1,2,3]) + ` + }); + await compile({ entry: "./src/main.js" }); + const firstCacheFileTimes = await getCacheFileTimes(); + + await updateSrc({ + "main.js": ` +import 'lodash'; + ` + }); + await compile({ + entry: "./src/main.js", + cache: { + ...config.cache, + readonly: true + } + }); + await expect(getCacheFileTimes()).resolves.toEqual(firstCacheFileTimes); + }, 20000); + + it("should not invalidate cache files if timestamps changed with dynamic import()", async () => { + const configAdditions = { + entry: "./src/main.js", + snapshot: { + resolve: { hash: true }, + module: { hash: true }, + contextModule: { hash: true } + } + }; + await updateSrc({ + "newer.js": "export default 2;", + // eslint-disable-next-line no-template-curly-in-string + "main.js": 'const f = "newer.js"; import(`./${f}`);' + }); + await compile(configAdditions); + const firstCacheFileTimes = await getCacheFileTimes(); + + await utimes(path.resolve(srcPath, "newer.js"), new Date(), new Date()); + + await compile(configAdditions); + await expect(getCacheFileTimes()).resolves.toEqual(firstCacheFileTimes); + }, 20000); +}); diff --git a/test/Platform.unittest.js b/test/Platform.unittest.js new file mode 100644 index 00000000000..ea6fd6de82c --- /dev/null +++ b/test/Platform.unittest.js @@ -0,0 +1,44 @@ +"use strict"; + +const { applyWebpackOptionsDefaults, getNormalizedWebpackOptions } = + require("..").config; + +/** + * @param {EXPECTED_ANY} target target option + * @returns {EXPECTED_ANY} resolved platform target properties + */ +const getPlatform = (target) => { + const normalized = getNormalizedWebpackOptions( + /** @type {EXPECTED_ANY} */ ({ target }) + ); + return applyWebpackOptionsDefaults(/** @type {EXPECTED_ANY} */ (normalized)) + .platform; +}; + +describe("platform", () => { + describe("universal", () => { + it("is true for the universal target", () => { + expect(getPlatform("universal").universal).toBe(true); + }); + + it('is true for the ["web", "node"] target', () => { + expect(getPlatform(["web", "node"]).universal).toBe(true); + }); + + it("is true when the array target also spans a worker", () => { + expect(getPlatform(["web", "node", "webworker"]).universal).toBe(true); + }); + + it('is true for the ["webworker", "node"] target (worker is web)', () => { + expect(getPlatform(["webworker", "node"]).universal).toBe(true); + }); + + it("is false for a web-only target", () => { + expect(getPlatform("web").universal).toBe(false); + }); + + it("is false for a node-only target", () => { + expect(getPlatform("node").universal).toBe(false); + }); + }); +}); diff --git a/test/ProfilingPlugin.test.js b/test/ProfilingPlugin.test.js new file mode 100644 index 00000000000..6ab2d834901 --- /dev/null +++ b/test/ProfilingPlugin.test.js @@ -0,0 +1,88 @@ +"use strict"; + +require("./helpers/warmup-webpack"); + +const path = require("path"); +const fs = require("graceful-fs"); +const rimraf = require("rimraf"); + +// ProfilingPlugin intercepts the deprecated Compilation.hooks.normalModuleLoader +// hook; that deprecation is asserted in configCases/plugins/profiling-plugin. +describe("Profiling Plugin", () => { + jest.setTimeout(120000); + + it("should handle output path with folder creation", (done) => { + const webpack = require("../"); + + const outputPath = path.join(__dirname, "js/profilingPath"); + const finalPath = path.join(outputPath, "events.json"); + let counter = 0; + rimraf(outputPath, () => { + const startTime = process.hrtime(); + const compiler = webpack({ + context: __dirname, + entry: "./fixtures/a.js", + output: { + path: path.join(__dirname, "js/profilingOut") + }, + plugins: [ + new webpack.debug.ProfilingPlugin({ + outputPath: finalPath + }), + { + apply(compiler) { + const hook = compiler.hooks.make; + for (const { stage, order } of [ + { stage: 0, order: 1 }, + { stage: 1, order: 2 }, + { stage: -1, order: 0 } + ]) { + hook.tap( + { + name: "RespectStageCheckerPlugin", + stage + }, + // eslint-disable-next-line no-loop-func + () => { + expect(counter++).toBe(order); + } + ); + } + } + } + ], + experiments: { + backCompat: false + } + }); + compiler.run((err) => { + if (err) return done(err); + const testDuration = process.hrtime(startTime); + if (!fs.existsSync(outputPath)) { + return done(new Error("Folder should be created.")); + } + + const data = require(finalPath); + + const maxTs = data.reduce( + (/** @type {number} */ max, /** @type {EXPECTED_ANY} */ entry) => + Math.max(max, entry.ts), + 0 + ); + const minTs = data[0].ts; + const duration = maxTs - minTs; + expect(duration).toBeLessThan( + testDuration[0] * 1000000 + testDuration[1] / 1000 + ); + const cpuProfile = data.find( + (/** @type {EXPECTED_ANY} */ entry) => entry.name === "CpuProfile" + ); + expect(cpuProfile).toBeTypeOf("object"); + const profile = cpuProfile.args.data.cpuProfile; + expect(profile.startTime).toBeGreaterThanOrEqual(minTs); + expect(profile.endTime).toBeLessThanOrEqual(maxTs); + done(); + }); + }); + }); +}); diff --git a/test/ProfilingPlugin.unittest.js b/test/ProfilingPlugin.unittest.js index 0528c9ecb7d..a4745092e31 100644 --- a/test/ProfilingPlugin.unittest.js +++ b/test/ProfilingPlugin.unittest.js @@ -1,41 +1,53 @@ "use strict"; +const path = require("path"); const ProfilingPlugin = require("../lib/debug/ProfilingPlugin"); describe("Profiling Plugin", () => { - it("should persist the passed outpath", () => { + it("should persist the passed output path", () => { + const outputPath = path.join(__dirname, "invest_in_doge_coin"); const plugin = new ProfilingPlugin({ - outputPath: "invest_in_doge_coin" + outputPath }); - expect(plugin.outputPath).toBe("invest_in_doge_coin"); + expect(plugin.options.outputPath).toBe(outputPath); }); it("should handle no options", () => { - const plugin = new ProfilingPlugin(); - expect(plugin.outputPath).toBe("events.json"); + expect(() => { + // eslint-disable-next-line no-new + new ProfilingPlugin(); + }).not.toThrow(); }); it("should handle when unable to require the inspector", () => { + // @ts-expect-error intentionally calling without required argument const profiler = new ProfilingPlugin.Profiler(); return profiler.startProfiling(); }); it("should handle when unable to start a profiling session", () => { - const profiler = new ProfilingPlugin.Profiler({ - Session() { - throw new Error("Sean Larkin was here."); - } - }); + const profiler = new ProfilingPlugin.Profiler( + /** @type {EXPECTED_ANY} */ ({ + Session() { + throw new Error("Sean Larkin was here."); + } + }) + ); return profiler.startProfiling(); }); it("handles sending a profiling message when no session", () => { + // @ts-expect-error intentionally calling without required argument const profiler = new ProfilingPlugin.Profiler(); - return profiler.sendCommand("randy", "is a puppers"); + return profiler.sendCommand( + "randy", + /** @type {EXPECTED_OBJECT} */ (/** @type {unknown} */ ("is awesome")) + ); }); it("handles destroying when no session", () => { + // @ts-expect-error intentionally calling without required argument const profiler = new ProfilingPlugin.Profiler(); return profiler.destroy(); }); diff --git a/test/ProgressPlugin.test.js b/test/ProgressPlugin.test.js index bfa2691591a..834a037b820 100644 --- a/test/ProgressPlugin.test.js +++ b/test/ProgressPlugin.test.js @@ -1,40 +1,400 @@ "use strict"; +require("./helpers/warmup-webpack"); + const path = require("path"); -const MemoryFs = require("memory-fs"); -const webpack = require("../"); - -const createMultiCompiler = () => { - const compiler = webpack([ - { - context: path.join(__dirname, "fixtures"), - entry: "./a.js" +const _ = require("lodash"); +const { Volume, createFsFromVolume } = require("memfs"); +const webpack = require(".."); +const captureStdio = require("./helpers/captureStdio"); +const expectNoDeprecations = require("./helpers/expectNoDeprecations"); + +const createMultiCompiler = ( + /** @type {Record | undefined} */ progressOptions = undefined, + /** @type {Record | undefined} */ configOptions = undefined +) => { + const compiler = webpack( + Object.assign( + [ + { + context: path.join(__dirname, "fixtures"), + entry: "./a.js" + }, + { + context: path.join(__dirname, "fixtures"), + entry: "./b.js" + } + ], + configOptions + ) + ); + compiler.outputFileSystem = /** @type {import("../").OutputFileSystem} */ ( + /** @type {unknown} */ (createFsFromVolume(new Volume())) + ); + + new webpack.ProgressPlugin(progressOptions).apply(compiler); + + return compiler; +}; + +const createSimpleCompiler = ( + /** @type {Record | undefined} */ progressOptions = undefined +) => { + const compiler = webpack({ + context: path.join(__dirname, "fixtures"), + entry: "./a.js", + infrastructureLogging: { + debug: /Progress/ }, - { - context: path.join(__dirname, "fixtures"), - entry: "./b.js" - } - ]); - compiler.outputFileSystem = new MemoryFs(); + plugins: [ + new webpack.ProgressPlugin({ + activeModules: true, + ...progressOptions + }) + ] + }); + + compiler.outputFileSystem = /** @type {import("../").OutputFileSystem} */ ( + /** @type {unknown} */ (createFsFromVolume(new Volume())) + ); + return compiler; }; -describe("ProgressPlugin", function() { - it("should not contain NaN as a percentage when it is applied to MultiCompiler", function(done) { - const compiler = createMultiCompiler(); +const createSimpleCompilerWithCustomHandler = ( + /** @type {Record | undefined} */ options = undefined +) => { + const compiler = webpack({ + context: path.join(__dirname, "fixtures"), + entry: "./a.js" + }); - let percentage = 0; - new webpack.ProgressPlugin((p, msg, ...args) => { - percentage += p; - }).apply(compiler); + compiler.outputFileSystem = /** @type {import("../").OutputFileSystem} */ ( + /** @type {unknown} */ (createFsFromVolume(new Volume())) + ); + const logger = compiler.getInfrastructureLogger("custom test logger"); + new webpack.ProgressPlugin({ + activeModules: true, + ...options, + handler: (...args) => logger.status(args) + }).apply(compiler); - compiler.run(err => { + return compiler; +}; + +const getLogs = (/** @type {string} */ logsStr) => + logsStr.split(/\r/).filter((/** @type {string} */ v) => v !== " "); + +const runCompilerAsync = ( + /** @type {import("../").Compiler | import("../").MultiCompiler} */ compiler +) => + new Promise((resolve, reject) => { + compiler.run((/** @type {Error | null} */ err) => { if (err) { - throw err; + reject(err); } else { - expect(percentage).not.toBe(NaN); - done(); + resolve(undefined); } }); }); + +/** @typedef {{ toString(): string, toStringRaw(): string, restore(): void, data: string[], reset(): void }} CapturedStdio */ + +expectNoDeprecations(); + +describe("ProgressPlugin", () => { + /** @type {CapturedStdio} */ + let stderr; + /** @type {CapturedStdio} */ + let stdout; + + beforeEach(() => { + stderr = captureStdio(process.stderr, true); + stdout = captureStdio(process.stdout, true); + }); + + afterEach(() => { + // eslint-disable-next-line no-unused-expressions + stderr && stderr.restore(); + // eslint-disable-next-line no-unused-expressions + stdout && stdout.restore(); + }); + + const nanTest = + (/** @type {(...args: EXPECTED_ANY[]) => EXPECTED_ANY} */ createCompiler) => + () => { + const compiler = createCompiler(); + + return runCompilerAsync(compiler).then(() => { + expect(stderr.toString()).toContain("%"); + expect(stderr.toString()).not.toContain("NaN"); + }); + }; + + it( + "should not contain NaN as a percentage when it is applied to Compiler", + nanTest(createSimpleCompiler) + ); + + it( + "should not contain NaN as a percentage when it is applied to MultiCompiler", + nanTest(createMultiCompiler) + ); + + it( + "should not contain NaN as a percentage when it is applied to MultiCompiler (parallelism: 1)", + nanTest(() => createMultiCompiler(undefined, { parallelism: 1 })) + ); + + it("should start print only on call run/watch", (done) => { + const compiler = createSimpleCompiler(); + + const logs = getLogs(stderr.toString()); + expect(logs.join("")).toHaveLength(0); + + compiler.close(done); + }); + + it("should print profile information", () => { + const compiler = createSimpleCompiler({ + profile: true + }); + + return runCompilerAsync(compiler).then(() => { + const logs = getLogs(stderr.toString()); + + expect(logs).toContainEqual( + expect.stringMatching( + /\[webpack\.Progress\] {2}| {2}| \d+ ms module ids > DeterministicModuleIdsPlugin\n$/ + ) + ); + expect(logs).toContainEqual( + expect.stringMatching( + /\[webpack\.Progress\] {2}| \d+ ms building > \.\.\. entries \.\.\. dependencies \.\.\. modules\n$/ + ) + ); + expect(logs).toContainEqual( + expect.stringMatching(/\[webpack\.Progress\] \d+ ms building\n$/) + ); + expect(logs).toContainEqual( + expect.stringMatching( + /\[webpack\.Progress\] {2}| \d+ ms sealing > module ids\n$/ + ) + ); + expect(logs).toContainEqual( + expect.stringMatching(/\[webpack\.Progress\] \d+ ms sealing\n$/) + ); + }); + }); + + const monotonicTest = + (/** @type {(...args: EXPECTED_ANY[]) => EXPECTED_ANY} */ createCompiler) => + () => { + /** @type {{ value: number, text: string }[]} */ + const handlerCalls = []; + const compiler = createCompiler({ + handler: (/** @type {number} */ p, /** @type {string[]} */ ...args) => { + handlerCalls.push({ value: p, text: `${p}% ${args.join(" ")}` }); + } + }); + + return runCompilerAsync(compiler).then(() => { + let lastLine = handlerCalls[0]; + for (const line of handlerCalls) { + if (line.value < lastLine.value) { + throw new Error( + `Progress value is not monotonic increasing:\n${lastLine.text}\n${line.text}` + ); + } + lastLine = line; + } + }); + }; + + it( + "should have monotonic increasing progress", + monotonicTest(createSimpleCompiler) + ); + + it( + "should have monotonic increasing progress (multi compiler)", + monotonicTest(createMultiCompiler) + ); + + it( + "should have monotonic increasing progress (multi compiler, parallelism)", + monotonicTest((/** @type {Record} */ o) => + createMultiCompiler(o, { parallelism: 1 }) + ) + ); + + it("should not print lines longer than stderr.columns", () => { + const compiler = createSimpleCompiler(); + process.stderr.columns = 36; + + return runCompilerAsync(compiler).then(() => { + const logs = getLogs(stderr.toString()); + + expect(logs.length).toBeGreaterThan(20); + for (const log of logs) { + expect(log.length).toBeLessThanOrEqual(35); + } + // cspell:ignore mization nsPlugin + /** @type {EXPECTED_ANY} */ (expect(logs)).toContain( + "75% sealing ...mization ...nsPlugin", + "trims each detail string equally" + ); + expect(logs).toContain("92% sealing asset processing"); + expect(logs).toContain("100%"); + }); + }); + + it("should handle when stderr.columns is undefined", () => { + const compiler = createSimpleCompiler(); + + /** @type {EXPECTED_ANY} */ (process.stderr).columns = undefined; + return runCompilerAsync(compiler).then(() => { + const logs = getLogs(stderr.toString()); + + expect(logs.length).toBeGreaterThan(20); + expect( + /** @type {string} */ (_.maxBy(logs, "length")).length + ).not.toBeGreaterThan(40); + }); + }); + + it("should contain the new compiler hooks", () => { + const compiler = createSimpleCompiler(); + + /** @type {EXPECTED_ANY} */ (process.stderr).columns = undefined; + return runCompilerAsync(compiler).then(() => { + const logs = getLogs(stderr.toString()); + + expect(logs).toContain("4% setup normal module factory"); + expect(logs).toContain("5% setup context module factory"); + }); + }); + + it("should display all type of percentage when it is applied to SingleCompiler", () => { + const compiler = createSimpleCompiler({ + entries: true, + modules: true, + dependencies: true, + activeModules: true + }); + + process.stderr.columns = 70; + return runCompilerAsync(compiler).then(() => { + const logs = stderr.toString(); + + expect(logs).toEqual(expect.stringMatching(/\d+\/\d+ entries/)); + expect(logs).toEqual(expect.stringMatching(/\d+\/\d+ dependencies/)); + expect(logs).toEqual(expect.stringMatching(/\d+\/\d+ modules/)); + expect(logs).toEqual(expect.stringMatching(/\d+ active/)); + }); + }); + + it("should respect falsy boolean options", () => { + const plugin = new webpack.ProgressPlugin({ + modules: false, + dependencies: false, + entries: false, + activeModules: false, + profile: false + }); + + expect(plugin.showModules).toBe(false); + expect(plugin.showDependencies).toBe(false); + expect(plugin.showEntries).toBe(false); + expect(plugin.showActiveModules).toBe(false); + expect(plugin.profile).toBe(false); + }); + + it("should normalize progressBar option", () => { + expect(new webpack.ProgressPlugin({}).progressBar).toBe(false); + expect(new webpack.ProgressPlugin({ progressBar: false }).progressBar).toBe( + false + ); + expect( + new webpack.ProgressPlugin({ progressBar: true }).progressBar + ).toEqual({ name: "Build", color: "green" }); + expect( + new webpack.ProgressPlugin({ progressBar: { name: "Custom" } }) + .progressBar + ).toEqual({ name: "Custom", color: "green" }); + expect( + new webpack.ProgressPlugin({ progressBar: { color: "red" } }).progressBar + ).toEqual({ name: "Build", color: "red" }); + expect( + new webpack.ProgressPlugin({ + progressBar: { name: "Custom", color: "cyan" } + }).progressBar + ).toEqual({ name: "Custom", color: "cyan" }); + }); + + it("should render progress bar with block characters when enabled", () => { + const compiler = createSimpleCompiler({ + progressBar: { name: "TestBar", color: "magenta" } + }); + + process.stderr.columns = 70; + return runCompilerAsync(compiler).then(() => { + const logs = stderr.toString(); + expect(logs).toContain("â”"); + expect(logs).toContain("â—"); + expect(logs).toContain("TestBar"); + expect(logs).toEqual(expect.stringMatching(/\(\d+%\)/)); + }); + }); + + it("should render progress bar when applied to MultiCompiler", () => { + const compiler = createMultiCompiler({ + progressBar: { name: "MultiBar", color: "cyan" } + }); + + process.stderr.columns = 200; + return runCompilerAsync(compiler).then(() => { + const logs = stderr.toString(); + + expect(logs).toContain("â”"); + expect(logs).toContain("â—"); + expect(logs).toContain("MultiBar"); + expect(logs).toEqual(expect.stringMatching(/\(\d+%\)/)); + }); + }); + + it("should render progress bars for two parallel compilers", () => { + const compilerA = createSimpleCompiler({ + progressBar: { name: "BarA", color: "red" } + }); + const compilerB = createSimpleCompiler({ + progressBar: { name: "BarB", color: "blue" } + }); + + process.stderr.columns = 70; + return Promise.all([ + runCompilerAsync(compilerA), + runCompilerAsync(compilerB) + ]).then(() => { + const logs = stderr.toString(); + expect(logs).toContain("BarA"); + expect(logs).toContain("BarB"); + expect(logs).toContain("â”"); + expect(logs).toContain("â—"); + }); + }); + + it("should get the custom handler text from the log", () => { + const compiler = createSimpleCompilerWithCustomHandler(); + + process.stderr.columns = 70; + return runCompilerAsync(compiler).then(() => { + const logs = stderr.toString(); + expect(logs).toEqual( + expect.stringMatching(/\d+\/\d+ [custom test logger]/) + ); + expect(logs).toEqual(expect.stringMatching(/\d+ active/)); + expect(logs).toEqual(expect.stringMatching(/\d+\/\d+ modules/)); + }); + }); }); diff --git a/test/ProvideSharedDependency.unittest.js b/test/ProvideSharedDependency.unittest.js new file mode 100644 index 00000000000..31cb984030c --- /dev/null +++ b/test/ProvideSharedDependency.unittest.js @@ -0,0 +1,48 @@ +"use strict"; + +const ProvideSharedDependency = require("../lib/sharing/ProvideSharedDependency"); + +describe("ProvideSharedDependency", () => { + it("round-trips through serialization, keeping version and request distinct", () => { + const dep = new ProvideSharedDependency( + "scope", + "my-name", + "1.2.3", + "./my-request", + true + ); + + /** @type {unknown[]} */ + const buffer = []; + const writeCtx = + /** @type {import("../lib/serialization/ObjectMiddleware").ObjectSerializerContext} */ ( + /** @type {unknown} */ ({ + write(/** @type {unknown} */ v) { + buffer.push(v); + return writeCtx; + }, + setCircularReference() {} + }) + ); + dep.serialize(writeCtx); + + let i = 0; + const readCtx = + /** @type {import("../lib/serialization/ObjectMiddleware").ObjectDeserializerContext} */ ( + /** @type {unknown} */ ({ + read: () => buffer[i++], + get rest() { + return readCtx; + }, + setCircularReference() {} + }) + ); + const restored = ProvideSharedDependency.deserialize(readCtx); + + expect(restored.shareScope).toBe("scope"); + expect(restored.name).toBe("my-name"); + expect(restored.version).toBe("1.2.3"); + expect(restored.request).toBe("./my-request"); + expect(restored.eager).toBe(true); + }); +}); diff --git a/test/Queue.unittest.js b/test/Queue.unittest.js new file mode 100644 index 00000000000..97e941c10a0 --- /dev/null +++ b/test/Queue.unittest.js @@ -0,0 +1,58 @@ +"use strict"; + +const Queue = require("../lib/util/Queue"); + +describe("Queue", () => { + it("constructor", () => { + const q = new Queue(); + + q.enqueue("item1"); + q.enqueue("item2"); + q.enqueue("item3"); + + expect(q.dequeue()).toBe("item1"); + expect(q.dequeue()).toBe("item2"); + expect(q.dequeue()).toBe("item3"); + expect(q.dequeue()).toBeUndefined(); + + q.enqueue("item2"); + q.enqueue("item3"); + + expect(q.dequeue()).toBe("item2"); + expect(q.dequeue()).toBe("item3"); + expect(q.dequeue()).toBeUndefined(); + }); + + it("enqueue and dequeue", () => { + const q = new Queue(); + + q.enqueue("item1"); + + expect(q.dequeue()).toBe("item1"); + expect(q.dequeue()).toBeUndefined(); + + q.enqueue("item2"); + q.enqueue("item3"); + + expect(q.dequeue()).toBe("item2"); + expect(q.dequeue()).toBe("item3"); + expect(q.dequeue()).toBeUndefined(); + }); + + it("length", () => { + const q = new Queue(); + + q.enqueue("item1"); + q.enqueue("item2"); + + expect(q).toHaveLength(2); + + q.dequeue(); + + expect(q).toHaveLength(1); + + q.dequeue(); + + expect(q).toHaveLength(0); + }); +}); diff --git a/test/README.md b/test/README.md index f648f049744..f460a490b71 100644 --- a/test/README.md +++ b/test/README.md @@ -1,72 +1,85 @@ # Welcome to the webpack test suite!!!! + Every pull request that you submit to webpack (besides README and spelling corrections in comments) requires tests that are created. But don't give up hope!!! Although our tests may appear complex and overwhelming, once you become familiar with the test suite and structure, adding and creating tests will be fun and beneficial as you work inside the codebase! ⤠## tl;dr + Run all tests (this automatically runs the setup): + ```sh yarn test ``` Run an individual suite: + ```sh yarn jest ConfigTestCases ``` Watch mode: + ```sh yarn jest --watch ConfigTestCases ``` -See also: [Jest CLI docs](https://facebook.github.io/jest/docs/cli.html) +See also: [Jest CLI docs](https://jestjs.io/docs/cli) ## Test suite overview -We use Jest for our tests. For more information on Jest you can visit their [homepage](https://facebook.github.io/jest/)! + +We use Jest for our tests. For more information on Jest you can visit their [homepage](https://jestjs.io/)! ### Class Tests -All test files can be found in *.test.js. There are many tests that simply test API's of a specific class/file (such as `Compiler`, `Errors`, Integration, `Parser`, `RuleSet`, Validation). + +All test files can be found in \*.test.js. There are many tests that simply test APIs of a specific class/file (such as `Compiler`, `Errors`, Integration, `Parser`, `RuleSet`, Validation). If the feature you are contributing involves one of those classes, then best to start there to understand the structure. ### xCases -In addition to Class specific tests, there are also directories that end in "Cases". The suites for these cases also have corresponding *.test.js files. + +In addition to Class specific tests, there are also directories that end in "Cases". The suites for these cases also have corresponding \*.test.js files. #### cases (`TestCases.test.js`) 1 + Cases are a set of general purpose tests that will run against a variety of permutations of webpack configurations. When you are making a general purpose change that doesn't require you to have a special configuration, you would likely add your tests here. Inside of the `./test/cases` directory you will find tests are broken into thematic sub directories. Take a moment to explore the different options. To add a new case, create a new directory inside of the top level test groups, and then add an `index.js` file (and any other supporting files). By default this file will be the entry point for the test suite and you can add your `it()`'s there. This will also become bundled so that node env support happens as well. -#### configCases (`ConfigTestCases.test.js`) 1 +#### configCases (`ConfigTestCases.basictest.js`) 1 + If you are trying to solve a bug which is reproducible when x and y properties are used together in a config, then configCases is the place to be!!!! In addition to an `index.js`, these configCases require a `webpack.config.js` is located inside of your test suite. This will run this specific config through `webpack` just as you were building individually. They will use the same loading/bundling technique of your `it()` tests, however you now have a more specific config use cases that you can write even before you start coding. -#### statsCases (`StatsTestCases.test.js`) +#### statsCases (`StatsTestCases.basictest.js`) + Stats cases are similar to configCases except specifically focusing on the `expected` output of your stats. Instead of writing to the console, however the output of stats will be written to disk. By default, the "expected" outcome is a pain to write by hand so instead when statsCases are run, runner is checking output using jest's awesome snapshot functionality. -Basically you don't need to write any expected behaviors your self. The assumption is that the stats output from your test code is what you expect. +Basically you don't need to write any expected behaviors yourself. The assumption is that the stats output from your test code is what you expect. -Please follow the approach described bellow: +Please follow the approach described below: -* write your test code in ```statsCases/``` folder by creating a separate folder for it, for example -```statsCases/some-file-import-stats/index.js``` -``` - import(./someModule); +- write your test code in `statsCases/` folder by creating a separate folder for it, for example `statsCases/some-file-import-stats/index.js` + +```javascript +import("./someModule"); ``` -** dont's forget the ```webpack.config.js``` -* run the test -* jest will automatically add the output from your test code to ```StatsTestCases.test.js.snap``` and you can always check your results there -* Next time test will run -> runner will compare results against your output written to snapshot previously -You can read more about SnapShot testing [right here](https://facebook.github.io/jest/docs/en/snapshot-testing.html) +- don't forget the `webpack.config.js` +- run the test +- jest will automatically add the output from your test code to `StatsTestCases.test.js.snap` and you can always check your results there +- Next time test will run -> runner will compare results against your output written to snapshot previously + +You can read more about SnapShot testing [right here](https://jestjs.io/docs/snapshot-testing) ## Questions? Comments? -If you are still nervous or don't quite understand, please submit an issue and tag us in it, and provide a relevant PR while working on! +If you are still nervous or don't quite understand, please submit an issue and tag us in it, and provide a relevant PR while working on! ## Footnotes -1 webpack's parser supports the use of ES2015 features like arrow functions, harmony exports, etc. However as a library we follow NodeJS's timeline for dropping older versions of node. Because of this we expect your tests on Travis to pass all the way back to NodeJS v0.12; Therefore if you would like specific tests that use these features to be ignored if they are not supported, then you should add a `test.filter.js` file. This allows you to import the syntax needed for that test, meanwhile ignoring it on node versions (during CI) that don't support it. webpack has a variety of helpful examples you can refer to if you are just starting out. See the `./helpers` folder to find a list of the versions. + +1 webpack's parser supports the use of ES2015 features like arrow functions, harmony exports, etc. However as a library we follow Node.js' timeline for dropping older versions of node. Because of this we expect your tests on GitHub Actions to pass all the way back to NodeJS v10; Therefore if you would like specific tests that use these features to be ignored if they are not supported, then you should add a `test.filter.js` file. This allows you to import the syntax needed for that test, meanwhile ignoring it on node versions (during CI) that don't support it. webpack has a variety of helpful examples you can refer to if you are just starting out. See the `./helpers` folder to find a list of the versions. diff --git a/test/RawModule.unittest.js b/test/RawModule.unittest.js index 8d3e6b90f3a..35a680dfb39 100644 --- a/test/RawModule.unittest.js +++ b/test/RawModule.unittest.js @@ -1,11 +1,8 @@ "use strict"; +const path = require("path"); const RawModule = require("../lib/RawModule"); -const OriginalSource = require("webpack-sources").OriginalSource; -const RawSource = require("webpack-sources").RawSource; const RequestShortener = require("../lib/RequestShortener"); -const path = require("path"); -const crypto = require("crypto"); describe("RawModule", () => { const source = "sourceStr attribute"; @@ -36,49 +33,4 @@ describe("RawModule", () => { } ); }); - - describe("needRebuild", () => { - it("returns false", () => { - expect(myRawModule.needRebuild()).toBe(false); - }); - }); - - describe("source", () => { - it( - "returns a new OriginalSource instance with sourceStr attribute and " + - "return value of identifier() function provided as constructor arguments", - () => { - const originalSource = new OriginalSource( - myRawModule.sourceStr, - myRawModule.identifier() - ); - myRawModule.useSourceMap = true; - expect(myRawModule.source()).toEqual(originalSource); - } - ); - - it( - "returns a new RawSource instance with sourceStr attribute provided " + - "as constructor argument if useSourceMap is falsy", - () => { - const rawSource = new RawSource(myRawModule.sourceStr); - myRawModule.useSourceMap = false; - expect(myRawModule.source()).toEqual(rawSource); - } - ); - }); - - describe("updateHash", () => { - it("should include sourceStr in its hash", () => { - const hashModule = module => { - const hash = crypto.createHash("sha256"); - module.updateHash(hash); - return hash.digest("hex"); - }; - - const hashFoo = hashModule(new RawModule('"foo"')); - const hashBar = hashModule(new RawModule('"bar"')); - expect(hashFoo).not.toBe(hashBar); - }); - }); }); diff --git a/test/RemovedPlugins.unittest.js b/test/RemovedPlugins.unittest.js deleted file mode 100644 index 915ee0e116c..00000000000 --- a/test/RemovedPlugins.unittest.js +++ /dev/null @@ -1,18 +0,0 @@ -const webpack = require("../lib/webpack"); -const RemovedPluginError = require("../lib/RemovedPluginError"); - -describe("removed plugin errors", () => { - it("should error when accessing removed plugins", () => { - expect(() => webpack.optimize.UglifyJsPlugin).toThrow(RemovedPluginError); - expect( - () => webpack.optimize.UglifyJsPlugin - ).toThrowErrorMatchingSnapshot(); - - expect(() => webpack.optimize.CommonsChunkPlugin).toThrow( - RemovedPluginError - ); - expect( - () => webpack.optimize.CommonsChunkPlugin - ).toThrowErrorMatchingSnapshot(); - }); -}); diff --git a/test/RequestShortener.unittest.js b/test/RequestShortener.unittest.js new file mode 100644 index 00000000000..014c7be53af --- /dev/null +++ b/test/RequestShortener.unittest.js @@ -0,0 +1,22 @@ +"use strict"; + +const RequestShortener = require("../lib/RequestShortener"); + +describe("RequestShortener", () => { + it("should create RequestShortener and shorten with ./ file in directory", () => { + const shortener = new RequestShortener("/foo/bar"); + expect(shortener.shorten("/foo/bar/some.js")).toBe("./some.js"); + }); + + it("should create RequestShortener and shorten with ../ file in parent directory", () => { + const shortener = new RequestShortener("/foo/bar"); + expect(shortener.shorten("/foo/baz/some.js")).toBe("../baz/some.js"); + }); + + it("should create RequestShortener and not shorten parent directory neighbor", () => { + const shortener = new RequestShortener("/foo/bar"); + expect(shortener.shorten("/foo_baz/bar/some.js")).toBe( + "../../foo_baz/bar/some.js" + ); + }); +}); diff --git a/test/RuleSet.unittest.js b/test/RuleSet.unittest.js deleted file mode 100644 index c62b7bb64fb..00000000000 --- a/test/RuleSet.unittest.js +++ /dev/null @@ -1,483 +0,0 @@ -"use strict"; - -const RuleSet = require("../lib/RuleSet"); - -function match(ruleSet, resource) { - const result = ruleSet.exec({ - resource: resource - }); - return result - .filter(r => { - return r.type === "use"; - }) - .map(r => r.value) - .map(r => { - if (!r.options) return r.loader; - if (typeof r.options === "string") return r.loader + "?" + r.options; - return r.loader + "?" + JSON.stringify(r.options); - }); -} - -describe("RuleSet", () => { - it("should create RuleSet with a blank array", () => { - const loader = new RuleSet([]); - expect(loader.rules).toEqual([]); - }); - it("should create RuleSet and match with empty array", () => { - const loader = new RuleSet([]); - expect(match(loader, "something")).toEqual([]); - }); - it("should not match with loaders array", () => { - const loader = new RuleSet([ - { - test: /\.css$/, - loader: "css" - } - ]); - expect(match(loader, "something")).toEqual([]); - }); - - it("should match with regex", () => { - const loader = new RuleSet([ - { - test: /\.css$/, - loader: "css" - } - ]); - expect(match(loader, "style.css")).toEqual(["css"]); - }); - - it("should match with string", () => { - const loader = new RuleSet([ - { - test: "style.css", - loader: "css" - } - ]); - expect(match(loader, "style.css")).toEqual(["css"]); - }); - - it("should match with function", () => { - const loader = new RuleSet([ - { - test: function(str) { - return str === "style.css"; - }, - loader: "css" - } - ]); - expect(match(loader, "style.css")).toEqual(["css"]); - }); - - it("should throw if invalid test", () => { - expect(() => { - const loader = new RuleSet([ - { - test: { - invalid: "test" - }, - loader: "css" - } - ]); - match(loader, "style.css"); - }).toThrow(/Unexcepted property invalid in condition/); - }); - - it("should accept multiple test array that all match", () => { - const loader = new RuleSet([ - { - test: [/style.css/, /yle.css/], - loader: "css" - } - ]); - expect(match(loader, "style.css")).toEqual(["css"]); - }); - - it("should accept multiple test array that not all match", () => { - const loader = new RuleSet([ - { - test: [/style.css/, /something.css/], - loader: "css" - } - ]); - expect(match(loader, "style.css")).toEqual(["css"]); - }); - - it("should not match if include does not match", () => { - const loader = new RuleSet([ - { - test: /\.css$/, - include: /output.css/, - loader: "css" - } - ]); - expect(match(loader, "style.css")).toEqual([]); - }); - - it("should match if include matches", () => { - const loader = new RuleSet([ - { - test: /\.css$/, - include: /style.css/, - loader: "css" - } - ]); - expect(match(loader, "style.css")).toEqual(["css"]); - }); - - it("should not match if exclude matches", () => { - const loader = new RuleSet([ - { - test: /\.css$/, - exclude: /style.css/, - loader: "css" - } - ]); - expect(match(loader, "style.css")).toEqual([]); - }); - - it("should match if exclude does not match", () => { - const loader = new RuleSet([ - { - test: /\.css$/, - exclude: /output.css/, - loader: "css" - } - ]); - expect(match(loader, "style.css")).toEqual(["css"]); - }); - - it("should work if a loader is applied to all files", () => { - const loader = new RuleSet([ - { - loader: "css" - } - ]); - expect(match(loader, "style.css")).toEqual(["css"]); - expect(match(loader, "scripts.js")).toEqual(["css"]); - }); - - it("should work with using loader as string", () => { - const loader = new RuleSet([ - { - test: /\.css$/, - loader: "css" - } - ]); - expect(match(loader, "style.css")).toEqual(["css"]); - }); - - it("should work with using loader as array", () => { - const loader = new RuleSet([ - { - test: /\.css$/, - loader: ["css"] - } - ]); - expect(match(loader, "style.css")).toEqual(["css"]); - }); - - it("should work with using loaders as string", () => { - const loader = new RuleSet([ - { - test: /\.css$/, - loaders: "css" - } - ]); - expect(match(loader, "style.css")).toEqual(["css"]); - }); - - it("should work with using loaders as array", () => { - const loader = new RuleSet([ - { - test: /\.css$/, - loaders: ["css"] - } - ]); - expect(match(loader, "style.css")).toEqual(["css"]); - }); - - it("should throw if using loaders with non-string or array", () => { - expect(() => { - const loader = new RuleSet([ - { - test: /\.css$/, - loaders: { - someObj: true - } - } - ]); - match(loader, "style.css"); - }).toThrow(/No loader specified/); - }); - - it("should work with using loader with inline query", () => { - const loader = new RuleSet([ - { - test: /\.css$/, - loader: "css?modules=1" - } - ]); - expect(match(loader, "style.css")).toEqual(["css?modules=1"]); - }); - - it("should work with using loader with string query", () => { - const loader = new RuleSet([ - { - test: /\.css$/, - loader: "css", - query: "modules=1" - } - ]); - expect(match(loader, "style.css")).toEqual(["css?modules=1"]); - }); - - it("should work with using loader with object query", () => { - const loader = new RuleSet([ - { - test: /\.css$/, - loader: "css", - query: { - modules: 1 - } - } - ]); - expect(match(loader, "style.css")).toEqual(['css?{"modules":1}']); - }); - - it("should work with using array loaders with basic object notation", () => { - const loader = new RuleSet([ - { - test: /\.css$/, - loaders: [ - { - loader: "css" - } - ] - } - ]); - expect(match(loader, "style.css")).toEqual(["css"]); - }); - - it("should throw if using array loaders with object notation without specifying a loader", () => { - expect(() => { - const loader = new RuleSet([ - { - test: /\.css$/, - loaders: [ - { - stuff: 1 - } - ] - } - ]); - match(loader, "style.css"); - }).toThrow(/No loader specified/); - }); - - it("should work with using array loaders with object notation", () => { - const loader = new RuleSet([ - { - test: /\.css$/, - loaders: [ - { - loader: "css", - query: "modules=1" - } - ] - } - ]); - expect(match(loader, "style.css")).toEqual(["css?modules=1"]); - }); - - it("should work with using multiple array loaders with object notation", () => { - const loader = new RuleSet([ - { - test: /\.css$/, - loaders: [ - { - loader: "style", - query: "filesize=1000" - }, - { - loader: "css", - query: "modules=1" - } - ] - } - ]); - expect(match(loader, "style.css")).toEqual([ - "style?filesize=1000", - "css?modules=1" - ]); - }); - - it("should work with using string multiple loaders", () => { - const loader = new RuleSet([ - { - test: /\.css$/, - loaders: "style?filesize=1000!css?modules=1" - } - ]); - expect(match(loader, "style.css")).toEqual([ - "style?filesize=1000", - "css?modules=1" - ]); - }); - - it("should throw if using array loaders with a single legacy", () => { - expect(() => { - const loader = new RuleSet([ - { - test: /\.css$/, - loaders: ["style-loader", "css-loader"], - query: "modules=1" - } - ]); - match(loader, "style.css"); - }).toThrow(/options\/query cannot be used with loaders/); - }); - - it("should work when using array loaders", () => { - const loader = new RuleSet([ - { - test: /\.css$/, - loaders: ["style-loader", "css-loader"] - } - ]); - expect(match(loader, "style.css")).toEqual(["style-loader", "css-loader"]); - }); - - it("should work when using an array of functions returning a loader", () => { - const loader = new RuleSet([ - { - test: /\.css$/, - use: [ - function(data) { - return { - loader: "style-loader" - }; - }, - function(data) { - return { - loader: "css-loader" - }; - } - ] - } - ]); - expect(match(loader, "style.css")).toEqual(["style-loader", "css-loader"]); - }); - - it("should work when using an array of either functions or strings returning a loader", () => { - const loader = new RuleSet([ - { - test: /\.css$/, - use: [ - "style-loader", - function(data) { - return { - loader: "css-loader" - }; - } - ] - } - ]); - expect(match(loader, "style.css")).toEqual(["style-loader", "css-loader"]); - }); - - it("should work when using an array of functions returning either a loader object or loader name string", () => { - const loader = new RuleSet([ - { - test: /\.css$/, - use: [ - function(data) { - return "style-loader"; - }, - function(data) { - return { - loader: "css-loader" - }; - } - ] - } - ]); - expect(match(loader, "style.css")).toEqual(["style-loader", "css-loader"]); - }); - - it("should throw if using array loaders with invalid type", () => { - expect(() => { - const loader = new RuleSet([ - { - test: /\.css$/, - loaders: ["style-loader", "css-loader", 5] - } - ]); - match(loader, "style.css"); - }).toThrow(/No loader specified/); - }); - - describe("when exclude array holds an undefined item", () => { - function errorHasContext(err) { - return ( - /Expected condition but got falsy value/.test(err) && - /test/.test(err) && - /include/.test(err) && - /exclude/.test(err) && - /node_modules/.test(err) && - /undefined/.test(err) - ); - } - - it("should throw with context", () => { - try { - const loader = new RuleSet([ - { - test: /\.css$/, - loader: "css", - include: ["src"], - exclude: ["node_modules", undefined] - } - ]); - match(loader, "style.css"); - throw new Error("unreachable"); - } catch (e) { - expect(errorHasContext(e.message)).toBe(true); - } - }); - it("in resource should throw with context", () => { - try { - const loader = new RuleSet([ - { - resource: { - test: /\.css$/, - include: ["src"], - exclude: ["node_modules", undefined] - } - } - ]); - match(loader, "style.css"); - throw new Error("unreachable"); - } catch (e) { - expect(errorHasContext(e.message)).toBe(true); - } - }); - it("in issuer should throw with context", () => { - try { - const loader = new RuleSet([ - { - issuer: { - test: /\.css$/, - include: ["src"], - exclude: ["node_modules", undefined] - } - } - ]); - match(loader, "style.css"); - throw new Error("unreachable"); - } catch (e) { - expect(errorHasContext(e.message)).toBe(true); - } - }); - }); -}); diff --git a/test/RuntimeTemplate.unittest.js b/test/RuntimeTemplate.unittest.js new file mode 100644 index 00000000000..258244f0044 --- /dev/null +++ b/test/RuntimeTemplate.unittest.js @@ -0,0 +1,259 @@ +"use strict"; + +const RequestShortener = require("../lib/RequestShortener"); +const RuntimeTemplate = require("../lib/RuntimeTemplate"); + +/** @typedef {import("../lib/config/defaults").OutputNormalizedWithDefaults} OutputOptions */ + +describe("RuntimeTemplate.concatenation", () => { + it("no args", () => { + const runtimeTemplate = new RuntimeTemplate( + /** @type {import("../lib/Compilation")} */ ( + /** @type {unknown} */ (undefined) + ), + /** @type {OutputOptions} */ ( + /** @type {unknown} */ ({ environment: { templateLiteral: false } }) + ), + new RequestShortener(__dirname) + ); + expect(runtimeTemplate.concatenation()).toBe('""'); + }); + + it("1 arg", () => { + const runtimeTemplate = new RuntimeTemplate( + /** @type {import("../lib/Compilation")} */ ( + /** @type {unknown} */ (undefined) + ), + /** @type {OutputOptions} */ ( + /** @type {unknown} */ ({ environment: { templateLiteral: false } }) + ), + new RequestShortener(__dirname) + ); + expect( + runtimeTemplate.concatenation({ + expr: /** @type {string} */ (/** @type {unknown} */ (1)) + }) + ).toBe('"" + 1'); + expect(runtimeTemplate.concatenation("str")).toBe('"str"'); + }); + + it("es5", () => { + const runtimeTemplate = new RuntimeTemplate( + /** @type {import("../lib/Compilation")} */ ( + /** @type {unknown} */ (undefined) + ), + /** @type {OutputOptions} */ ( + /** @type {unknown} */ ({ environment: { templateLiteral: false } }) + ), + new RequestShortener(__dirname) + ); + + expect( + runtimeTemplate.concatenation({ expr: "__webpack__.p" }, "str/a") + ).toBe('__webpack__.p + "str/a"'); + expect( + runtimeTemplate.concatenation( + { expr: "__webpack__.p" }, + { expr: "str.a" }, + "str" + ) + ).toBe('"" + __webpack__.p + str.a + "str"'); + expect( + runtimeTemplate.concatenation("a", "b", { + expr: /** @type {string} */ (/** @type {unknown} */ (1)) + }) + ).toBe('"a" + "b" + 1'); + expect( + runtimeTemplate.concatenation( + "a", + { expr: /** @type {string} */ (/** @type {unknown} */ (1)) }, + "b" + ) + ).toBe('"a" + 1 + "b"'); + }); + + describe("es6", () => { + const runtimeTemplate = new RuntimeTemplate( + /** @type {import("../lib/Compilation")} */ ( + /** @type {unknown} */ (undefined) + ), + /** @type {OutputOptions} */ ( + /** @type {unknown} */ ({ environment: { templateLiteral: true } }) + ), + new RequestShortener(__dirname) + ); + + it("should prefer shorten variant #1", () => { + expect( + runtimeTemplate.concatenation( + { expr: /** @type {string} */ (/** @type {unknown} */ (1)) }, + "a", + { expr: /** @type {string} */ (/** @type {unknown} */ (2)) } + ) + ).toBe('1 + "a" + 2'); + }); + + it("should prefer shorten variant #2", () => { + expect( + runtimeTemplate.concatenation( + { expr: /** @type {string} */ (/** @type {unknown} */ (1)) }, + "a", + { expr: /** @type {string} */ (/** @type {unknown} */ (2)) }, + "b" + ) + ).toBe('1 + "a" + 2 + "b"'); + }); + + it("should prefer shorten variant #3", () => { + /* eslint-disable no-template-curly-in-string */ + expect( + runtimeTemplate.concatenation( + "a", + { expr: /** @type {string} */ (/** @type {unknown} */ (1)) }, + "b" + ) + ).toBe("`a${1}b`"); + /* eslint-enable */ + }); + }); +}); + +describe("RuntimeTemplate.optionalChaining", () => { + /** + * @param {boolean} optionalChaining whether the environment supports optional chaining + * @returns {RuntimeTemplate} runtime template + */ + const create = (optionalChaining) => + new RuntimeTemplate( + /** @type {import("../lib/Compilation")} */ ( + /** @type {unknown} */ (undefined) + ), + /** @type {OutputOptions} */ ( + /** @type {unknown} */ ({ environment: { optionalChaining } }) + ), + new RequestShortener(__dirname) + ); + + it("uses optional chaining when supported", () => { + const runtimeTemplate = create(true); + expect(runtimeTemplate.optionalChaining("obj", "prop")).toBe("obj?.prop"); + expect(runtimeTemplate.optionalChaining("fn", "()")).toBe("fn?.()"); + expect(runtimeTemplate.optionalChaining("obj", "method(arg)")).toBe( + "obj?.method(arg)" + ); + expect(runtimeTemplate.optionalChaining("obj", "[key]")).toBe("obj?.[key]"); + }); + + it("falls back to && when not supported", () => { + const runtimeTemplate = create(false); + expect(runtimeTemplate.optionalChaining("obj", "prop")).toBe( + "obj && obj.prop" + ); + expect(runtimeTemplate.optionalChaining("fn", "()")).toBe("fn && fn()"); + expect(runtimeTemplate.optionalChaining("obj", "method(arg)")).toBe( + "obj && obj.method(arg)" + ); + expect(runtimeTemplate.optionalChaining("obj", "[key]")).toBe( + "obj && obj[key]" + ); + }); +}); + +describe("RuntimeTemplate.method", () => { + /** + * @param {boolean} methodShorthand whether the environment supports method shorthand + * @param {boolean} arrowFunction whether the environment supports arrow functions + * @returns {RuntimeTemplate} runtime template + */ + const create = (methodShorthand, arrowFunction) => + new RuntimeTemplate( + /** @type {import("../lib/Compilation")} */ ( + /** @type {unknown} */ (undefined) + ), + /** @type {OutputOptions} */ ( + /** @type {unknown} */ ({ + environment: { methodShorthand, arrowFunction } + }) + ), + new RequestShortener(__dirname) + ); + + it("uses method shorthand when supported", () => { + const runtimeTemplate = create(true, true); + expect(runtimeTemplate.method("get", "name", "return name;")).toBe( + "get(name) {\n\treturn name;\n}" + ); + }); + + it("falls back to an arrow property when shorthand is unsupported", () => { + const runtimeTemplate = create(false, true); + expect(runtimeTemplate.method("get", "name", "return name;")).toBe( + "get: (name) => {\n\treturn name;\n}" + ); + }); + + it("falls back to a function property without arrow support", () => { + const runtimeTemplate = create(false, false); + expect(runtimeTemplate.method("get", "name", "return name;")).toBe( + "get: function(name) {\n\treturn name;\n}" + ); + }); +}); + +describe("RuntimeTemplate.objectHasOwn", () => { + /** + * @param {boolean} hasOwn whether the environment supports `Object.hasOwn` + * @returns {RuntimeTemplate} runtime template + */ + const create = (hasOwn) => + new RuntimeTemplate( + /** @type {import("../lib/Compilation")} */ ( + /** @type {unknown} */ (undefined) + ), + /** @type {OutputOptions} */ ( + /** @type {unknown} */ ({ environment: { hasOwn } }) + ), + new RequestShortener(__dirname) + ); + + it("uses Object.hasOwn when supported", () => { + expect(create(true).objectHasOwn("obj", "prop")).toBe( + "Object.hasOwn(obj, prop)" + ); + }); + + it("falls back to hasOwnProperty.call when not supported", () => { + expect(create(false).objectHasOwn("obj", "prop")).toBe( + "Object.prototype.hasOwnProperty.call(obj, prop)" + ); + }); +}); + +describe("RuntimeTemplate.assignOr", () => { + /** + * @param {boolean} logicalAssignment whether the environment supports logical assignment + * @returns {RuntimeTemplate} runtime template + */ + const create = (logicalAssignment) => + new RuntimeTemplate( + /** @type {import("../lib/Compilation")} */ ( + /** @type {unknown} */ (undefined) + ), + /** @type {OutputOptions} */ ( + /** @type {unknown} */ ({ environment: { logicalAssignment } }) + ), + new RequestShortener(__dirname) + ); + + it("uses ||= when supported", () => { + expect(create(true).assignOr("scope[name]", "{}")).toBe( + "scope[name] ||= {}" + ); + }); + + it("falls back to self-assignment when not supported", () => { + expect(create(false).assignOr("scope[name]", "{}")).toBe( + "scope[name] = scope[name] || {}" + ); + }); +}); diff --git a/test/Schemas.lint.js b/test/Schemas.lint.js deleted file mode 100644 index 50fff3badb6..00000000000 --- a/test/Schemas.lint.js +++ /dev/null @@ -1,144 +0,0 @@ -"use strict"; - -const fs = require("fs"); -const path = require("path"); -const glob = require("glob"); -const rootDir = path.resolve(__dirname, ".."); - -describe("Schemas", () => { - const schemas = glob.sync("schemas/**/*.json", { - cwd: rootDir - }); - - schemas.forEach(filename => { - describe(filename, () => { - let content; - let fileContent; - let errorWhileParsing; - - try { - fileContent = fs.readFileSync(path.resolve(rootDir, filename), "utf-8"); - content = JSON.parse(fileContent); - } catch (e) { - errorWhileParsing = e; - } - - it("should be parse-able", () => { - if (errorWhileParsing) throw errorWhileParsing; - }); - - if (content) { - it("should be formated correctly", () => { - expect(fileContent.replace(/\r\n?/g, "\n")).toBe( - JSON.stringify(content, 0, 2) + "\n" - ); - }); - - const arrayProperties = ["oneOf", "anyOf", "allOf"]; - const allowedProperties = [ - "definitions", - "$ref", - "id", - "items", - "properties", - "additionalProperties", - "type", - "oneOf", - "anyOf", - "allOf", - "absolutePath", - "description", - "enum", - "minLength", - "minimum", - "required", - "uniqueItems", - "minItems", - "minProperties", - "instanceof" - ]; - - const validateProperty = property => { - it("should have description set", () => { - expect(typeof property.description).toBe("string"); - expect(property.description.length).toBeGreaterThan(1); - }); - }; - - const walker = item => { - it("should only use allowed schema properties", () => { - const otherProperties = Object.keys(item).filter( - p => allowedProperties.indexOf(p) < 0 - ); - if (otherProperties.length > 0) { - throw new Error( - `The properties ${otherProperties.join( - ", " - )} are not allowed to use` - ); - // When allowing more properties make sure to add nice error messages for them in WebpackOptionsValidationError - } - }); - - if (Object.keys(item).indexOf("$ref") >= 0) { - it("should not have other properties next to $ref", () => { - const otherProperties = Object.keys(item).filter( - p => p !== "$ref" - ); - if (otherProperties.length > 0) { - throw new Error( - `When using $ref not other properties are possible (${otherProperties.join( - ", " - )})` - ); - } - }); - } - - arrayProperties.forEach(prop => { - if (prop in item) { - describe(prop, () => { - item[prop].forEach(walker); - }); - } - }); - if ("items" in item) { - describe("items", () => { - if (Object.keys(item).join() !== "$ref") { - validateProperty(item.items); - } - walker(item.items); - }); - } - if ("definitions" in item) { - Object.keys(item.definitions).forEach(name => { - describe(`#${name}`, () => { - walker(item.definitions[name]); - }); - }); - } - if ("properties" in item) { - it("should have additionalProperties set to some value when describing properties", () => { - expect(item.additionalProperties).toBeDefined(); - }); - Object.keys(item.properties).forEach(name => { - describe(`> '${name}'`, () => { - const property = item.properties[name]; - validateProperty(property); - walker(property); - }); - }); - } - if (typeof item.additionalProperties === "object") { - describe("properties", () => { - validateProperty(item.additionalProperties); - walker(item.additionalProperties); - }); - } - }; - - walker(content); - } - }); - }); -}); diff --git a/test/SemVer.unittest.js b/test/SemVer.unittest.js new file mode 100644 index 00000000000..34552ec628f --- /dev/null +++ b/test/SemVer.unittest.js @@ -0,0 +1,631 @@ +"use strict"; + +const { + parseRange, + parseVersion, + parseVersionRuntimeCode, + rangeToString, + rangeToStringRuntimeCode, + satisfy, + satisfyRuntimeCode, + versionLt, + versionLtRuntimeCode +} = require("../lib/util/semver"); + +describe("SemVer", () => { + const createRuntimeFunction = ( + /** @type {(helpers: unknown) => string} */ runtimeCodeFunction + ) => { + const runtimeFunction = runtimeCodeFunction({ + basicFunction: ( + /** @type {string} */ args, + /** @type {string[]} */ body + ) => `(${args}) => {\n${body.join("\n")}\n}`, + supportsArrowFunction: () => true + }); + const functionName = /** @type {RegExpMatchArray} */ ( + runtimeFunction.match(/^var (\w+)/) + )[1]; + return eval( + `(function (...args) { ${runtimeFunction}; return ${functionName}(...args); })` + ); + }; + + for (const [name, fn] of [ + ["normal", parseVersion], + [ + "runtime", + createRuntimeFunction( + /** @type {(helpers: unknown) => string} */ ( + /** @type {unknown} */ (parseVersionRuntimeCode) + ) + ) + ] + ]) { + it(`should parseVersion correctly (${name})`, () => { + expect(fn("1")).toEqual([1]); + expect(fn("1.2.3")).toEqual([1, 2, 3]); + expect(fn("1.2.3.4.999")).toEqual([1, 2, 3, 4, 999]); + // eslint-disable-next-line no-sparse-arrays + expect(fn("1.2.3-beta")).toEqual([1, 2, 3, , "beta"]); + // eslint-disable-next-line no-sparse-arrays + expect(fn("1.2.3-beta.1.2")).toEqual([1, 2, 3, , "beta", 1, 2]); + /* eslint-disable no-sparse-arrays */ + expect(fn("1.2.3-alpha.beta-42")).toEqual([ + 1, + 2, + 3, + , + "alpha", + "beta-42" + ]); + expect(fn("1.2.3-beta.1.alpha.0+5343")).toEqual([ + 1, + 2, + 3, + , + "beta", + 1, + "alpha", + 0, + [], + 5343 + ]); + /* eslint-enable no-sparse-arrays */ + expect(fn("1.2.3+5343.beta+1")).toEqual([1, 2, 3, [], 5343, "beta+1"]); + expect(fn("1.2.3+5343.beta+1")).toEqual([1, 2, 3, [], 5343, "beta+1"]); + }); + } + + describe("versionLt", () => { + const cases = [ + "1 < 2", + "99 < 100", + "1 < 1.2", + "1 < 1.2.3", + "1.2 < 1.2.3", + "1.2.2 < 1.2.3", + "1.1.3 < 1.2.0", + "1.1.3 < 2.0.0", + "1.1.3 < 2", + "1.1.3 < 2.0", + "1.2.3 < 1.2.3+0", + "1.2.3+23 < 1.2.3+123", + "1.2+2 < 1.2.3+1", + "1.2.3-beta < 1.2.3", + "1.2.3 < 1.2.4-beta", + "1.2.3 < 1.3.0-beta", + "1.2.3 < 2.0.0-beta", + "1.2.3-alpha < 1.2.3-beta", + "1.2.3-beta < 1.2.3.1", + "1.2.3-beta < 1.2.3-beta.0", + "1.2.3-beta.0 < 1.2.3-beta.1", + "1.2.3-0 < 1.2.3-beta", + "1.2.3-beta < 1.2.3-beta+123", + "1.2.3-beta+123 < 1.2.3-beta+234", + "1.2.3-beta+99 < 1.2.3-beta+111", + "1.2.3-beta < 1.2.3+1", + "1.0.0-alpha < 1.0.0-alpha.1", + "1.0.0-alpha.1 < 1.0.0-alpha.beta", + "1.0.0-alpha.beta < 1.0.0-beta", + "1.0.0-beta < 1.0.0-beta.2", + "1.0.0-beta.2 < 1.0.0-beta.11", + "1.0.0-beta.11 < 1.0.0-rc.1", + "1.0.0-rc.1 < 1.0.0", + "2.2 < 2.beta", + "2.2 < 2.beta.1", + "2.2.1 < 2.beta.1", + "2.2.1 < 2.beta", + "2.2.3 < 2.beta.1", + "2.alpha < 2.beta", + "2.alpha.1 < 2.beta", + "2.alpha.1 < 2.beta.1", + "2.alpha < 2.beta.1" + ]; + for (const c of cases) { + const parts = c.split(" < "); + const a = parts[0]; + const b = parts[1]; + + for (const [name, fn] of [ + ["normal", versionLt], + [ + "runtime", + createRuntimeFunction( + /** @type {(helpers: unknown) => string} */ ( + /** @type {unknown} */ (versionLtRuntimeCode) + ) + ) + ] + ]) { + it(`${c} (${name})`, () => { + expect(fn(a, a)).toBe(false); + expect(fn(b, b)).toBe(false); + expect(fn(a, b)).toBe(true); + expect(fn(b, a)).toBe(false); + }); + } + } + }); + + describe("parseRange", () => { + /** @type {Record} */ + const cases = { + "5 || 6 || 7.x.x": ["5.x.x || 6.x || 7"], + "1 - 2": ["1 - 2"], + "=3": [ + "3", + "v3", + "3.x", + "3.X", + "3.x.x", + "3.*", + "3.*.*", + "^3", + "^3.x", + "= 3" + ], + "=3.0": ["3.0", "v3.0", "3.0.x", "3.0.X", "3.0.*", "~3.0", "= 3.0"], + "^3.4": ["^3.4.*", "^ 3.4"], + "3.4 - 6.5": [">=3.4 <=6.5", ">= 3.4 <= 6.5"], + "<=3.4": ["<3.4 || =3.4", "<= 3.4"], + ">3.4": [">=3.4 !3.4", "> 3.4"], + "1.2.3-alpha.x.x": ["1.2.3-alpha", "1.2.3-alpha+build.25"], + "1.2.3-NaN": ["1.2.3-NaN"] + }; + for (const key of Object.keys(cases)) { + describe(key, () => { + for (const c of cases[key]) { + it(`should be equal ${c}`, () => { + expect(parseRange(c)).toEqual(parseRange(key)); + }); + } + }); + } + }); + + describe("rangeToString", () => { + /** @type {Record} */ + const cases = { + "*": "*", + 1: "^1", + 1.2: "~1.2", + "1.2.3": "=1.2.3", + "^1.2.3": "^1.2.3", + "~1.2.3": "~1.2.3", + "0.0.1": "=0.0.1", + "^0.0.1": "=0.0.1", + "^0.1.2": "~0.1.2", + "~0.0.1": "~0.0.1", + "~0.1.2": "~0.1.2", + ">=1.2.3": ">=1.2.3", + "1.2.3-beta.25": "=1.2.3-beta.25", + "1.2.3-beta.25+12.34": "=1.2.3-beta.25", + "1.2.3+12.34": "=1.2.3", + ">=1.2.3-beta.25": ">=1.2.3-beta.25", + ">=1.2.3-beta.25+12.34": ">=1.2.3-beta.25", + ">=1.2.3+12.34": ">=1.2.3", + "<1.2.3-beta.25": "<1.2.3-beta.25", + "<1.2.3-beta.25+12.34": "<1.2.3-beta.25", + "<1.2.3+12.34": "<1.2.3", + "1.2.3 - 3.2.1": ">=1.2.3 (<3.2.1 || =3.2.1)", + ">3.4": ">=3.4 not(~3.4)", + "1 || 2 || 3": "^1 || ^2 || ^3", + "1.2.3 - 3.2.1 || >3 <=4 || 1": + ">=1.2.3 (<3.2.1 || =3.2.1) || >=3 not(^3) (<4 || ^4) || ^1" + }; + + for (const key of Object.keys(cases)) { + const expected = cases[key]; + + for (const [name, fn] of [ + ["normal", rangeToString], + [ + "runtime", + createRuntimeFunction( + /** @type {(helpers: unknown) => string} */ ( + /** @type {unknown} */ (rangeToStringRuntimeCode) + ) + ) + ] + ]) { + it(`should ${key} stringify to ${expected} (${name})`, () => { + expect(fn(parseRange(key))).toEqual(expected); + }); + } + } + }); + + describe("satisfies", () => { + /** @type {Record} */ + const cases = { + // table cases + ">=1": [ + "1", + "2", + "!1-beta", + "!2-beta", + "1.2", + "!1.2-beta", + "2.2", + "!2.2-beta", + "1.beta", + "!1.beta-beta", + "!2.beta-beta" + ], + ">=1-beta": [ + "1", + "2", + "1-beta", + "1-gamma", + "!1-alpha", + "!2-beta", + "1.2", + "!1.2-beta", + "2.2", + "!2.2-beta", + "1.beta", + "!1.beta-beta", + "2.beta", + "!2.beta-beta" + ], + ">=1.2": [ + "!1", + "2", + "!1-beta", + "!2-beta", + "!1.1", + "1.2", + "1.3", + "2.1", + "2.2", + "2.3", + "1.beta", + "2.beta" + ], + "~1.2": [ + "!1", + "!2", + "!10", + "!1-beta", + "!2-beta", + "!1.1", + "1.2", + "!1.3", + "!1.20" + ], + "~1": [ + "1", + "1.1", + "1.2", + "1.2.1", + "1.0.1", + "1.1.1", + "1.3.0", + "!2.0.0", + "!2.3.4", + "!1.0.0-beta", + "!1.1.0-beta" + ], + ">=1.beta": [ + "!1", + "2", + "!1-beta", + "!2-beta", + "!1.2", + "2.2", + "!1.0", + "!1.100", + "!1.alpha", + "1.beta", + "1.gamma", + "2.beta" + ], + // fixed cases + 2: [ + "2", + "2.0.0", + "2.99.99", + "!2.3.4-beta", + "!2.3.4-beta.1", + "!2.3.4-beta+123", + "2.3.4+123", + "!1", + "!1.2.3", + "!3", + "!3.4.5" + ], + "1.2.3-beta.1.2+33.44": [ + "1.2.3-beta.1.2+33.44", + "1.2.3-beta.1.2+22", + "1.2.3-beta.1.2+0", + "1.2.3-beta.1.2", + "1.2.3-beta.1.2+33.44.55", + "!1.2.3-beta.1.2.3+33.44", + "!1.2.3.4-beta.1.2+33.44", + "1.2.3-beta.1.2+33", + "1.2.3-beta.1.2", + "!1.2.3-beta", + "!1.2-beta.1.2+33.44", + "!1.2.3+33.44", + "!1.2.3", + "!1" + ], + "1.2.3+33.44": [ + "1.2.3+33.44", + "!1.2.4+33.44", + "1.2.3+22", + "1.2.3+33.55", + "!1.2.3-beta+33.44", + "1.2.3+33.44.55", + "1.2.3+33", + "!1.2+33.44", + "!1.2.3.4+33.44", + "1.2.3", + "!1.2.4", + "!1.3", + "!1", + "!2" + ], + "1.2.3-beta.1.2": [ + "1.2.3-beta.1.2", + "1.2.3-beta.1.2+33", + "!1.2.3-beta.1.2.3", + "!1.2.3.4-beta.1.2", + "!1.2.3-beta", + "!1.2-beta.1.2", + "!1.2.3+33", + "!1.2.3", + "!1" + ], + "^2.3.4": [ + "2.3.4", + "2.3.5", + "2.4.0", + "!3.3.4", + "!1.5.6", + "!2.3.3", + "!2.3.4-beta", + "!2.3.5-beta", + "2.3.4.test", + "2.3.test", + "!2.3-test", + "2.3.4+33", + "2.3.5+33", + "2.4.0+33", + "2.3.4.5", + "2.3.beta", + "2.3.beta.1", + "2.beta" + ], + "^2.beta.4": [ + "2.beta.4", + "!2.3.4", + "2.beta.alpha", + "2.beta.alpha+gamma", + "!2.beta-4" + ], + "~2": [ + "2.0.0", + "2.1.0", + "2.1.1", + "2.2.0", + "!1.0.0", + "2.0.1", + "2.1.2", + "2.3.0", + "!3.0.0", + "!3.6.8", + "!2.0.0-beta", + "!2.1.0-beta" + ], + "~2.3.4": [ + "2.3.4", + "2.3.5", + "!2.4.0", + "!3.3.4", + "!1.5.6", + "!2.3.3", + "!2.3.4-beta", + "!2.3.5-beta", + "2.3.4.test", + "2.3.test", + "!2.3-test", + "2.3.4+33", + "2.3.5+33", + "!2.4.0+33", + "2.3.4.5" + ], + "~1.2.0-beta": [ + "1.2.0-beta", + "1.2.0-beta+1", + "1.2.0-rc.0", + "1.2.0", + "1.2.1", + "!1.2.0-alpha", + "!1.2.0-0", + "!1.2.0-100", + "!1.2.1-beta", + "!1.3.0-beta", + "!1.3.0" + ], + "!2.3": [ + "!2.3", + "!2.3.4", + "2.2", + "2.2.2", + "2.4", + "2.4.4", + "2.3-beta", + "2.3.4-beta" + ], + "<2.3": [ + "!2.3", + "!2.3.4", + "2", + "2.2", + "2.2.1", + "1.5", + "0.1", + "!2.2-beta", + "!2.3-beta", + "!2.3-0" + ], + "<4.5-beta.14": [ + "4.5-beta.13", + "!4.5-beta.14", + "!4.5-beta.15", + "!4.5-beta.14.1", + "4.5-beta.13.1", + "4.5-beta.13+15", + "!4.5-beta.14+15", + "4.5-0", + "4.5-100", + "4.5-alpha", + "!4.5-gamma" + ], + "2.3 - 4.5": [ + "2.3", + "2.4", + "!2.3-beta", + "4.5", + "3.0.0", + "!3.5.7-beta.1", + "4.4", + "4.5", + "4.5.1", + "!4.5.2-beta", + "4.5+123" + ], + ">7.8-beta.4": [ + "!7.8-beta.3", + "!7.8-beta.4", + "!7.8-beta.4+55", + "7.8-beta.4.1", + "7.8-beta.5", + "7.8-beta.5.1", + "7.8-gamma", + "!7.8-alpha", + "7.8", + "7.8.0", + "7.8.1", + "7.9", + "8.1", + "10" + ], + "^0.0.3": [ + "!0.0.2", + "0.0.3", + "!0.0.4", + "!0.1.0", + "!0.1.3", + "!1.1.3", + "!1.0.0" + ], + "^0.3.3": [ + "!0.0.3", + "!0.3.2", + "0.3.3", + "0.3.4", + "!0.4.0", + "!0.4.3", + "!0.5.10", + "!1.0.0", + "!1.3.3" + ], + ">=1.0.0+42": [ + "1.0.0+42", + "!1.0+42", + "!1.0+43", + "1.0.0+43", + "1.0.0+5", + "1.0.0+100", + "2.0.0+10", + "1.0.0", + "!1.0.0-beta" + ], + "<1.0.1+42": [ + "!1.0.1+42", + "!1.0.1+43", + "!1.0.1+9", + "!1.0.1+5", + "!1.0.1+100", + "!2.0.0+10", + "!1.0.1", + "1.0.0", + "1.0.0+0", + "1.0.0+9999", + "0.5.0", + "!1.0.1-beta" + ], + "=1.0.0+42": [ + "1.0.0+42", + "!1.0+42", + "1.0.0+43", + "1.0.0+9", + "1.0.0+5", + "1.0.0+100", + "!2.0.0+10", + "1.0.0", + "!0.5.0", + "!1.0.0-beta" + ], + "!1.0.1+42": [ + "!1.0.1+42", + "!1.0.1+43", + "!1.0.1+9", + "!1.0.1+5", + "!1.0.1+100", + "2.0.0+10", + "!1.0.1", + "1.0.0", + "1.0.2", + "0.5.0", + "1.0.1-beta" + ], + "*": [ + "0.0.0", + "0.0.1", + "0.1.0", + "1.0.0", + "!1.0.0-beta", + "!1.0.0-beta.1", + "1.0.0+55" + ] + }; + + for (const range of Object.keys(cases)) { + describe(range, () => { + it(`should be able to parse ${range}`, () => { + parseRange(range); + }); + + for (const item of cases[range]) { + for (const [name, fn] of [ + ["normal", satisfy], + [ + "runtime", + createRuntimeFunction( + /** @type {(helpers: unknown) => string} */ ( + /** @type {unknown} */ (satisfyRuntimeCode) + ) + ) + ] + ]) { + if (item.startsWith("!")) { + it(`should not be satisfied by ${item.slice( + 1 + )} (${name})`, () => { + expect(fn(parseRange(range), item.slice(1))).toBe(false); + }); + } else { + it(`should be satisfied by ${item} (${name})`, () => { + expect(fn(parseRange(range), item)).toBe(true); + }); + } + } + } + }); + } + }); +}); diff --git a/test/SharingUtil.unittest.js b/test/SharingUtil.unittest.js new file mode 100644 index 00000000000..7f3c03ad301 --- /dev/null +++ b/test/SharingUtil.unittest.js @@ -0,0 +1,1006 @@ +"use strict"; + +const ProvideSharedModule = require("../lib/sharing/ProvideSharedModule"); +const { normalizeVersion } = require("../lib/sharing/utils"); +const { makePathsRelative } = require("../lib/util/identifier"); + +describe("ProvideSharedModule identifier", () => { + it("normalizes the request path across different build roots", () => { + const a = new ProvideSharedModule( + "default", + "x", + "1.0.0", + "/runner-a/project/node_modules/x/index.js", + true + ).identifier(); + const b = new ProvideSharedModule( + "default", + "x", + "1.0.0", + "/runner-b/project/node_modules/x/index.js", + true + ).identifier(); + // After makePathsRelative the two identifiers must match so that + // DeterministicModuleIdsPlugin assigns the same id for the same + // shared module regardless of the absolute project root. + expect(makePathsRelative("/runner-a/project", a)).toBe( + makePathsRelative("/runner-b/project", b) + ); + }); +}); + +describe("normalize dep version", () => { + const commonInvalid = [ + "https://github.com#v1.0", + "git://github.com#v1.0", + "other:github.com/foo/bar#v1.0", + "::", + "", + null, + undefined + ]; + + const commonValid = { + "git+ssh://git@github.com:npm/cli.git#v1.0.27": "v1.0.27", + "git+ssh://git@github.com:npm/cli#semver:^5.0": "^5.0", + "git://github.com/npm/cli.git#v1.0.27": "v1.0.27", + "git+https://isaacs@github.com/npm/cli.git": "", + "http://github.com/npm/cli.git#v1.0": "v1.0", + // for uppercase + "http://GITHUB.com/npm/cli.git#v1.0": "v1.0", + "HTTP://github.com/npm/cli.git#v1.0": "v1.0", + "FILE://foo/bar": "", + "file://foo/bar": "", + "v1.2": "v1.2", + "^1.2.0": "^1.2.0", + "git://localhost:12345/foo/bar#v1.0": "v1.0", + "localhost:foo/bar#v1.0": "v1.0" + }; + + const githubInvalid = [ + // foo/bar shorthand but specifying auth + "user@foo/bar#v1.0", + "user:password@foo/bar#v1.0", + ":password@foo/bar#v1.0", + // foo/bar shorthand but with a space in it + "foo/ bar#v1.0", + // string that ends with a slash, probably a directory + "foo/bar/#v1.0", + // git@github.com style, but omitting the username + "github.com:foo/bar#v1.0", + "github.com/foo/bar#v1.0", + // invalid URI encoding + "github:foo%0N/bar#v1.0", + // missing path + "git+ssh://git@github.com:#v1.0", + // a deep url to something we don't know + "https://github.com/foo/bar/issues#v1.0" + ]; + + const githubValid = { + // extreme shorthand (only for github) + "foo/bar": "", + "foo/bar#branch": "branch", + "foo/bar#v1.0": "v1.0", + "foo/bar.git": "", + "foo/bar.git#v1.0": "v1.0", + + // shortcuts + // + // NOTE auth is accepted but ignored + "github:foo/bar": "", + "github:foo/bar#v1.0": "v1.0", + "github:user@foo/bar": "", + "github:user@foo/bar#v1.0": "v1.0", + "github:user:password@foo/bar": "", + "github:user:password@foo/bar#v1.0": "v1.0", + "github::password@foo/bar": "", + "github::password@foo/bar#v1.0": "v1.0", + + "github:foo/bar.git": "", + "github:foo/bar.git#v1.0": "v1.0", + "github:user@foo/bar.git": "", + "github:user@foo/bar.git#v1.0": "v1.0", + "github:user:password@foo/bar.git": "", + "github:user:password@foo/bar.git#v1.0": "v1.0", + "github::password@foo/bar.git": "", + "github::password@foo/bar.git#v1.0": "v1.0", + + // NOTE auth is accepted and respected + "git://github.com/foo/bar": "", + "git://github.com/foo/bar#v1.0": "v1.0", + "git://user@github.com/foo/bar": "", + "git://user@github.com/foo/bar#v1.0": "v1.0", + "git://user:password@github.com/foo/bar": "", + "git://user:password@github.com/foo/bar#v1.0": "v1.0", + "git://:password@github.com/foo/bar": "", + "git://:password@github.com/foo/bar#v1.0": "v1.0", + + "git://github.com/foo/bar.git": "", + "git://github.com/foo/bar.git#v1.0": "v1.0", + "git://git@github.com/foo/bar.git": "", + "git://git@github.com/foo/bar.git#v1.0": "v1.0", + "git://user:password@github.com/foo/bar.git": "", + "git://user:password@github.com/foo/bar.git#v1.0": "v1.0", + "git://:password@github.com/foo/bar.git": "", + "git://:password@github.com/foo/bar.git#v1.0": "v1.0", + + // no-protocol git+ssh + // + // NOTE auth is _required_ (see invalid list) but ignored + "user@github.com:foo/bar": "", + "user@github.com:foo/bar#v1.0": "v1.0", + "user:password@github.com:foo/bar": "", + "user:password@github.com:foo/bar#v1.0": "v1.0", + ":password@github.com:foo/bar": "", + ":password@github.com:foo/bar#v1.0": "v1.0", + + "user@github.com:foo/bar.git": "", + "user@github.com:foo/bar.git#v1.0": "v1.0", + "user:password@github.com:foo/bar.git": "", + "user:password@github.com:foo/bar.git#v1.0": "v1.0", + ":password@github.com:foo/bar.git": "", + ":password@github.com:foo/bar.git#v1.0": "v1.0", + + // git+ssh urls + // + // NOTE auth is accepted but ignored + "git+ssh://github.com:foo/bar": "", + "git+ssh://github.com:foo/bar#v1.0": "v1.0", + "git+ssh://user@github.com:foo/bar": "", + "git+ssh://user@github.com:foo/bar#v1.0": "v1.0", + "git+ssh://user:password@github.com:foo/bar": "", + "git+ssh://user:password@github.com:foo/bar#v1.0": "v1.0", + "git+ssh://:password@github.com:foo/bar": "", + "git+ssh://:password@github.com:foo/bar#v1.0": "v1.0", + + "git+ssh://github.com:foo/bar.git": "", + "git+ssh://github.com:foo/bar.git#v1.0": "v1.0", + "git+ssh://user@github.com:foo/bar.git": "", + "git+ssh://user@github.com:foo/bar.git#v1.0": "v1.0", + "git+ssh://user:password@github.com:foo/bar.git": "", + "git+ssh://user:password@github.com:foo/bar.git#v1.0": "v1.0", + "git+ssh://:password@github.com:foo/bar.git": "", + "git+ssh://:password@github.com:foo/bar.git#v1.0": "v1.0", + + // ssh urls + // + // NOTE auth is accepted but ignored + "ssh://github.com:foo/bar": "", + "ssh://github.com:foo/bar#v1.0": "v1.0", + "ssh://user@github.com:foo/bar": "", + "ssh://user@github.com:foo/bar#v1.0": "v1.0", + "ssh://user:password@github.com:foo/bar": "", + "ssh://user:password@github.com:foo/bar#v1.0": "v1.0", + "ssh://:password@github.com:foo/bar": "", + "ssh://:password@github.com:foo/bar#v1.0": "v1.0", + + "ssh://github.com:foo/bar.git": "", + "ssh://github.com:foo/bar.git#v1.0": "v1.0", + "ssh://user@github.com:foo/bar.git": "", + "ssh://user@github.com:foo/bar.git#v1.0": "v1.0", + "ssh://user:password@github.com:foo/bar.git": "", + "ssh://user:password@github.com:foo/bar.git#v1.0": "v1.0", + "ssh://:password@github.com:foo/bar.git": "", + "ssh://:password@github.com:foo/bar.git#v1.0": "v1.0", + + // git+https urls + // + // NOTE auth is accepted and respected + "git+https://github.com/foo/bar": "", + "git+https://github.com/foo/bar#v1.0": "v1.0", + "git+https://user@github.com/foo/bar": "", + "git+https://user@github.com/foo/bar#v1.0": "v1.0", + "git+https://user:password@github.com/foo/bar": "", + "git+https://user:password@github.com/foo/bar#v1.0": "v1.0", + "git+https://:password@github.com/foo/bar": "", + "git+https://:password@github.com/foo/bar#v1.0": "v1.0", + + "git+https://github.com/foo/bar.git": "", + "git+https://github.com/foo/bar.git#v1.0": "v1.0", + "git+https://user@github.com/foo/bar.git": "", + "git+https://user@github.com/foo/bar.git#v1.0": "v1.0", + "git+https://user:password@github.com/foo/bar.git": "", + "git+https://user:password@github.com/foo/bar.git#v1.0": "v1.0", + "git+https://:password@github.com/foo/bar.git": "", + "git+https://:password@github.com/foo/bar.git#v1.0": "v1.0", + + // https urls + // + // NOTE auth is accepted and respected + "https://github.com/foo/bar": "", + "https://github.com/foo/bar#v1.0": "v1.0", + "https://user@github.com/foo/bar": "", + "https://user@github.com/foo/bar#v1.0": "v1.0", + "https://user:password@github.com/foo/bar": "", + "https://user:password@github.com/foo/bar#v1.0": "v1.0", + "https://:password@github.com/foo/bar": "", + "https://:password@github.com/foo/bar#v1.0": "v1.0", + + "https://github.com/foo/bar.git": "", + "https://github.com/foo/bar.git#v1.0": "v1.0", + "https://user@github.com/foo/bar.git": "", + "https://user@github.com/foo/bar.git#v1.0": "v1.0", + "https://user:password@github.com/foo/bar.git": "", + "https://user:password@github.com/foo/bar.git#v1.0": "v1.0", + "https://:password@github.com/foo/bar.git": "", + "https://:password@github.com/foo/bar.git#v1.0": "v1.0", + + // inputs that are not quite proper but we accept anyway + "https://www.github.com/foo/bar": "", + "foo/bar#branch with space": "branch with space", + "https://github.com/foo/bar/tree/branch": "branch", + "user..test--/..foo-js# . . . . . some . tags / / /": "" + }; + + const gitlabInvalid = [ + // gitlab urls can contain a /-/ segment, make sure we ignore those + "https://gitlab.com/foo/-/something", + // missing project + "https://gitlab.com/foo", + // tarball, this should not parse so that it can be used for a remote package fetcher + "https://gitlab.com/foo/bar/repository/archive.tar.gz", + "https://gitlab.com/foo/bar/repository/archive.tar.gz?ref=49b393e2ded775f2df36ef2ffcb61b0359c194c9" + ]; + + const gitlabValid = { + // shortcuts + // + // NOTE auth is accepted but ignored + // NOTE subgroups are respected, but the subgroup is treated as the project and the real project is lost + "gitlab:foo/bar": "", + "gitlab:foo/bar#v1.0": "v1.0", + "gitlab:user@foo/bar": "", + "gitlab:user@foo/bar#v1.0": "v1.0", + "gitlab:user:password@foo/bar": "", + "gitlab:user:password@foo/bar#v1.0": "v1.0", + "gitlab::password@foo/bar": "", + "gitlab::password@foo/bar#v1.0": "v1.0", + + "gitlab:foo/bar.git": "", + "gitlab:foo/bar.git#v1.0": "v1.0", + "gitlab:user@foo/bar.git": "", + "gitlab:user@foo/bar.git#v1.0": "v1.0", + "gitlab:user:password@foo/bar.git": "", + "gitlab:user:password@foo/bar.git#v1.0": "v1.0", + "gitlab::password@foo/bar.git": "", + "gitlab::password@foo/bar.git#v1.0": "v1.0", + + "gitlab:foo/bar/baz": "", + "gitlab:foo/bar/baz#v1.0": "v1.0", + "gitlab:user@foo/bar/baz": "", + "gitlab:user@foo/bar/baz#v1.0": "v1.0", + "gitlab:user:password@foo/bar/baz": "", + "gitlab:user:password@foo/bar/baz#v1.0": "v1.0", + "gitlab::password@foo/bar/baz": "", + "gitlab::password@foo/bar/baz#v1.0": "v1.0", + + "gitlab:foo/bar/baz.git": "", + "gitlab:foo/bar/baz.git#v1.0": "v1.0", + "gitlab:user@foo/bar/baz.git": "", + "gitlab:user@foo/bar/baz.git#v1.0": "v1.0", + "gitlab:user:password@foo/bar/baz.git": "", + "gitlab:user:password@foo/bar/baz.git#v1.0": "v1.0", + "gitlab::password@foo/bar/baz.git": "", + "gitlab::password@foo/bar/baz.git#v1.0": "v1.0", + + // no-protocol git+ssh + // + // NOTE auth is _required_ (see invalid list) but ignored + "user@gitlab.com:foo/bar": "", + "user@gitlab.com:foo/bar#v1.0": "v1.0", + "user:password@gitlab.com:foo/bar": "", + "user:password@gitlab.com:foo/bar#v1.0": "v1.0", + ":password@gitlab.com:foo/bar": "", + ":password@gitlab.com:foo/bar#v1.0": "v1.0", + + "user@gitlab.com:foo/bar.git": "", + "user@gitlab.com:foo/bar.git#v1.0": "v1.0", + "user:password@gitlab.com:foo/bar.git": "", + "user:password@gitlab.com:foo/bar.git#v1.0": "v1.0", + ":password@gitlab.com:foo/bar.git": "", + ":password@gitlab.com:foo/bar.git#v1.0": "v1.0", + + "user@gitlab.com:foo/bar/baz": "", + "user@gitlab.com:foo/bar/baz#v1.0": "v1.0", + "user:password@gitlab.com:foo/bar/baz": "", + "user:password@gitlab.com:foo/bar/baz#v1.0": "v1.0", + ":password@gitlab.com:foo/bar/baz": "", + ":password@gitlab.com:foo/bar/baz#v1.0": "v1.0", + + "user@gitlab.com:foo/bar/baz.git": "", + "user@gitlab.com:foo/bar/baz.git#v1.0": "v1.0", + "user:password@gitlab.com:foo/bar/baz.git": "", + "user:password@gitlab.com:foo/bar/baz.git#v1.0": "v1.0", + ":password@gitlab.com:foo/bar/baz.git": "", + ":password@gitlab.com:foo/bar/baz.git#v1.0": "v1.0", + + // git+ssh urls + // + // NOTE auth is accepted but ignored + // NOTE sub projects are accepted, but the sub project is treated as the project and the real project is lost + "git+ssh://gitlab.com:foo/bar": "", + "git+ssh://gitlab.com:foo/bar#v1.0": "v1.0", + "git+ssh://user@gitlab.com:foo/bar": "", + "git+ssh://user@gitlab.com:foo/bar#v1.0": "v1.0", + "git+ssh://user:password@gitlab.com:foo/bar": "", + "git+ssh://user:password@gitlab.com:foo/bar#v1.0": "v1.0", + "git+ssh://:password@gitlab.com:foo/bar": "", + "git+ssh://:password@gitlab.com:foo/bar#v1.0": "v1.0", + + "git+ssh://gitlab.com:foo/bar.git": "", + "git+ssh://gitlab.com:foo/bar.git#v1.0": "v1.0", + "git+ssh://user@gitlab.com:foo/bar.git": "", + "git+ssh://user@gitlab.com:foo/bar.git#v1.0": "v1.0", + "git+ssh://user:password@gitlab.com:foo/bar.git": "", + "git+ssh://user:password@gitlab.com:foo/bar.git#v1.0": "v1.0", + "git+ssh://:password@gitlab.com:foo/bar.git": "", + "git+ssh://:password@gitlab.com:foo/bar.git#v1.0": "v1.0", + + "git+ssh://gitlab.com:foo/bar/baz": "", + "git+ssh://gitlab.com:foo/bar/baz#v1.0": "v1.0", + "git+ssh://user@gitlab.com:foo/bar/baz": "", + "git+ssh://user@gitlab.com:foo/bar/baz#v1.0": "v1.0", + "git+ssh://user:password@gitlab.com:foo/bar/baz": "", + "git+ssh://user:password@gitlab.com:foo/bar/baz#v1.0": "v1.0", + "git+ssh://:password@gitlab.com:foo/bar/baz": "", + "git+ssh://:password@gitlab.com:foo/bar/baz#v1.0": "v1.0", + + "git+ssh://gitlab.com:foo/bar/baz.git": "", + "git+ssh://gitlab.com:foo/bar/baz.git#v1.0": "v1.0", + "git+ssh://user@gitlab.com:foo/bar/baz.git": "", + "git+ssh://user@gitlab.com:foo/bar/baz.git#v1.0": "v1.0", + "git+ssh://user:password@gitlab.com:foo/bar/baz.git": "", + "git+ssh://user:password@gitlab.com:foo/bar/baz.git#v1.0": "v1.0", + "git+ssh://:password@gitlab.com:foo/bar/baz.git": "", + "git+ssh://:password@gitlab.com:foo/bar/baz.git#v1.0": "v1.0", + + // ssh urls + // + // NOTE auth is accepted but ignored + // NOTE sub projects are accepted, but the sub project is treated as the project and the real project is lost + "ssh://gitlab.com:foo/bar": "", + "ssh://gitlab.com:foo/bar#v1.0": "v1.0", + "ssh://user@gitlab.com:foo/bar": "", + "ssh://user@gitlab.com:foo/bar#v1.0": "v1.0", + "ssh://user:password@gitlab.com:foo/bar": "", + "ssh://user:password@gitlab.com:foo/bar#v1.0": "v1.0", + "ssh://:password@gitlab.com:foo/bar": "", + "ssh://:password@gitlab.com:foo/bar#v1.0": "v1.0", + + "ssh://gitlab.com:foo/bar.git": "", + "ssh://gitlab.com:foo/bar.git#v1.0": "v1.0", + "ssh://user@gitlab.com:foo/bar.git": "", + "ssh://user@gitlab.com:foo/bar.git#v1.0": "v1.0", + "ssh://user:password@gitlab.com:foo/bar.git": "", + "ssh://user:password@gitlab.com:foo/bar.git#v1.0": "v1.0", + "ssh://:password@gitlab.com:foo/bar.git": "", + "ssh://:password@gitlab.com:foo/bar.git#v1.0": "v1.0", + + "ssh://gitlab.com:foo/bar/baz": "", + "ssh://gitlab.com:foo/bar/baz#v1.0": "v1.0", + "ssh://user@gitlab.com:foo/bar/baz": "", + "ssh://user@gitlab.com:foo/bar/baz#v1.0": "v1.0", + "ssh://user:password@gitlab.com:foo/bar/baz": "", + "ssh://user:password@gitlab.com:foo/bar/baz#v1.0": "v1.0", + "ssh://:password@gitlab.com:foo/bar/baz": "", + "ssh://:password@gitlab.com:foo/bar/baz#v1.0": "v1.0", + + "ssh://gitlab.com:foo/bar/baz.git": "", + "ssh://gitlab.com:foo/bar/baz.git#v1.0": "v1.0", + "ssh://user@gitlab.com:foo/bar/baz.git": "", + "ssh://user@gitlab.com:foo/bar/baz.git#v1.0": "v1.0", + "ssh://user:password@gitlab.com:foo/bar/baz.git": "", + "ssh://user:password@gitlab.com:foo/bar/baz.git#v1.0": "v1.0", + "ssh://:password@gitlab.com:foo/bar/baz.git": "", + "ssh://:password@gitlab.com:foo/bar/baz.git#v1.0": "v1.0", + + // git+https urls + // + // NOTE auth is accepted and respected + // NOTE sub projects are accepted, but the sub project is treated as the project and the real project is lost + "git+https://gitlab.com/foo/bar": "", + "git+https://gitlab.com/foo/bar#v1.0": "v1.0", + "git+https://user@gitlab.com/foo/bar": "", + "git+https://user@gitlab.com/foo/bar#v1.0": "v1.0", + "git+https://user:password@gitlab.com/foo/bar": "", + "git+https://user:password@gitlab.com/foo/bar#v1.0": "v1.0", + "git+https://:password@gitlab.com/foo/bar": "", + "git+https://:password@gitlab.com/foo/bar#v1.0": "v1.0", + + "git+https://gitlab.com/foo/bar.git": "", + "git+https://gitlab.com/foo/bar.git#v1.0": "v1.0", + "git+https://user@gitlab.com/foo/bar.git": "", + "git+https://user@gitlab.com/foo/bar.git#v1.0": "v1.0", + "git+https://user:password@gitlab.com/foo/bar.git": "", + "git+https://user:password@gitlab.com/foo/bar.git#v1.0": "v1.0", + "git+https://:password@gitlab.com/foo/bar.git": "", + "git+https://:password@gitlab.com/foo/bar.git#v1.0": "v1.0", + + "git+https://gitlab.com/foo/bar/baz": "", + "git+https://gitlab.com/foo/bar/baz#v1.0": "v1.0", + "git+https://user@gitlab.com/foo/bar/baz": "", + "git+https://user@gitlab.com/foo/bar/baz#v1.0": "v1.0", + "git+https://user:password@gitlab.com/foo/bar/baz": "", + "git+https://user:password@gitlab.com/foo/bar/baz#v1.0": "v1.0", + "git+https://:password@gitlab.com/foo/bar/baz": "", + "git+https://:password@gitlab.com/foo/bar/baz#v1.0": "v1.0", + + "git+https://gitlab.com/foo/bar/baz.git": "", + "git+https://gitlab.com/foo/bar/baz.git#v1.0": "v1.0", + "git+https://user@gitlab.com/foo/bar/baz.git": "", + "git+https://user@gitlab.com/foo/bar/baz.git#v1.0": "v1.0", + "git+https://user:password@gitlab.com/foo/bar/baz.git": "", + "git+https://user:password@gitlab.com/foo/bar/baz.git#v1.0": "v1.0", + "git+https://:password@gitlab.com/foo/bar/baz.git": "", + "git+https://:password@gitlab.com/foo/bar/baz.git#v1.0": "v1.0", + + // https urls + // + // NOTE auth is accepted and respected + // NOTE sub projects are accepted, but the sub project is treated as the project and the real project is lost + "https://gitlab.com/foo/bar": "", + "https://gitlab.com/foo/bar#v1.0": "v1.0", + "https://user@gitlab.com/foo/bar": "", + "https://user@gitlab.com/foo/bar#v1.0": "v1.0", + "https://user:password@gitlab.com/foo/bar": "", + "https://user:password@gitlab.com/foo/bar#v1.0": "v1.0", + "https://:password@gitlab.com/foo/bar": "", + "https://:password@gitlab.com/foo/bar#v1.0": "v1.0", + + "https://gitlab.com/foo/bar.git": "", + "https://gitlab.com/foo/bar.git#v1.0": "v1.0", + "https://user@gitlab.com/foo/bar.git": "", + "https://user@gitlab.com/foo/bar.git#v1.0": "v1.0", + "https://user:password@gitlab.com/foo/bar.git": "", + "https://user:password@gitlab.com/foo/bar.git#v1.0": "v1.0", + "https://:password@gitlab.com/foo/bar.git": "", + "https://:password@gitlab.com/foo/bar.git#v1.0": "v1.0", + + "https://gitlab.com/foo/bar/baz": "", + "https://gitlab.com/foo/bar/baz#v1.0": "v1.0", + "https://user@gitlab.com/foo/bar/baz": "", + "https://user@gitlab.com/foo/bar/baz#v1.0": "v1.0", + "https://user:password@gitlab.com/foo/bar/baz": "", + "https://user:password@gitlab.com/foo/bar/baz#v1.0": "v1.0", + "https://:password@gitlab.com/foo/bar/baz": "", + "https://:password@gitlab.com/foo/bar/baz#v1.0": "v1.0", + + "https://gitlab.com/foo/bar/baz.git": "", + "https://gitlab.com/foo/bar/baz.git#v1.0": "v1.0", + "https://user@gitlab.com/foo/bar/baz.git": "", + "https://user@gitlab.com/foo/bar/baz.git#v1.0": "v1.0", + "https://user:password@gitlab.com/foo/bar/baz.git": "", + "https://user:password@gitlab.com/foo/bar/baz.git#v1.0": "v1.0", + "https://:password@gitlab.com/foo/bar/baz.git": "", + "https://:password@gitlab.com/foo/bar/baz.git#v1.0": "v1.0" + }; + + const bitbucketInvalid = [ + // invalid protocol + "git://bitbucket.org/foo/bar", + // url to get a tarball + "https://bitbucket.org/foo/bar/get/archive.tar.gz", + // missing project + "https://bitbucket.org/foo" + ]; + + const bitbucketValid = { + // shortcuts + // + // NOTE auth is accepted but ignored + "bitbucket:foo/bar": "", + "bitbucket:foo/bar#v1.0": "v1.0", + "bitbucket:user@foo/bar": "", + "bitbucket:user@foo/bar#v1.0": "v1.0", + "bitbucket:user:password@foo/bar": "", + "bitbucket:user:password@foo/bar#v1.0": "v1.0", + "bitbucket::password@foo/bar": "", + "bitbucket::password@foo/bar#v1.0": "v1.0", + + "bitbucket:foo/bar.git": "", + "bitbucket:foo/bar.git#v1.0": "v1.0", + "bitbucket:user@foo/bar.git": "", + "bitbucket:user@foo/bar.git#v1.0": "v1.0", + "bitbucket:user:password@foo/bar.git": "", + "bitbucket:user:password@foo/bar.git#v1.0": "v1.0", + "bitbucket::password@foo/bar.git": "", + "bitbucket::password@foo/bar.git#v1.0": "v1.0", + + // no-protocol git+ssh + // + // NOTE auth is accepted but ignored + "git@bitbucket.org:foo/bar": "", + "git@bitbucket.org:foo/bar#v1.0": "v1.0", + "user@bitbucket.org:foo/bar": "", + "user@bitbucket.org:foo/bar#v1.0": "v1.0", + "user:password@bitbucket.org:foo/bar": "", + "user:password@bitbucket.org:foo/bar#v1.0": "v1.0", + ":password@bitbucket.org:foo/bar": "", + ":password@bitbucket.org:foo/bar#v1.0": "v1.0", + + "git@bitbucket.org:foo/bar.git": "", + "git@bitbucket.org:foo/bar.git#v1.0": "v1.0", + "user@bitbucket.org:foo/bar.git": "", + "user@bitbucket.org:foo/bar.git#v1.0": "v1.0", + "user:password@bitbucket.org:foo/bar.git": "", + "user:password@bitbucket.org:foo/bar.git#v1.0": "v1.0", + ":password@bitbucket.org:foo/bar.git": "", + ":password@bitbucket.org:foo/bar.git#v1.0": "v1.0", + + // git+ssh urls + // + // NOTE auth is accepted but ignored + "git+ssh://bitbucket.org:foo/bar": "", + "git+ssh://bitbucket.org:foo/bar#v1.0": "v1.0", + "git+ssh://user@bitbucket.org:foo/bar": "", + "git+ssh://user@bitbucket.org:foo/bar#v1.0": "v1.0", + "git+ssh://user:password@bitbucket.org:foo/bar": "", + "git+ssh://user:password@bitbucket.org:foo/bar#v1.0": "v1.0", + "git+ssh://:password@bitbucket.org:foo/bar": "", + "git+ssh://:password@bitbucket.org:foo/bar#v1.0": "v1.0", + + "git+ssh://bitbucket.org:foo/bar.git": "", + "git+ssh://bitbucket.org:foo/bar.git#v1.0": "v1.0", + "git+ssh://user@bitbucket.org:foo/bar.git": "", + "git+ssh://user@bitbucket.org:foo/bar.git#v1.0": "v1.0", + "git+ssh://user:password@bitbucket.org:foo/bar.git": "", + "git+ssh://user:password@bitbucket.org:foo/bar.git#v1.0": "v1.0", + "git+ssh://:password@bitbucket.org:foo/bar.git": "", + "git+ssh://:password@bitbucket.org:foo/bar.git#v1.0": "v1.0", + + // ssh urls + // + // NOTE auth is accepted but ignored + "ssh://bitbucket.org:foo/bar": "", + "ssh://bitbucket.org:foo/bar#v1.0": "v1.0", + "ssh://user@bitbucket.org:foo/bar": "", + "ssh://user@bitbucket.org:foo/bar#v1.0": "v1.0", + "ssh://user:password@bitbucket.org:foo/bar": "", + "ssh://user:password@bitbucket.org:foo/bar#v1.0": "v1.0", + "ssh://:password@bitbucket.org:foo/bar": "", + "ssh://:password@bitbucket.org:foo/bar#v1.0": "v1.0", + + "ssh://bitbucket.org:foo/bar.git": "", + "ssh://bitbucket.org:foo/bar.git#v1.0": "v1.0", + "ssh://user@bitbucket.org:foo/bar.git": "", + "ssh://user@bitbucket.org:foo/bar.git#v1.0": "v1.0", + "ssh://user:password@bitbucket.org:foo/bar.git": "", + "ssh://user:password@bitbucket.org:foo/bar.git#v1.0": "v1.0", + "ssh://:password@bitbucket.org:foo/bar.git": "", + "ssh://:password@bitbucket.org:foo/bar.git#v1.0": "v1.0", + + // git+https urls + // + // NOTE auth is accepted and respected + "git+https://bitbucket.org/foo/bar": "", + "git+https://bitbucket.org/foo/bar#v1.0": "v1.0", + "git+https://user@bitbucket.org/foo/bar": "", + "git+https://user@bitbucket.org/foo/bar#v1.0": "v1.0", + "git+https://user:password@bitbucket.org/foo/bar": "", + "git+https://user:password@bitbucket.org/foo/bar#v1.0": "v1.0", + "git+https://:password@bitbucket.org/foo/bar": "", + "git+https://:password@bitbucket.org/foo/bar#v1.0": "v1.0", + + "git+https://bitbucket.org/foo/bar.git": "", + "git+https://bitbucket.org/foo/bar.git#v1.0": "v1.0", + "git+https://user@bitbucket.org/foo/bar.git": "", + "git+https://user@bitbucket.org/foo/bar.git#v1.0": "v1.0", + "git+https://user:password@bitbucket.org/foo/bar.git": "", + "git+https://user:password@bitbucket.org/foo/bar.git#v1.0": "v1.0", + "git+https://:password@bitbucket.org/foo/bar.git": "", + "git+https://:password@bitbucket.org/foo/bar.git#v1.0": "v1.0", + + // https urls + // + // NOTE auth is accepted and respected + "https://bitbucket.org/foo/bar": "", + "https://bitbucket.org/foo/bar#v1.0": "v1.0", + "https://user@bitbucket.org/foo/bar": "", + "https://user@bitbucket.org/foo/bar#v1.0": "v1.0", + "https://user:password@bitbucket.org/foo/bar": "", + "https://user:password@bitbucket.org/foo/bar#v1.0": "v1.0", + "https://:password@bitbucket.org/foo/bar": "", + "https://:password@bitbucket.org/foo/bar#v1.0": "v1.0", + + "https://bitbucket.org/foo/bar.git": "", + "https://bitbucket.org/foo/bar.git#v1.0": "v1.0", + "https://user@bitbucket.org/foo/bar.git": "", + "https://user@bitbucket.org/foo/bar.git#v1.0": "v1.0", + "https://user:password@bitbucket.org/foo/bar.git": "", + "https://user:password@bitbucket.org/foo/bar.git#v1.0": "v1.0", + "https://:password@bitbucket.org/foo/bar.git": "", + "https://:password@bitbucket.org/foo/bar.git#v1.0": "v1.0" + }; + + const gistInvalid = [ + // raw urls that are wrong anyway but for some reason are in the wild + "https://gist.github.com/foo/feed/raw/fix%2Fbug/", + // missing both user and project + "https://gist.github.com/" + ]; + + const gistValid = { + // shortcuts + // + // NOTE auth is accepted but ignored + "gist:feed": "", + "gist:feed#v1.0": "v1.0", + "gist:user@feed": "", + "gist:user@feed#v1.0": "v1.0", + "gist:user:password@feed": "", + "gist:user:password@feed#v1.0": "v1.0", + "gist::password@feed": "", + "gist::password@feed#v1.0": "v1.0", + + "gist:feed.git": "", + "gist:feed.git#v1.0": "v1.0", + "gist:user@feed.git": "", + "gist:user@feed.git#v1.0": "v1.0", + "gist:user:password@feed.git": "", + "gist:user:password@feed.git#v1.0": "v1.0", + "gist::password@feed.git": "", + "gist::password@feed.git#v1.0": "v1.0", + + "gist:/feed": "", + "gist:/feed#v1.0": "v1.0", + "gist:user@/feed": "", + "gist:user@/feed#v1.0": "v1.0", + "gist:user:password@/feed": "", + "gist:user:password@/feed#v1.0": "v1.0", + "gist::password@/feed": "", + "gist::password@/feed#v1.0": "v1.0", + + "gist:/feed.git": "", + "gist:/feed.git#v1.0": "v1.0", + "gist:user@/feed.git": "", + "gist:user@/feed.git#v1.0": "v1.0", + "gist:user:password@/feed.git": "", + "gist:user:password@/feed.git#v1.0": "v1.0", + "gist::password@/feed.git": "", + "gist::password@/feed.git#v1.0": "v1.0", + + "gist:foo/feed": "", + "gist:foo/feed#v1.0": "v1.0", + "gist:user@foo/feed": "", + "gist:user@foo/feed#v1.0": "v1.0", + "gist:user:password@foo/feed": "", + "gist:user:password@foo/feed#v1.0": "v1.0", + "gist::password@foo/feed": "", + "gist::password@foo/feed#v1.0": "v1.0", + + "gist:foo/feed.git": "", + "gist:foo/feed.git#v1.0": "v1.0", + "gist:user@foo/feed.git": "", + "gist:user@foo/feed.git#v1.0": "v1.0", + "gist:user:password@foo/feed.git": "", + "gist:user:password@foo/feed.git#v1.0": "v1.0", + "gist::password@foo/feed.git": "", + "gist::password@foo/feed.git#v1.0": "v1.0", + + // git urls + // + // NOTE auth is accepted and respected + "git://gist.github.com/feed": "", + "git://gist.github.com/feed#v1.0": "v1.0", + "git://user@gist.github.com/feed": "", + "git://user@gist.github.com/feed#v1.0": "v1.0", + "git://user:password@gist.github.com/feed": "", + "git://user:password@gist.github.com/feed#v1.0": "v1.0", + "git://:password@gist.github.com/feed": "", + "git://:password@gist.github.com/feed#v1.0": "v1.0", + + "git://gist.github.com/feed.git": "", + "git://gist.github.com/feed.git#v1.0": "v1.0", + "git://user@gist.github.com/feed.git": "", + "git://user@gist.github.com/feed.git#v1.0": "v1.0", + "git://user:password@gist.github.com/feed.git": "", + "git://user:password@gist.github.com/feed.git#v1.0": "v1.0", + "git://:password@gist.github.com/feed.git": "", + "git://:password@gist.github.com/feed.git#v1.0": "v1.0", + + "git://gist.github.com/foo/feed": "", + "git://gist.github.com/foo/feed#v1.0": "v1.0", + "git://user@gist.github.com/foo/feed": "", + "git://user@gist.github.com/foo/feed#v1.0": "v1.0", + "git://user:password@gist.github.com/foo/feed": "", + "git://user:password@gist.github.com/foo/feed#v1.0": "v1.0", + "git://:password@gist.github.com/foo/feed": "", + "git://:password@gist.github.com/foo/feed#v1.0": "v1.0", + + "git://gist.github.com/foo/feed.git": "", + "git://gist.github.com/foo/feed.git#v1.0": "v1.0", + "git://user@gist.github.com/foo/feed.git": "", + "git://user@gist.github.com/foo/feed.git#v1.0": "v1.0", + "git://user:password@gist.github.com/foo/feed.git": "", + "git://user:password@gist.github.com/foo/feed.git#v1.0": "v1.0", + "git://:password@gist.github.com/foo/feed.git": "", + "git://:password@gist.github.com/foo/feed.git#v1.0": "v1.0", + + // no-protocol git+ssh + // + // NOTE auth is accepted and ignored + "git@gist.github.com:feed": "", + "git@gist.github.com:feed#v1.0": "v1.0", + "user@gist.github.com:feed": "", + "user@gist.github.com:feed#v1.0": "v1.0", + "user:password@gist.github.com:feed": "", + "user:password@gist.github.com:feed#v1.0": "v1.0", + ":password@gist.github.com:feed": "", + ":password@gist.github.com:feed#v1.0": "v1.0", + + "git@gist.github.com:feed.git": "", + "git@gist.github.com:feed.git#v1.0": "v1.0", + "user@gist.github.com:feed.git": "", + "user@gist.github.com:feed.git#v1.0": "v1.0", + "user:password@gist.github.com:feed.git": "", + "user:password@gist.github.com:feed.git#v1.0": "v1.0", + ":password@gist.github.com:feed.git": "", + ":password@gist.github.com:feed.git#v1.0": "v1.0", + + "git@gist.github.com:foo/feed": "", + "git@gist.github.com:foo/feed#v1.0": "v1.0", + "user@gist.github.com:foo/feed": "", + "user@gist.github.com:foo/feed#v1.0": "v1.0", + "user:password@gist.github.com:foo/feed": "", + "user:password@gist.github.com:foo/feed#v1.0": "v1.0", + ":password@gist.github.com:foo/feed": "", + ":password@gist.github.com:foo/feed#v1.0": "v1.0", + + "git@gist.github.com:foo/feed.git": "", + "git@gist.github.com:foo/feed.git#v1.0": "v1.0", + "user@gist.github.com:foo/feed.git": "", + "user@gist.github.com:foo/feed.git#v1.0": "v1.0", + "user:password@gist.github.com:foo/feed.git": "", + "user:password@gist.github.com:foo/feed.git#v1.0": "v1.0", + ":password@gist.github.com:foo/feed.git": "", + ":password@gist.github.com:foo/feed.git#v1.0": "v1.0", + + // git+ssh urls + // + // NOTE auth is accepted but ignored + // NOTE see TODO at list of invalids, some inputs fail and shouldn't + "git+ssh://gist.github.com:feed": "", + "git+ssh://gist.github.com:feed#v1.0": "v1.0", + "git+ssh://user@gist.github.com:feed": "", + "git+ssh://user@gist.github.com:feed#v1.0": "v1.0", + "git+ssh://user:password@gist.github.com:feed": "", + "git+ssh://user:password@gist.github.com:feed#v1.0": "v1.0", + "git+ssh://:password@gist.github.com:feed": "", + "git+ssh://:password@gist.github.com:feed#v1.0": "v1.0", + + "git+ssh://gist.github.com:feed.git": "", + "git+ssh://gist.github.com:feed.git#v1.0": "v1.0", + "git+ssh://user@gist.github.com:feed.git": "", + "git+ssh://user@gist.github.com:feed.git#v1.0": "v1.0", + "git+ssh://user:password@gist.github.com:feed.git": "", + "git+ssh://user:password@gist.github.com:feed.git#v1.0": "v1.0", + "git+ssh://:password@gist.github.com:feed.git": "", + "git+ssh://:password@gist.github.com:feed.git#v1.0": "v1.0", + + "git+ssh://gist.github.com:foo/feed": "", + "git+ssh://gist.github.com:foo/feed#v1.0": "v1.0", + "git+ssh://user@gist.github.com:foo/feed": "", + "git+ssh://user@gist.github.com:foo/feed#v1.0": "v1.0", + "git+ssh://user:password@gist.github.com:foo/feed": "", + "git+ssh://user:password@gist.github.com:foo/feed#v1.0": "v1.0", + "git+ssh://:password@gist.github.com:foo/feed": "", + "git+ssh://:password@gist.github.com:foo/feed#v1.0": "v1.0", + + "git+ssh://gist.github.com:foo/feed.git": "", + "git+ssh://gist.github.com:foo/feed.git#v1.0": "v1.0", + "git+ssh://user@gist.github.com:foo/feed.git": "", + "git+ssh://user@gist.github.com:foo/feed.git#v1.0": "v1.0", + "git+ssh://user:password@gist.github.com:foo/feed.git": "", + "git+ssh://user:password@gist.github.com:foo/feed.git#v1.0": "v1.0", + "git+ssh://:password@gist.github.com:foo/feed.git": "", + "git+ssh://:password@gist.github.com:foo/feed.git#v1.0": "v1.0", + + // ssh urls + // + // NOTE auth is accepted but ignored + "ssh://gist.github.com:feed": "", + "ssh://gist.github.com:feed#v1.0": "v1.0", + "ssh://user@gist.github.com:feed": "", + "ssh://user@gist.github.com:feed#v1.0": "v1.0", + "ssh://user:password@gist.github.com:feed": "", + "ssh://user:password@gist.github.com:feed#v1.0": "v1.0", + "ssh://:password@gist.github.com:feed": "", + "ssh://:password@gist.github.com:feed#v1.0": "v1.0", + + "ssh://gist.github.com:feed.git": "", + "ssh://gist.github.com:feed.git#v1.0": "v1.0", + "ssh://user@gist.github.com:feed.git": "", + "ssh://user@gist.github.com:feed.git#v1.0": "v1.0", + "ssh://user:password@gist.github.com:feed.git": "", + "ssh://user:password@gist.github.com:feed.git#v1.0": "v1.0", + "ssh://:password@gist.github.com:feed.git": "", + "ssh://:password@gist.github.com:feed.git#v1.0": "v1.0", + + "ssh://gist.github.com:foo/feed": "", + "ssh://gist.github.com:foo/feed#v1.0": "v1.0", + "ssh://user@gist.github.com:foo/feed": "", + "ssh://user@gist.github.com:foo/feed#v1.0": "v1.0", + "ssh://user:password@gist.github.com:foo/feed": "", + "ssh://user:password@gist.github.com:foo/feed#v1.0": "v1.0", + "ssh://:password@gist.github.com:foo/feed": "", + "ssh://:password@gist.github.com:foo/feed#v1.0": "v1.0", + + "ssh://gist.github.com:foo/feed.git": "", + "ssh://gist.github.com:foo/feed.git#v1.0": "v1.0", + "ssh://user@gist.github.com:foo/feed.git": "", + "ssh://user@gist.github.com:foo/feed.git#v1.0": "v1.0", + "ssh://user:password@gist.github.com:foo/feed.git": "", + "ssh://user:password@gist.github.com:foo/feed.git#v1.0": "v1.0", + "ssh://:password@gist.github.com:foo/feed.git": "", + "ssh://:password@gist.github.com:foo/feed.git#v1.0": "v1.0", + + // git+https urls + // + // NOTE auth is accepted and respected + "git+https://gist.github.com/feed": "", + "git+https://gist.github.com/feed#v1.0": "v1.0", + "git+https://user@gist.github.com/feed": "", + "git+https://user@gist.github.com/feed#v1.0": "v1.0", + "git+https://user:password@gist.github.com/feed": "", + "git+https://user:password@gist.github.com/feed#v1.0": "v1.0", + "git+https://:password@gist.github.com/feed": "", + "git+https://:password@gist.github.com/feed#v1.0": "v1.0", + + "git+https://gist.github.com/feed.git": "", + "git+https://gist.github.com/feed.git#v1.0": "v1.0", + "git+https://user@gist.github.com/feed.git": "", + "git+https://user@gist.github.com/feed.git#v1.0": "v1.0", + "git+https://user:password@gist.github.com/feed.git": "", + "git+https://user:password@gist.github.com/feed.git#v1.0": "v1.0", + "git+https://:password@gist.github.com/feed.git": "", + "git+https://:password@gist.github.com/feed.git#v1.0": "v1.0", + + "git+https://gist.github.com/foo/feed": "", + "git+https://gist.github.com/foo/feed#v1.0": "v1.0", + "git+https://user@gist.github.com/foo/feed": "", + "git+https://user@gist.github.com/foo/feed#v1.0": "v1.0", + "git+https://user:password@gist.github.com/foo/feed": "", + "git+https://user:password@gist.github.com/foo/feed#v1.0": "v1.0", + "git+https://:password@gist.github.com/foo/feed": "", + "git+https://:password@gist.github.com/foo/feed#v1.0": "v1.0", + + "git+https://gist.github.com/foo/feed.git": "", + "git+https://gist.github.com/foo/feed.git#v1.0": "v1.0", + "git+https://user@gist.github.com/foo/feed.git": "", + "git+https://user@gist.github.com/foo/feed.git#v1.0": "v1.0", + "git+https://user:password@gist.github.com/foo/feed.git": "", + "git+https://user:password@gist.github.com/foo/feed.git#v1.0": "v1.0", + "git+https://:password@gist.github.com/foo/feed.git": "", + "git+https://:password@gist.github.com/foo/feed.git#v1.0": "v1.0", + + // https urls + // + // NOTE auth is accepted and respected + "https://gist.github.com/feed": "", + "https://gist.github.com/feed#v1.0": "v1.0", + "https://user@gist.github.com/feed": "", + "https://user@gist.github.com/feed#v1.0": "v1.0", + "https://user:password@gist.github.com/feed": "", + "https://user:password@gist.github.com/feed#v1.0": "v1.0", + "https://:password@gist.github.com/feed": "", + "https://:password@gist.github.com/feed#v1.0": "v1.0", + + "https://gist.github.com/feed.git": "", + "https://gist.github.com/feed.git#v1.0": "v1.0", + "https://user@gist.github.com/feed.git": "", + "https://user@gist.github.com/feed.git#v1.0": "v1.0", + "https://user:password@gist.github.com/feed.git": "", + "https://user:password@gist.github.com/feed.git#v1.0": "v1.0", + "https://:password@gist.github.com/feed.git": "", + "https://:password@gist.github.com/feed.git#v1.0": "v1.0", + + "https://gist.github.com/foo/feed": "", + "https://gist.github.com/foo/feed#v1.0": "v1.0", + "https://user@gist.github.com/foo/feed": "", + "https://user@gist.github.com/foo/feed#v1.0": "v1.0", + "https://user:password@gist.github.com/foo/feed": "", + "https://user:password@gist.github.com/foo/feed#v1.0": "v1.0", + "https://:password@gist.github.com/foo/feed": "", + "https://:password@gist.github.com/foo/feed#v1.0": "v1.0", + + "https://gist.github.com/foo/feed.git": "", + "https://gist.github.com/foo/feed.git#v1.0": "v1.0", + "https://user@gist.github.com/foo/feed.git": "", + "https://user@gist.github.com/foo/feed.git#v1.0": "v1.0", + "https://user:password@gist.github.com/foo/feed.git": "", + "https://user:password@gist.github.com/foo/feed.git#v1.0": "v1.0", + "https://:password@gist.github.com/foo/feed.git": "", + "https://:password@gist.github.com/foo/feed.git#v1.0": "v1.0" + }; + + const otherDomainValid = { + "https://other.com/foo/bar.git#v1.0": "v1.0", + "ssh://other.com:foo/bar.git#v1.0": "v1.0", + "user@other.com:foo/bar#v1.0": "v1.0" + }; + + const otherDomainInvalid = ["other:foo/bar#v1.0"]; + + it("should return empty string for some invalid URL deps", () => { + for (const url of commonInvalid) { + expect(normalizeVersion(/** @type {string} */ (url))).toBe(""); + } + }); + + it("should get correct version for some valid URL deps", () => { + const commonValidMap = /** @type {Record} */ (commonValid); + for (const url of Object.keys(commonValid)) { + expect(normalizeVersion(url)).toBe(commonValidMap[url]); + } + }); + + it("should return empty string for github invalid URL deps", () => { + for (const url of githubInvalid) { + expect(normalizeVersion(url)).toBe(""); + } + }); + + it("should get correct version for github URL deps", () => { + const githubValidMap = /** @type {Record} */ (githubValid); + for (const url of Object.keys(githubValid)) { + expect(normalizeVersion(url)).toBe(githubValidMap[url]); + } + }); + + it("should return empty string for gitlab invalid URL deps", () => { + for (const url of gitlabInvalid) { + expect(normalizeVersion(url)).toBe(""); + } + }); + + it("should get correct version for gitlab URL deps", () => { + const gitlabValidMap = /** @type {Record} */ (gitlabValid); + for (const url of Object.keys(gitlabValid)) { + expect(normalizeVersion(url)).toBe(gitlabValidMap[url]); + } + }); + + it("should return empty string for bitbucket invalid URL deps", () => { + for (const url of bitbucketInvalid) { + expect(normalizeVersion(url)).toBe(""); + } + }); + + it("should get correct version for bitbucket URL deps", () => { + const bitbucketValidMap = /** @type {Record} */ ( + bitbucketValid + ); + for (const url of Object.keys(bitbucketValid)) { + expect(normalizeVersion(url)).toBe(bitbucketValidMap[url]); + } + }); + + it("should return empty string for gist invalid URL deps", () => { + for (const url of gistInvalid) { + expect(normalizeVersion(url)).toBe(""); + } + }); + + it("should get correct version for gist URL deps", () => { + const gistValidMap = /** @type {Record} */ (gistValid); + for (const url of Object.keys(gistValid)) { + expect(normalizeVersion(url)).toBe(gistValidMap[url]); + } + }); + + it("should return empty string for other domain invalid URL deps", () => { + for (const url of otherDomainInvalid) { + expect(normalizeVersion(url)).toBe(""); + } + }); + + it("should return correct version for other domain URL deps", () => { + const otherDomainValidMap = /** @type {Record} */ ( + otherDomainValid + ); + for (const url of Object.keys(otherDomainValid)) { + expect(normalizeVersion(url)).toBe(otherDomainValidMap[url]); + } + }); +}); diff --git a/test/SideEffectsFlagPlugin.unittest.js b/test/SideEffectsFlagPlugin.unittest.js index a0e9c9830c0..4842ff730cc 100644 --- a/test/SideEffectsFlagPlugin.unittest.js +++ b/test/SideEffectsFlagPlugin.unittest.js @@ -5,16 +5,28 @@ const SideEffectsFlagPlugin = require("../lib/optimize/SideEffectsFlagPlugin"); describe("SideEffectsFlagPlugin", () => { it("should assume true", () => { expect( - SideEffectsFlagPlugin.moduleHasSideEffects("./foo/bar.js", undefined) + SideEffectsFlagPlugin.moduleHasSideEffects( + "./foo/bar.js", + undefined, + new Map() + ) ).toBe(true); }); it("should understand boolean values", () => { expect( - SideEffectsFlagPlugin.moduleHasSideEffects("./foo/bar.js", true) + SideEffectsFlagPlugin.moduleHasSideEffects( + "./foo/bar.js", + true, + new Map() + ) ).toBe(true); expect( - SideEffectsFlagPlugin.moduleHasSideEffects("./foo/bar.js", false) + SideEffectsFlagPlugin.moduleHasSideEffects( + "./foo/bar.js", + false, + new Map() + ) ).toBe(false); }); @@ -22,90 +34,110 @@ describe("SideEffectsFlagPlugin", () => { expect( SideEffectsFlagPlugin.moduleHasSideEffects( "./src/x/y/z.js", - "./src/**/*.js" + "./src/**/*.js", + new Map() ) ).toBe(true); expect( - SideEffectsFlagPlugin.moduleHasSideEffects("./x.js", "./src/**/*.js") + SideEffectsFlagPlugin.moduleHasSideEffects( + "./x.js", + "./src/**/*.js", + new Map() + ) ).toBe(false); expect( SideEffectsFlagPlugin.moduleHasSideEffects( "./src/x/y/z.js", - "./**/src/x/y/z.js" + "./**/src/x/y/z.js", + new Map() ) ).toBe(true); expect( - SideEffectsFlagPlugin.moduleHasSideEffects("./src/x/y/z.js", "**.js") + SideEffectsFlagPlugin.moduleHasSideEffects( + "./src/x/y/z.js", + "**.js", + new Map() + ) ).toBe(true); expect( SideEffectsFlagPlugin.moduleHasSideEffects( "./src/x/y/z.js", - "./src/**/z.js" + "./src/**/z.js", + new Map() ) ).toBe(true); expect( SideEffectsFlagPlugin.moduleHasSideEffects( "./src/x/y/z.js", - "./**/x/**/z.js" + "./**/x/**/z.js", + new Map() ) ).toBe(true); expect( SideEffectsFlagPlugin.moduleHasSideEffects( "./src/x/y/z.js", - "./**/src/**" + "./**/src/**", + new Map() ) ).toBe(true); expect( - SideEffectsFlagPlugin.moduleHasSideEffects("./src/x/y/z.js", "./**/src/*") + SideEffectsFlagPlugin.moduleHasSideEffects( + "./src/x/y/z.js", + "./**/src/*", + new Map() + ) ).toBe(false); expect( - SideEffectsFlagPlugin.moduleHasSideEffects("./src/x/y/z.js", "*.js") + SideEffectsFlagPlugin.moduleHasSideEffects( + "./src/x/y/z.js", + "*.js", + new Map() + ) ).toBe(true); - expect( - SideEffectsFlagPlugin.moduleHasSideEffects("./src/x/y/z.js", "x/**/z.js") - ).toBe(false); expect( SideEffectsFlagPlugin.moduleHasSideEffects( "./src/x/y/z.js", - "src/**/z.js" + "x/**/z.js", + new Map() ) - ).toBe(true); + ).toBe(false); expect( SideEffectsFlagPlugin.moduleHasSideEffects( "./src/x/y/z.js", - "src/**/{x,y,z}.js" + "src/**/z.js", + new Map() ) ).toBe(true); expect( SideEffectsFlagPlugin.moduleHasSideEffects( "./src/x/y/z.js", - "src/**/[x-z].js" + "src/**/{x,y,z}.js", + new Map() ) ).toBe(true); expect( SideEffectsFlagPlugin.moduleHasSideEffects( "./src/x/y/z.js", - "src/**/[[:lower:]].js" + "src/**/[x-z].js", + new Map() ) ).toBe(true); - expect( - SideEffectsFlagPlugin.moduleHasSideEffects("./src/x/y/z.js", "!*.js") - ).toBe(false); - expect( - SideEffectsFlagPlugin.moduleHasSideEffects("./src/x/y/z.js", "!**/*.js") - ).toBe(false); }); it("should understand arrays", () => { const array = ["./src/**/*.js", "./dirty.js"]; expect( - SideEffectsFlagPlugin.moduleHasSideEffects("./src/x/y/z.js", array) + SideEffectsFlagPlugin.moduleHasSideEffects( + "./src/x/y/z.js", + array, + new Map() + ) ).toBe(true); expect( - SideEffectsFlagPlugin.moduleHasSideEffects("./dirty.js", array) + SideEffectsFlagPlugin.moduleHasSideEffects("./dirty.js", array, new Map()) ).toBe(true); expect( - SideEffectsFlagPlugin.moduleHasSideEffects("./clean.js", array) + SideEffectsFlagPlugin.moduleHasSideEffects("./clean.js", array, new Map()) ).toBe(false); }); }); diff --git a/test/SizeFormatHelpers.unittest.js b/test/SizeFormatHelpers.unittest.js deleted file mode 100644 index 5fd6558271b..00000000000 --- a/test/SizeFormatHelpers.unittest.js +++ /dev/null @@ -1,49 +0,0 @@ -/* globals describe, it, beforeEach */ -"use strict"; - -const SizeFormatHelpers = require("../lib/SizeFormatHelpers"); - -describe("SizeFormatHelpers", () => { - describe("formatSize", () => { - it("should handle zero size", () => { - expect(SizeFormatHelpers.formatSize(0)).toBe("0 bytes"); - }); - - it("should handle bytes", () => { - expect(SizeFormatHelpers.formatSize(1000)).toBe("1000 bytes"); - }); - - it("should handle integer kibibytes", () => { - expect(SizeFormatHelpers.formatSize(2048)).toBe("2 KiB"); - }); - - it("should handle float kibibytes", () => { - expect(SizeFormatHelpers.formatSize(2560)).toBe("2.5 KiB"); - }); - - it("should handle integer mebibytes", () => { - expect(SizeFormatHelpers.formatSize(10 * 1024 * 1024)).toBe("10 MiB"); - }); - - it("should handle float mebibytes", () => { - expect(SizeFormatHelpers.formatSize(12.5 * 1024 * 1024)).toBe("12.5 MiB"); - }); - - it("should handle integer gibibytes", () => { - expect(SizeFormatHelpers.formatSize(3 * 1024 * 1024 * 1024)).toBe( - "3 GiB" - ); - }); - - it("should handle float gibibytes", () => { - expect(SizeFormatHelpers.formatSize(1.2 * 1024 * 1024 * 1024)).toBe( - "1.2 GiB" - ); - }); - - it("should handle undefined/NaN", () => { - expect(SizeFormatHelpers.formatSize(undefined)).toBe("unknown size"); - expect(SizeFormatHelpers.formatSize(NaN)).toBe("unknown size"); - }); - }); -}); diff --git a/test/SortableSet.unittest.js b/test/SortableSet.unittest.js index 66968c3b38d..f37d3e2ee92 100644 --- a/test/SortableSet.unittest.js +++ b/test/SortableSet.unittest.js @@ -1,29 +1,40 @@ -/* globals describe, it */ "use strict"; const SortableSet = require("../lib/util/SortableSet"); describe("util/SortableSet", () => { - it("Can be constructed like a normal Set", () => { - const sortableSet = new SortableSet([1, 1, 1, 1, 1, 4, 5, 2], () => {}); - expect(Array.from(sortableSet)).toEqual([1, 4, 5, 2]); + it("can be constructed like a normal Set", () => { + const sortableSet = new SortableSet( + [1, 1, 1, 1, 1, 4, 5, 2], + (a, b) => a - b + ); + expect([...sortableSet]).toEqual([1, 4, 5, 2]); }); - it("Can sort its content", () => { + it("can sort its content", () => { const sortableSet = new SortableSet( [1, 1, 1, 6, 6, 1, 1, 4, 5, 2, 3, 8, 5, 7, 9, 0, 3, 1], (a, b) => a - b ); sortableSet.sort(); - expect(Array.from(sortableSet)).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + expect([...sortableSet]).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + }); + + it("can sort by a specified function", () => { + const sortableSet = new SortableSet( + [1, 1, 1, 6, 6, 1, 1, 4, 5, 2, 3, 8, 5, 7, 9, 0, 3, 1], + (a, b) => a - b + ); + sortableSet.sortWith((a, b) => b - a); + expect([...sortableSet]).toEqual([9, 8, 7, 6, 5, 4, 3, 2, 1, 0]); }); - it("Can sort by a specified function", () => { + it("can sort by a specified function and convert to JSON", () => { const sortableSet = new SortableSet( [1, 1, 1, 6, 6, 1, 1, 4, 5, 2, 3, 8, 5, 7, 9, 0, 3, 1], (a, b) => a - b ); sortableSet.sortWith((a, b) => b - a); - expect(Array.from(sortableSet)).toEqual([9, 8, 7, 6, 5, 4, 3, 2, 1, 0]); + expect(sortableSet.toJSON()).toEqual([9, 8, 7, 6, 5, 4, 3, 2, 1, 0]); }); }); diff --git a/test/SourceMapDevToolModuleOptionsPlugin.unittest.js b/test/SourceMapDevToolModuleOptionsPlugin.unittest.js deleted file mode 100644 index dd348267bd8..00000000000 --- a/test/SourceMapDevToolModuleOptionsPlugin.unittest.js +++ /dev/null @@ -1,145 +0,0 @@ -"use strict"; - -const SourceMapDevToolModuleOptionsPlugin = require("../lib/SourceMapDevToolModuleOptionsPlugin"); -const applyPluginWithOptions = require("./helpers/applyPluginWithOptions"); - -describe("SourceMapDevToolModuleOptionsPlugin", () => { - describe("when applied", () => { - let eventBindings; - - beforeEach(() => { - eventBindings = undefined; - }); - - describe("with module false and line-to-line false", () => { - beforeEach(() => { - eventBindings = applyPluginWithOptions( - SourceMapDevToolModuleOptionsPlugin, - { - module: false, - lineToLine: false - } - ); - }); - - it("does not bind any event handlers", () => { - expect(eventBindings.length).toBe(0); - }); - }); - - describe("with module true", () => { - beforeEach(() => { - eventBindings = applyPluginWithOptions( - SourceMapDevToolModuleOptionsPlugin, - { - module: true, - lineToLine: false - } - ); - }); - - it("binds one event handler", () => { - expect(eventBindings.length).toBe(1); - }); - - describe("event handler", () => { - it("binds to build-module event", () => { - expect(eventBindings[0].name).toBe("build-module"); - }); - - it("sets source map flag", () => { - const module = {}; - eventBindings[0].handler(module); - expect(module).toEqual({ - useSourceMap: true - }); - }); - }); - }); - - describe("with line-to-line true", () => { - beforeEach(() => - (eventBindings = applyPluginWithOptions( - SourceMapDevToolModuleOptionsPlugin, - { - module: false, - lineToLine: true - } - ))); - - it("binds one event handler", () => { - expect(eventBindings.length).toBe(1); - }); - - describe("event handler", () => { - it("binds to build-module event", () => { - expect(eventBindings[0].name).toBe("build-module"); - }); - - it("sets line-to-line flag", () => { - const module = {}; - eventBindings[0].handler(module); - expect(module).toEqual({ - lineToLine: true - }); - }); - }); - }); - - describe("with line-to-line object", () => { - beforeEach(() => { - eventBindings = applyPluginWithOptions( - SourceMapDevToolModuleOptionsPlugin, - { - module: false, - lineToLine: {} - } - ); - }); - - it("binds one event handler", () => { - expect(eventBindings.length).toBe(1); - }); - - describe("event handler", () => { - it("binds to build-module event", () => { - expect(eventBindings[0].name).toBe("build-module"); - }); - - describe("when module has no resource", () => { - it("makes no changes", () => { - const module = {}; - eventBindings[0].handler(module); - expect(module).toEqual({}); - }); - }); - - describe("when module has a resource", () => { - it("sets line-to-line flag", () => { - const module = { - resource: "foo" - }; - eventBindings[0].handler(module); - expect(module).toEqual({ - lineToLine: true, - resource: "foo" - }); - }); - }); - - describe("when module has a resource with query", () => { - it("sets line-to-line flag", () => { - const module = { - resource: "foo?bar" - }; - eventBindings[0].handler(module); - expect(module).toEqual({ - lineToLine: true, - resource: "foo?bar" - }); - }); - }); - }); - }); - }); -}); diff --git a/test/Stats.test.js b/test/Stats.test.js index ffe2b8cecfe..ee459e8dd09 100644 --- a/test/Stats.test.js +++ b/test/Stats.test.js @@ -1,28 +1,102 @@ -/*globals describe it */ "use strict"; -const webpack = require("../lib/webpack"); -const MemoryFs = require("memory-fs"); +require("./helpers/warmup-webpack"); + +const { Volume, createFsFromVolume } = require("memfs"); +const expectNoDeprecations = require("./helpers/expectNoDeprecations"); + +/** + * @param {import("../").Configuration} options options + * @returns {Promise} stats + */ +const compile = (options) => + /** @type {Promise} */ ( + new Promise((resolve, reject) => { + const webpack = require(".."); + + const compiler = /** @type {import("../").Compiler} */ (webpack(options)); + compiler.outputFileSystem = /** @type {EXPECTED_ANY} */ ( + createFsFromVolume(new Volume()) + ); + compiler.run((err, stats) => { + if (err) { + reject(err); + } else { + resolve(/** @type {import("../").Stats} */ (stats)); + } + }); + }) + ); + +expectNoDeprecations(); describe("Stats", () => { - it("should print env string in stats", done => { - const compiler = webpack({ + it("should work with a boolean value", async () => { + const stats = await compile({ + context: __dirname, + entry: "./fixtures/a" + }); + expect(stats.toJson(false)).toMatchInlineSnapshot("Object {}"); + expect(stats.toString(false)).toMatchInlineSnapshot('""'); + }); + + it("should work with a string value", async () => { + const stats = await compile({ + context: __dirname, + entry: "./fixtures/a" + }); + expect(stats.toJson("none")).toMatchInlineSnapshot("Object {}"); + expect(stats.toString("none")).toMatchInlineSnapshot('""'); + }); + + it("should work with an object value", async () => { + const stats = await compile({ + context: __dirname, + entry: "./fixtures/a" + }); + expect( + stats.toJson({ + all: false, + version: false, + errorsCount: true, + warningsCount: true + }) + ).toMatchInlineSnapshot(` + Object { + "errorsCount": 0, + "warningsCount": 1, + } + `); + expect( + stats.toString({ + all: false, + version: false, + errorsCount: true, + warningsCount: true + }) + ).toMatchInlineSnapshot('"webpack compiled with 1 warning"'); + }); + + it("should print env string in stats", async () => { + const stats = await compile({ context: __dirname, entry: "./fixtures/a" }); - compiler.outputFileSystem = new MemoryFs(); - compiler.run((err, stats) => { - if (err) return done(err); - try { - expect( - stats.toString({ + expect( + stats.toString( + /** @type {import("../").StatsOptions} */ ( + /** @type {unknown} */ ({ all: false, env: true, _env: "production" }) - ).toBe('Environment (--env): "production"'); - expect( - stats.toString({ + ) + ) + ).toBe('Environment (--env): "production"'); + expect( + stats.toString( + /** @type {import("../").StatsOptions} */ ( + /** @type {unknown} */ ({ all: false, env: true, _env: { @@ -30,19 +104,273 @@ describe("Stats", () => { baz: true } }) - ).toBe( - "Environment (--env): {\n" + - ' "prod": [\n' + - ' "foo",\n' + - ' "bar"\n' + - " ],\n" + - ' "baz": true\n' + - "}" - ); - done(); - } catch (e) { - done(e); + ) + ) + ).toBe( + "Environment (--env): {\n" + + ' "prod": [\n' + + ' "foo",\n' + + ' "bar"\n' + + " ],\n" + + ' "baz": true\n' + + "}" + ); + }); + + it("should omit all properties with all false", async () => { + const stats = await compile({ + context: __dirname, + entry: "./fixtures/a" + }); + expect( + stats.toJson({ + all: false + }) + ).toEqual({}); + }); + + it("should the results of hasWarnings() be affected by ignoreWarnings", async () => { + const stats = await compile({ + mode: "development", + context: __dirname, + entry: "./fixtures/ignoreWarnings/index", + module: { + rules: [ + { + loader: "./fixtures/ignoreWarnings/loader" + } + ] + }, + ignoreWarnings: [/__mocked__warning__/] + }); + expect(stats.hasWarnings()).toBeFalsy(); + }); + + describe("chunkGroups", () => { + it("should be empty when there is no additional chunks", async () => { + const stats = await compile({ + context: __dirname, + entry: { + entryA: "./fixtures/a", + entryB: "./fixtures/b" + } + }); + expect( + stats.toJson({ + all: false, + errorsCount: true, + chunkGroups: true + }) + ).toMatchInlineSnapshot(` + Object { + "errorsCount": 0, + "namedChunkGroups": Object { + "entryA": Object { + "assets": Array [ + Object { + "name": "entryA.js", + "size": 205, + }, + ], + "assetsSize": 205, + "auxiliaryAssets": undefined, + "auxiliaryAssetsSize": 0, + "childAssets": undefined, + "children": undefined, + "chunks": undefined, + "filteredAssets": 0, + "filteredAuxiliaryAssets": 0, + "name": "entryA", + }, + "entryB": Object { + "assets": Array [ + Object { + "name": "entryB.js", + "size": 205, + }, + ], + "assetsSize": 205, + "auxiliaryAssets": undefined, + "auxiliaryAssetsSize": 0, + "childAssets": undefined, + "children": undefined, + "chunks": undefined, + "filteredAssets": 0, + "filteredAuxiliaryAssets": 0, + "name": "entryB", + }, + }, + } + `); + }); + + it("should contain additional chunks", async () => { + const stats = await compile({ + context: __dirname, + entry: { + entryA: "./fixtures/a", + entryB: "./fixtures/chunk-b" + } + }); + expect( + stats.toJson({ + all: false, + errorsCount: true, + chunkGroups: true + }) + ).toMatchInlineSnapshot(` + Object { + "errorsCount": 0, + "namedChunkGroups": Object { + "chunkB": Object { + "assets": Array [ + Object { + "name": "chunkB.js", + "size": 106, + }, + ], + "assetsSize": 106, + "auxiliaryAssets": undefined, + "auxiliaryAssetsSize": 0, + "childAssets": undefined, + "children": undefined, + "chunks": undefined, + "filteredAssets": 0, + "filteredAuxiliaryAssets": 0, + "name": "chunkB", + }, + "entryA": Object { + "assets": Array [ + Object { + "name": "entryA.js", + "size": 205, + }, + ], + "assetsSize": 205, + "auxiliaryAssets": undefined, + "auxiliaryAssetsSize": 0, + "childAssets": undefined, + "children": undefined, + "chunks": undefined, + "filteredAssets": 0, + "filteredAuxiliaryAssets": 0, + "name": "entryA", + }, + "entryB": Object { + "assets": Array [ + Object { + "name": "entryB.js", + "size": 3310, + }, + ], + "assetsSize": 3310, + "auxiliaryAssets": undefined, + "auxiliaryAssetsSize": 0, + "childAssets": undefined, + "children": undefined, + "chunks": undefined, + "filteredAssets": 0, + "filteredAuxiliaryAssets": 0, + "name": "entryB", + }, + }, + } + `); + }); + + it("should contain assets", async () => { + const stats = await compile({ + context: __dirname, + entry: { + entryA: "./fixtures/a", + entryB: "./fixtures/chunk-b" + } + }); + expect( + stats.toJson({ + all: false, + errorsCount: true, + assets: true + }) + ).toMatchInlineSnapshot(` + Object { + "assets": Array [ + Object { + "auxiliaryChunkIdHints": Array [], + "auxiliaryChunkNames": Array [], + "cached": false, + "chunkIdHints": Array [], + "chunkNames": Array [ + "entryB", + ], + "comparedForEmit": false, + "emitted": true, + "filteredRelated": undefined, + "info": Object { + "javascriptModule": false, + "minimized": true, + "size": 3310, + }, + "name": "entryB.js", + "size": 3310, + "type": "asset", + }, + Object { + "auxiliaryChunkIdHints": Array [], + "auxiliaryChunkNames": Array [], + "cached": false, + "chunkIdHints": Array [], + "chunkNames": Array [ + "entryA", + ], + "comparedForEmit": false, + "emitted": true, + "filteredRelated": undefined, + "info": Object { + "javascriptModule": false, + "minimized": true, + "size": 205, + }, + "name": "entryA.js", + "size": 205, + "type": "asset", + }, + Object { + "auxiliaryChunkIdHints": Array [], + "auxiliaryChunkNames": Array [], + "cached": false, + "chunkIdHints": Array [], + "chunkNames": Array [ + "chunkB", + ], + "comparedForEmit": false, + "emitted": true, + "filteredRelated": undefined, + "info": Object { + "javascriptModule": false, + "minimized": true, + "size": 106, + }, + "name": "chunkB.js", + "size": 106, + "type": "asset", + }, + ], + "assetsByChunkName": Object { + "chunkB": Array [ + "chunkB.js", + ], + "entryA": Array [ + "entryA.js", + ], + "entryB": Array [ + "entryB.js", + ], + }, + "errorsCount": 0, + "filteredAssets": undefined, } + `); }); }); }); diff --git a/test/Stats.unittest.js b/test/Stats.unittest.js deleted file mode 100644 index 9ee873f1656..00000000000 --- a/test/Stats.unittest.js +++ /dev/null @@ -1,209 +0,0 @@ -/*globals describe it */ -"use strict"; - -const Stats = require("../lib/Stats"); -const packageJson = require("../package.json"); - -describe( - "Stats", - () => { - describe("formatFilePath", () => { - it("emit the file path and request", () => { - const mockStats = new Stats({ - children: [], - errors: ["firstError"], - hash: "1234", - compiler: { - context: "" - } - }); - const inputPath = - "./node_modules/ts-loader!./node_modules/vue-loader/lib/selector.js?type=script&index=0!./src/app.vue"; - const expectPath = `./src/app.vue (${inputPath})`; - - expect(mockStats.formatFilePath(inputPath)).toBe(expectPath); - }); - }); - - describe("Error Handling", () => { - describe("does have", () => { - it("hasErrors", () => { - const mockStats = new Stats({ - children: [], - errors: ["firstError"], - hash: "1234", - compiler: { - context: "" - } - }); - expect(mockStats.hasErrors()).toBe(true); - }); - it("hasWarnings", () => { - const mockStats = new Stats({ - children: [], - warnings: ["firstError"], - hash: "1234", - compiler: { - context: "" - } - }); - expect(mockStats.hasWarnings()).toBe(true); - }); - }); - describe("does not have", () => { - it("hasErrors", () => { - const mockStats = new Stats({ - children: [], - errors: [], - hash: "1234", - compiler: { - context: "" - } - }); - expect(mockStats.hasErrors()).toBe(false); - }); - it("hasWarnings", () => { - const mockStats = new Stats({ - children: [], - warnings: [], - hash: "1234", - compiler: { - context: "" - } - }); - expect(mockStats.hasWarnings()).toBe(false); - }); - }); - describe("children have", () => { - it("hasErrors", () => { - const mockStats = new Stats({ - children: [ - { - getStats: () => - new Stats({ - errors: ["firstError"], - hash: "5678" - }) - } - ], - errors: [], - hash: "1234" - }); - expect(mockStats.hasErrors()).toBe(true); - }); - it("hasWarnings", () => { - const mockStats = new Stats({ - children: [ - { - getStats: () => - new Stats({ - warnings: ["firstError"], - hash: "5678" - }) - } - ], - warnings: [], - hash: "1234" - }); - expect(mockStats.hasWarnings()).toBe(true); - }); - }); - it("formatError handles string errors", () => { - const mockStats = new Stats({ - errors: ["firstError"], - warnings: [], - assets: [], - entrypoints: new Map(), - namedChunkGroups: new Map(), - chunks: [], - modules: [], - children: [], - hash: "1234", - mainTemplate: { - outputOptions: { - path: "" - }, - getPublicPath: () => "path" - }, - compiler: { - context: "" - } - }); - const obj = mockStats.toJson(); - expect(obj.errors[0]).toEqual("firstError"); - }); - }); - describe("toJson", () => { - it("returns plain object representation", () => { - const mockStats = new Stats({ - errors: [], - warnings: [], - assets: [], - entrypoints: new Map(), - chunks: [], - namedChunkGroups: new Map(), - modules: [], - children: [], - hash: "1234", - mainTemplate: { - outputOptions: { - path: "/" - }, - getPublicPath: () => "path" - }, - compiler: { - context: "" - } - }); - const result = mockStats.toJson(); - expect(result).toEqual({ - assets: [], - assetsByChunkName: {}, - children: [], - chunks: [], - entrypoints: {}, - namedChunkGroups: {}, - filteredAssets: 0, - filteredModules: 0, - errors: [], - hash: "1234", - modules: [], - outputPath: "/", - publicPath: "path", - version: packageJson.version, - warnings: [] - }); - }); - }); - describe("Presets", () => { - describe("presetToOptions", () => { - it("returns correct object with 'Normal'", () => { - expect(Stats.presetToOptions("Normal")).toEqual({}); - }); - it("truthy values behave as 'normal'", () => { - const normalOpts = Stats.presetToOptions("normal"); - expect(Stats.presetToOptions("pizza")).toEqual(normalOpts); - expect(Stats.presetToOptions(true)).toEqual(normalOpts); - expect(Stats.presetToOptions(1)).toEqual(normalOpts); - - expect(Stats.presetToOptions("verbose")).not.toEqual(normalOpts); - expect(Stats.presetToOptions(false)).not.toEqual(normalOpts); - }); - it("returns correct object with 'none'", () => { - expect(Stats.presetToOptions("none")).toEqual({ - all: false - }); - }); - it("falsy values behave as 'none'", () => { - const noneOpts = Stats.presetToOptions("none"); - expect(Stats.presetToOptions("")).toEqual(noneOpts); - expect(Stats.presetToOptions(null)).toEqual(noneOpts); - expect(Stats.presetToOptions()).toEqual(noneOpts); - expect(Stats.presetToOptions(0)).toEqual(noneOpts); - expect(Stats.presetToOptions(false)).toEqual(noneOpts); - }); - }); - }); - }, - 10000 -); diff --git a/test/StatsTestCases.basictest.js b/test/StatsTestCases.basictest.js new file mode 100644 index 00000000000..56a2e4efdb6 --- /dev/null +++ b/test/StatsTestCases.basictest.js @@ -0,0 +1,329 @@ +"use strict"; + +require("./helpers/warmup-webpack"); + +const path = require("path"); +const fs = require("graceful-fs"); +const rimraf = require("rimraf"); +const webpack = require(".."); +const { registerPerCaseSnapshotHooks } = require("./harness/snapshot"); +const captureStdio = require("./helpers/captureStdio"); +const { + expectOnlyListedDeprecations +} = require("./helpers/expectNoDeprecations"); + +/** + * Escapes regular expression metacharacters + * @param {string} str String to quote + * @returns {string} Escaped string + */ +const quoteMeta = (str) => str.replace(/[-[\]\\/{}()*+?.^$|]/g, "\\$&"); + +const base = path.join(__dirname, "statsCases"); +const outputBase = path.join(__dirname, "js", "stats"); +const tests = fs + .readdirSync(base) + .filter( + (testName) => + fs.existsSync(path.join(base, testName, "index.js")) || + fs.existsSync(path.join(base, testName, "webpack.config.js")) + ) + .filter((testName) => { + const testDirectory = path.join(base, testName); + const filterPath = path.join(testDirectory, "test.filter.js"); + if (fs.existsSync(filterPath) && !require(filterPath)()) { + // eslint-disable-next-line jest/no-disabled-tests, jest/valid-describe-callback + describe.skip(testName, () => it("filtered", () => {})); + + return false; + } + return true; + }); + +/** @typedef {{ toString(): string, toStringRaw(): string, restore(): void, data: string[], reset(): void }} CapturedStdio */ + +describe("StatsTestCases", () => { + jest.setTimeout(30000); + /** @type {CapturedStdio} */ + let stderr; + + beforeEach(() => { + stderr = captureStdio(process.stderr, true); + }); + + afterEach(() => { + stderr.restore(); + }); + + for (const testName of tests) { + const testDirectory = path.join(base, testName); + + // eslint-disable-next-line no-loop-func + describe(testName, () => { + registerPerCaseSnapshotHooks(testDirectory, "StatsTestCases"); + expectOnlyListedDeprecations(() => testDirectory); + + it(`should print correct stats for ${testName}`, (done) => { + const outputDirectory = path.join(outputBase, testName); + rimraf.sync(outputDirectory); + fs.mkdirSync(outputDirectory, { recursive: true }); + /** @type {import("../").Configuration} */ + let options = { + mode: "development", + entry: "./index", + output: { + filename: "bundle.js" + } + }; + if (fs.existsSync(path.join(testDirectory, "webpack.config.js"))) { + options = require(path.join(testDirectory, "webpack.config.js")); + } + /** @type {{ validate?: (stats: import("../").Stats, stderr: string) => void }} */ + let testConfig = {}; + try { + // try to load a test file + testConfig = Object.assign( + testConfig, + require(path.join(testDirectory, "test.config.js")) + ); + } catch (_err) { + // ignored + } + + const resolvedOptions = Array.isArray(options) ? options : [options]; + for (const options of resolvedOptions) { + if (!options.context) options.context = testDirectory; + if (!options.output) options.output = options.output || {}; + if (!options.output.path) options.output.path = outputDirectory; + if (!options.plugins) options.plugins = []; + if (!options.optimization) options.optimization = {}; + if (options.optimization.minimize === undefined) { + options.optimization.minimize = false; + } + if ( + options.cache && + options.cache !== true && + options.cache.type === "filesystem" + ) { + options.cache.cacheDirectory = path.resolve( + outputBase, + ".cache", + testName + ); + } + } + const c = webpack(options); + const cAny = /** @type {EXPECTED_ANY} */ (c); + const compilers = /** @type {import("../").Compiler[]} */ ( + cAny.compilers ? cAny.compilers : [c] + ); + for (const c of compilers) { + const ifs = /** @type {NonNullable} */ ( + c.inputFileSystem + ); + c.inputFileSystem = Object.create(ifs); + /** @type {NonNullable} */ ( + c.inputFileSystem + ).readFile = function readFile() { + // eslint-disable-next-line prefer-rest-params + const args = Array.prototype.slice.call(arguments); + const callback = args.pop(); + // eslint-disable-next-line no-useless-call + /** @type {EXPECTED_ANY} */ ( + /** @type {NonNullable} */ (ifs).readFile + ).apply(ifs, [ + ...args, + ( + /** @type {Error | null} */ err, + /** @type {Buffer | undefined} */ result + ) => { + if (err) return callback(err); + if (!/\.(?:js|json|txt)$/.test(args[0])) { + return callback(null, result); + } + callback( + null, + /** @type {Buffer} */ (result) + .toString("utf8") + .replace(/\r/g, "") + ); + } + ]); + }; + c.hooks.compilation.tap( + "StatsTestCasesTest", + (/** @type {import("../").Compilation} */ compilation) => { + for (const hook of [ + "optimize", + "optimizeModules", + "optimizeChunks", + "afterOptimizeTree", + "afterOptimizeAssets", + "beforeHash" + ]) { + /** @type {Record} */ (compilation.hooks)[ + hook + ].tap("TestCasesTest", () => compilation.checkConstraints()); + } + } + ); + } + c.run((err, _stats) => { + if (err) return done(err); + const stats = /** @type {import("../").Stats} */ (_stats); + const statsAny = /** @type {EXPECTED_ANY} */ (stats); + for (const compilation of /** @type {import("../").Compilation[]} */ ( + [...(statsAny.stats ? statsAny.stats : [stats])].map( + (/** @type {EXPECTED_ANY} */ s) => s.compilation + ) + )) { + compilation.logging.delete("webpack.Compilation.ModuleProfile"); + } + expect(stats.hasErrors()).toBe(testName.endsWith("error")); + if (!testName.endsWith("error") && stats.hasErrors()) { + return done( + new Error( + stats.toString({ + all: false, + errors: true, + errorStack: true, + errorDetails: true + }) + ) + ); + } + fs.writeFileSync( + path.join(outputBase, testName, "stats.txt"), + stats.toString({ + preset: "verbose", + context: testDirectory, + colors: false + }), + "utf8" + ); + + /** @type {EXPECTED_ANY} */ + let toStringOptions = { + context: testDirectory, + colors: false + }; + let hasColorSetting = false; + const cOptions = /** @type {EXPECTED_ANY} */ (c.options); + if (typeof cOptions.stats !== "undefined") { + toStringOptions = cOptions.stats; + if ( + toStringOptions === null || + typeof toStringOptions !== "object" + ) { + toStringOptions = { preset: toStringOptions }; + } + if (!toStringOptions.context) { + toStringOptions.context = testDirectory; + } + hasColorSetting = typeof toStringOptions.colors !== "undefined"; + } + if (Array.isArray(cOptions) && !toStringOptions.children) { + toStringOptions.children = cOptions.map( + (/** @type {EXPECTED_ANY} */ o) => o.stats + ); + } + // mock timestamps + for (const { compilation: s } of statsAny.stats + ? statsAny.stats + : [stats]) { + expect(/** @type {EXPECTED_ANY} */ (s).startTime).toBeGreaterThan( + 0 + ); + expect(/** @type {EXPECTED_ANY} */ (s).endTime).toBeGreaterThan(0); + /** @type {EXPECTED_ANY} */ (s).endTime = new Date( + "04/20/1970, 12:42:42 PM" + ).getTime(); + /** @type {EXPECTED_ANY} */ (s).startTime = + /** @type {EXPECTED_ANY} */ (s).endTime - 1234; + } + let actual = stats.toString(toStringOptions); + expect(typeof actual).toBe("string"); + if (!hasColorSetting) { + actual = stderr.toString() + actual; + actual = actual + .replace(/\u001B\[[0-9;]*m/g, "") + .replace(/[.0-9]+(\s?ms)/g, "X$1"); + } else { + actual = stderr.toStringRaw() + actual; + actual = actual + .replace(/\u001B\[1m\u001B\[([0-9;]*)m/g, "") + .replace(/\u001B\[1m/g, "") + .replace(/\u001B\[39m\u001B\[22m/g, "") + .replace(/\u001B\[([0-9;]*)m/g, "") + .replace(/[.0-9]+(<\/CLR>)?(\s?ms)/g, "X$1$2"); + } + // cspell:ignore Xdir + actual = actual + .replace(/\r\n?/g, "\n") + .replace( + /webpack [^ )]+(\)?) compiled/g, + "webpack x.x.x$1 compiled" + ) + .replace( + new RegExp(quoteMeta(testDirectory), "g"), + `Xdir/${testName}` + ) + .replace(/(\w)\\(\w)/g, "$1/$2") + .replace(/, additional resolving: X ms/g, "") + .replace(/Unexpected identifier '.+?'/g, "Unexpected identifier") + // Normalize JSC (Bun) engine error phrasings to the V8 form. + .replace( + /Unexpected identifier\. Expected a ':' following the property name '[^']*'\./g, + "Unexpected identifier" + ) + .replace( + /JSON Parse error: Unexpected EOF/g, + "Unexpected end of JSON input" + ) + .replace(/[.0-9]+(\s?(bytes|KiB|MiB|GiB))/g, "X$1") + .replace( + /ms\s\([0-9a-f]{6,32}\)|(?!\d+-)[0-9a-f-]{6,32}\./g, + (match) => `${match.replace(/[0-9a-f]/g, "X")}` + ) + // Normalize stack traces between Jest v27 and v30 + // Jest v27: at Object..module.exports + // Jest v30: at Object.module.exports + .replace(/Object\.\./g, "Object."); + // Normalize logger trace frames across engines: V8 emits one + // "at fn (file:line:col)" frame, while JSC (Bun) emits extra + // internal frames, omits the function name, and reports different + // line:col. Keep only frames inside the test dir, reduced to the + // file path. + const traceFile = new RegExp( + `Xdir/${quoteMeta(testName)}/[^\\s():]+` + ); + actual = actual + .split("\n") + .reduce((lines, line) => { + const prefix = line.match(/^(\s*\|\s+)at\b/); + if (!prefix) { + lines.push(line); + return lines; + } + const file = line.match(traceFile); + if (file) lines.push(`${prefix[1]}at ${file[0]}`); + return lines; + }, /** @type {string[]} */ ([])) + .join("\n"); + expect(actual).toMatchSnapshot(); + + if (testConfig.validate) { + try { + testConfig.validate(stats, stderr.toString()); + } catch (err) { + done(err); + return; + } + } + + done(); + }); + }); + }); + } +}); diff --git a/test/StatsTestCases.test.js b/test/StatsTestCases.test.js deleted file mode 100644 index 6ab8049d6d0..00000000000 --- a/test/StatsTestCases.test.js +++ /dev/null @@ -1,126 +0,0 @@ -/*globals describe it */ -"use strict"; - -const path = require("path"); -const fs = require("fs"); - -const webpack = require("../lib/webpack"); -const Stats = require("../lib/Stats"); - -const base = path.join(__dirname, "statsCases"); -const outputBase = path.join(__dirname, "js", "stats"); -const tests = fs - .readdirSync(base) - .filter( - testName => - fs.existsSync(path.join(base, testName, "index.js")) || - fs.existsSync(path.join(base, testName, "webpack.config.js")) - ); - -describe("StatsTestCases", () => { - tests.forEach(testName => { - it("should print correct stats for " + testName, done => { - jest.setTimeout(10000); - let options = { - mode: "development", - entry: "./index", - output: { - filename: "bundle.js" - } - }; - if (fs.existsSync(path.join(base, testName, "webpack.config.js"))) { - options = require(path.join(base, testName, "webpack.config.js")); - } - (Array.isArray(options) ? options : [options]).forEach(options => { - if (!options.context) options.context = path.join(base, testName); - if (!options.output) options.output = options.output || {}; - if (!options.output.path) - options.output.path = path.join(outputBase, testName); - if (!options.plugins) options.plugins = []; - if (!options.optimization) options.optimization = {}; - if (options.optimization.minimize === undefined) - options.optimization.minimize = false; - // To support deprecated loaders - // TODO remove in webpack 5 - options.plugins.push( - new webpack.LoaderOptionsPlugin({ - options: {} - }) - ); - }); - const c = webpack(options); - const compilers = c.compilers ? c.compilers : [c]; - compilers.forEach(c => { - const ifs = c.inputFileSystem; - c.inputFileSystem = Object.create(ifs); - c.inputFileSystem.readFile = function() { - const args = Array.prototype.slice.call(arguments); - const callback = args.pop(); - ifs.readFile.apply( - ifs, - args.concat([ - (err, result) => { - if (err) return callback(err); - callback(null, result.toString("utf-8").replace(/\r/g, "")); - } - ]) - ); - }; - new webpack.optimize.OccurrenceOrderPlugin().apply(c); - }); - c.run((err, stats) => { - if (err) return done(err); - if (/error$/.test(testName)) { - expect(stats.hasErrors()).toBe(true); - } else if (stats.hasErrors()) { - return done(new Error(stats.toJson().errors.join("\n\n"))); - } - let toStringOptions = { - context: path.join(base, testName), - colors: false - }; - let hasColorSetting = false; - if (typeof options.stats !== "undefined") { - toStringOptions = options.stats; - if (toStringOptions === null || typeof toStringOptions !== "object") - toStringOptions = Stats.presetToOptions(toStringOptions); - hasColorSetting = typeof options.stats.colors !== "undefined"; - if (!toStringOptions.context) - toStringOptions.context = path.join(base, testName); - } - if (Array.isArray(options) && !toStringOptions.children) { - toStringOptions.children = options.map(o => o.stats); - } - let actual = stats.toString(toStringOptions); - expect(typeof actual).toBe("string"); - if (!hasColorSetting) { - actual = actual - .replace(/\u001b\[[0-9;]*m/g, "") - .replace(/[0-9]+(\s?ms)/g, "X$1") - .replace( - /^(\s*Built at:) (.*)$/gm, - "$1 Thu Jan 01 1970 00:00:00 GMT" - ); - } else { - actual = actual - .replace(/\u001b\[1m\u001b\[([0-9;]*)m/g, "") - .replace(/\u001b\[1m/g, "") - .replace(/\u001b\[39m\u001b\[22m/g, "") - .replace(/\u001b\[([0-9;]*)m/g, "") - .replace(/[0-9]+(<\/CLR>)?(\s?ms)/g, "X$1$2") - .replace( - /^(\s*Built at:) (.*)$/gm, - "$1 Thu Jan 01 1970 00:00:00 GMT" - ); - } - actual = actual - .replace(/\r\n?/g, "\n") - .replace(/[\t ]*Version:.+\n/g, "") - .replace(path.join(base, testName), "Xdir/" + testName) - .replace(/ dependencies:Xms/g, ""); - expect(actual).toMatchSnapshot(); - done(); - }); - }); - }); -}); diff --git a/test/Template.unittest.js b/test/Template.unittest.js index 8b744e9c58c..dccc8ed3faa 100644 --- a/test/Template.unittest.js +++ b/test/Template.unittest.js @@ -6,19 +6,40 @@ describe("Template", () => { it("should generate valid identifiers", () => { expect(Template.toIdentifier("0abc-def9")).toBe("_0abc_def9"); }); + it("should generate valid number identifiers", () => { const items = []; let item; for (let i = 0; i < 80; i += 1) { - item = Template.numberToIdentifer(i); + item = Template.numberToIdentifier(i); expect(item).not.toBe(""); expect(items).not.toContain(item); items.push(item); } }); + + // cspell:ignore sdfas sadfome it("should generate sanitized path identifiers", () => { expect(Template.toPath("path/to-sdfas/sadfome$$.js")).toBe( "path-to-sdfas-sadfome$$-js" ); }); + + it("should strip JSDoc types from runtime function content but keep other comments", () => { + const content = Template.getFunctionContent({ + toString: () => + [ + "function () {", + "\t/** @type {number} */", + "\tvar a = /** @type {EXPECTED_ANY} */ (1);", + "\t// keep this line comment", + "\treturn a /* keep */;", + "}" + ].join("\n") + }); + expect(content).not.toContain("@type"); + expect(content).toContain("var a = (1);"); + expect(content).toContain("// keep this line comment"); + expect(content).toContain("/* keep */"); + }); }); diff --git a/test/TemplatedPathPlugin.unittest.js b/test/TemplatedPathPlugin.unittest.js new file mode 100644 index 00000000000..c5cbbc45bcc --- /dev/null +++ b/test/TemplatedPathPlugin.unittest.js @@ -0,0 +1,140 @@ +"use strict"; + +const { interpolate } = require("../lib/TemplatedPathPlugin"); + +describe("TemplatedPathPlugin.interpolate", () => { + it("returns literal paths unchanged", () => { + expect(interpolate("static/main.js", {})).toBe("static/main.js"); + }); + + it("resolves a TemplatePathFn before interpolating", () => { + expect(interpolate((data) => `${data.url}.js`, { url: "x" })).toBe("x.js"); + }); + + it("interpolates filename placeholders", () => { + const data = { filename: "/a/b/file.js?q=1#frag" }; + expect(interpolate("[path][name][ext]", data)).toBe("/a/b/file.js"); + expect(interpolate("[base]", data)).toBe("file.js"); + expect(interpolate("[query]", data)).toBe("?q=1"); + expect(interpolate("[fragment]", data)).toBe("#frag"); + expect(interpolate("[file]", data)).toBe("/a/b/file.js"); + }); + + it("interpolates data-uri filename placeholders", () => { + const data = { filename: "data:image/png;base64,AAAA" }; + expect(interpolate("[ext]", data)).toBe(".png"); + expect(interpolate("[base][query][fragment][path]", data)).toBe(""); + // unknown mime type yields an empty [ext] + expect(interpolate("[ext]", { filename: "data:weird/x,AA" })).toBe(""); + }); + + it("supports the legacy [filebase] placeholder", () => { + expect(interpolate("[filebase]", { filename: "/a/file.js" })).toBe( + "file.js" + ); + expect( + interpolate("[filebase]", { filename: "data:image/png;base64,AA" }) + ).toBe(""); + }); + + it("interpolates [fullhash] and legacy [hash]", () => { + const data = { hash: "0123456789abcdef" }; + expect(interpolate("[fullhash]", data)).toBe("0123456789abcdef"); + expect(interpolate("[fullhash:4]", data)).toBe("0123"); + expect(interpolate("[hash]", data)).toBe("0123456789abcdef"); + }); + + it("interpolates chunk placeholders", () => { + const data = { + chunk: { + id: "7", + name: "app", + hash: "1122334455", + contentHash: { javascript: "9988776655" } + }, + contentHashType: "javascript" + }; + expect(interpolate("[id]", data)).toBe("7"); + expect(interpolate("[name]", data)).toBe("app"); + expect(interpolate("[chunkhash]", data)).toBe("1122334455"); + expect(interpolate("[contenthash:4]", data)).toBe("9988"); + // chunk name falls back to id when unnamed + expect( + interpolate( + "[name]", + /** @type {EXPECTED_ANY} */ ({ chunk: { id: "9" } }) + ) + ).toBe("9"); + }); + + it("interpolates module placeholders incl. legacy aliases", () => { + const data = { module: { id: "42", hash: "9876543210" } }; + expect(interpolate("[id]", data)).toBe("42"); + expect(interpolate("[moduleid]", data)).toBe("42"); + expect(interpolate("[modulehash:3]", data)).toBe("987"); + expect(interpolate("[hash]", data)).toBe("9876543210"); + // `[hash]` aliases the content hash when present + expect( + interpolate("[hash]", { + module: { id: "1", hash: "h" }, + contentHash: "c" + }) + ).toBe("c"); + }); + + it("interpolates [url] and [runtime]", () => { + expect(interpolate("[url]", { url: "u" })).toBe("u"); + expect(interpolate("[runtime]", { runtime: "main" })).toBe("main"); + // non-string runtime collapses to "_" + expect( + interpolate( + "[runtime]", + /** @type {EXPECTED_ANY} */ ({ runtime: ["a", "b"] }) + ) + ).toBe("_"); + }); + + it("interpolates [uniqueName] and its [uniquename] alias", () => { + const data = { uniqueName: "my-app" }; + expect(interpolate("[uniqueName].js", data)).toBe("my-app.js"); + expect(interpolate("[uniquename].js", data)).toBe("my-app.js"); + expect(interpolate("[uniqueName]", { uniqueName: "" })).toBe(""); + // left untouched when no uniqueName is provided + expect(interpolate("[uniqueName]", {})).toBe("[uniqueName]"); + }); + + it("unescapes bracket-escaped placeholders", () => { + expect(interpolate("[\\name\\]", {})).toBe("[name]"); + }); + + it("records hashes into assetInfo, accumulating repeats", () => { + const assetInfo = {}; + interpolate("[contenthash]", { + module: { id: "1", hash: "h" }, + contentHash: "c1" + }); + const info = { contenthash: "old" }; + interpolate( + "[contenthash]", + { module: { id: "1", hash: "h" }, contentHash: "c2" }, + info + ); + expect(info.contenthash).toEqual(["old", "c2"]); + const arrInfo = { contenthash: ["x"] }; + interpolate( + "[contenthash]", + { module: { id: "1", hash: "h" }, contentHash: "c3" }, + arrInfo + ); + expect(arrInfo.contenthash).toEqual(["x", "c3"]); + expect(assetInfo).toEqual({}); + }); + + it("clears the bounded present-kinds cache past its limit", () => { + // Exceed PRESENT_KINDS_CACHE_MAX (1000) distinct templates so the cache + // clear branch runs; results must still be correct afterwards. + for (let i = 0; i < 1100; i++) { + expect(interpolate(`a${i}[name]`, { filename: "x.js" })).toBe(`a${i}x`); + } + }); +}); diff --git a/test/TestCases.template.js b/test/TestCases.template.js index 57dab3269db..ae604c57547 100644 --- a/test/TestCases.template.js +++ b/test/TestCases.template.js @@ -1,271 +1,555 @@ -/* global describe it beforeAll expect */ "use strict"; +require("./helpers/warmup-webpack"); + +/** @typedef {{ name: string, tests: string[] }} Category */ +/** + * @typedef {object} SuiteConfig + * @property {string} name suite name + * @property {string=} target target + * @property {string=} mode mode + * @property {boolean=} module module + * @property {boolean=} minimize minimize + * @property {string | false=} devtool devtool + * @property {EXPECTED_ANY=} cache cache + * @property {EXPECTED_ANY=} snapshot snapshot + * @property {EXPECTED_ANY=} optimization optimization + * @property {string[]=} deprecations expected deprecations + * @property {EXPECTED_ANY[]=} plugins plugins + */ +/** + * @typedef {object} TestConfig + * @property {((i: EXPECTED_ANY, options: EXPECTED_ANY) => string)=} findBundle + * @property {number=} timeout + * @property {number=} cachedTimeout + * @property {boolean=} noTests + * @property {((scope: EXPECTED_ANY, options: EXPECTED_ANY) => void)=} moduleScope + */ + const path = require("path"); -const fs = require("fs"); -const vm = require("vm"); -const mkdirp = require("mkdirp"); -const UglifyJsPlugin = require("uglifyjs-webpack-plugin"); +const fs = require("graceful-fs"); +/** @type {{ sync: (p: string) => void, (p: string, cb: (err: EXPECTED_ANY) => void): void }} */ +const rimraf = require("rimraf"); +const { parseResource } = require("../lib/util/identifier"); const checkArrayExpectation = require("./checkArrayExpectation"); +const { TestRunner } = require("./harness/runner"); +const captureStdio = require("./helpers/captureStdio"); +const createLazyTestEnv = require("./helpers/createLazyTestEnv"); +const deprecationTracking = require("./helpers/deprecationTracking"); +const filterInfraStructureErrors = require("./helpers/infrastructureLogErrors"); +const supportsObjectHasOwn = require("./helpers/supportsObjectHasOwn"); +const supportsOptionalChaining = require("./helpers/supportsOptionalChaining"); -const Stats = require("../lib/Stats"); -const webpack = require("../lib/webpack"); +const casesPath = path.join(__dirname, "cases"); +/** @type {Category[]} */ +const categories = fs.readdirSync(casesPath).map((cat) => ({ + name: cat, + tests: fs + .readdirSync(path.join(casesPath, cat)) + .filter((folder) => !folder.includes("_")) +})); -const uglifyJsForTesting = new UglifyJsPlugin({ - cache: false, - parallel: false, - sourceMap: true +/** + * @param {string[]} appendTarget log collector + * @param {string[]} appendErrors warn/error collector + * @returns {EXPECTED_ANY} logger object + */ +const createLogger = (appendTarget, appendErrors) => ({ + log: (/** @type {string} */ l) => appendTarget.push(l), + debug: (/** @type {string} */ l) => appendTarget.push(l), + trace: (/** @type {string} */ l) => appendTarget.push(l), + info: (/** @type {string} */ l) => appendTarget.push(l), + // Collect warn/error separately: every infrastructure warning/error must be + // declared in the case's infrastructure-log.js or the test fails, so a cache + // store/restore failure can't slip through unnoticed. + warn: (/** @type {string} */ l, /** @type {EXPECTED_ANY[]} */ ...args) => { + appendErrors.push(l); + console.warn(l, ...args); + }, + error: (/** @type {string} */ l, /** @type {EXPECTED_ANY[]} */ ...args) => { + appendErrors.push(l); + console.error(l, ...args); + }, + logTime: () => {}, + group: () => {}, + groupCollapsed: () => {}, + groupEnd: () => {}, + profile: () => {}, + profileEnd: () => {}, + clear: () => {}, + status: () => {} }); -const DEFAULT_OPTIMIZATIONS = { - removeAvailableModules: true, - removeEmptyChunks: true, - mergeDuplicateChunks: true, - flagIncludedChunks: true, - occurrenceOrder: true, - sideEffects: true, - providedExports: true, - usedExports: true, - noEmitOnErrors: false, - concatenateModules: false, - namedModules: false, - minimizer: [uglifyJsForTesting] -}; +/** + * @param {SuiteConfig} config suite config + */ +const describeCases = (config) => { + describe(config.name, () => { + /** @type {ReturnType} */ + let stderr; -const NO_EMIT_ON_ERRORS_OPTIMIZATIONS = { - noEmitOnErrors: false, - minimizer: [uglifyJsForTesting] -}; + beforeEach(() => { + stderr = captureStdio(process.stderr, true); + }); -const casesPath = path.join(__dirname, "cases"); -let categories = fs.readdirSync(casesPath); -categories = categories.map(cat => { - return { - name: cat, - tests: fs - .readdirSync(path.join(casesPath, cat)) - .filter(folder => folder.indexOf("_") < 0) - }; -}); + afterEach(() => { + stderr.restore(); + }); -const describeCases = config => { - describe(config.name, () => { - categories.forEach(category => { - describe(category.name, function() { - jest.setTimeout(20000); + for (const category of categories) { + // eslint-disable-next-line no-loop-func + describe(category.name, () => { + jest.setTimeout(30000); + + for (const testName of category.tests.filter((test) => { + const testDirectory = path.join(casesPath, category.name, test); + const filterPath = path.join(testDirectory, "test.filter.js"); + if (fs.existsSync(filterPath) && !require(filterPath)(config)) { + // eslint-disable-next-line jest/no-disabled-tests + describe.skip(test, () => { + it("filtered", () => {}); + }); + + return false; + } + return true; + })) { + /** @type {string[]} */ + const infraStructureLog = []; + /** @type {string[]} */ + const infraStructureErrors = []; - category.tests - .filter(test => { - const testDirectory = path.join(casesPath, category.name, test); - const filterPath = path.join(testDirectory, "test.filter.js"); - if (fs.existsSync(filterPath) && !require(filterPath)(config)) { - describe.skip(test, () => it("filtered")); - return false; + // eslint-disable-next-line no-loop-func + describe(testName, () => { + const testDirectory = path.join(casesPath, category.name, testName); + const outputDirectory = path.join( + __dirname, + "js", + config.name, + category.name, + testName + ); + const cacheDirectory = path.join( + __dirname, + "js/.cache", + config.name, + category.name, + testName + ); + /** @type {TestConfig} */ + let testConfig = { + findBundle(_, options) { + const ext = path.extname( + parseResource(options.output.filename).path + ); + return `./bundle${ext}`; + } + }; + const testConfigPath = path.join(testDirectory, "test.config.js"); + if (fs.existsSync(testConfigPath)) { + testConfig = require(testConfigPath); } - return true; - }) - .forEach(testName => { - describe(testName, () => { - const testDirectory = path.join( - casesPath, - category.name, - testName - ); - const outputDirectory = path.join( - __dirname, - "js", - config.name, - category.name, - testName - ); - const options = { - context: casesPath, - entry: "./" + category.name + "/" + testName + "/index", - target: "async-node", - devtool: config.devtool, - mode: config.mode || "none", - optimization: config.mode - ? NO_EMIT_ON_ERRORS_OPTIMIZATIONS - : Object.assign( - {}, - config.optimization, - DEFAULT_OPTIMIZATIONS - ), - performance: { - hints: false - }, - output: { - pathinfo: true, - path: outputDirectory, - filename: "bundle.js" - }, - resolve: { - modules: ["web_modules", "node_modules"], - mainFields: [ - "webpack", - "browser", - "web", - "browserify", - ["jam", "main"], - "main" - ], - aliasFields: ["browser"], - extensions: [ - ".mjs", - ".webpack.js", - ".web.js", - ".js", - ".json" - ], - concord: true - }, - resolveLoader: { - modules: [ - "web_loaders", - "web_modules", - "node_loaders", - "node_modules" - ], - mainFields: ["webpackLoader", "webLoader", "loader", "main"], - extensions: [ - ".webpack-loader.js", - ".web-loader.js", - ".loader.js", - ".js" - ] - }, - module: { - rules: [ - { - test: /\.coffee$/, - loader: "coffee-loader" - }, - { - test: /\.pug/, - loader: "pug-loader" - }, - { - test: /\.wat$/i, - loader: "wast-loader", - type: "webassembly/experimental" + + const TerserPlugin = require("minimizer-webpack-plugin"); + + const terserForTesting = new TerserPlugin({ + parallel: false + }); + /** @type {import("../").Configuration} */ + let options = /** @type {import("../").Configuration} */ ({ + context: casesPath, + entry: `./${category.name}/${testName}/`, + target: config.target || "async-node", + devtool: config.devtool, + mode: config.mode || "none", + optimization: config.mode + ? { + emitOnErrors: true, + minimizer: [terserForTesting], + ...config.optimization + } + : { + removeAvailableModules: true, + removeEmptyChunks: true, + mergeDuplicateChunks: true, + flagIncludedChunks: true, + sideEffects: true, + providedExports: true, + usedExports: true, + mangleExports: true, + emitOnErrors: true, + concatenateModules: false, + moduleIds: "size", + chunkIds: "size", + minimizer: [terserForTesting], + ...config.optimization + }, + performance: { + hints: false + }, + node: { + __dirname: "mock", + __filename: "mock" + }, + cache: config.cache && { + cacheDirectory, + ...config.cache + }, + output: { + pathinfo: "verbose", + path: outputDirectory, + filename: config.module ? "bundle.mjs" : "bundle.js", + // generated runtime runs in this Node.js process; avoid `?.` on + // Node < 14 and `Object.hasOwn` on Node < 16.9 + ...(supportsOptionalChaining() && supportsObjectHasOwn() + ? {} + : { + environment: { + ...(supportsOptionalChaining() + ? {} + : { optionalChaining: false }), + ...(supportsObjectHasOwn() ? {} : { hasOwn: false }) + } + }) + }, + resolve: { + modules: ["web_modules", "node_modules"], + mainFields: [ + "webpack", + "browser", + "web", + "browserify", + ["jam", "main"], + "main" + ], + aliasFields: ["browser"], + extensions: [".webpack.js", ".web.js", ".js", ".json"] + }, + resolveLoader: { + modules: [ + "web_loaders", + "web_modules", + "node_loaders", + "node_modules" + ], + mainFields: ["webpackLoader", "webLoader", "loader", "main"], + extensions: [ + ".webpack-loader.js", + ".web-loader.js", + ".loader.js", + ".js" + ] + }, + module: { + rules: [ + { + test: /\.coffee$/, + loader: "coffee-loader" + }, + { + test: /\.pug/, + loader: "@webdiscus/pug-loader" + }, + { + test: /\.wat$/i, + loader: "wast-loader", + type: "webassembly/async" + } + ] + }, + plugins: [ + ...(config.plugins || []), + /** @this {import("../").Compiler} */ + function testCasesTest() { + this.hooks.compilation.tap( + "TestCasesTest", + (/** @type {EXPECTED_ANY} */ compilation) => { + for (const hook of [ + "optimize", + "optimizeModules", + "optimizeChunks", + "afterOptimizeTree", + "afterOptimizeAssets" + ]) { + compilation.hooks[hook].tap("TestCasesTest", () => + compilation.checkConstraints() + ); + } } - ] - }, - plugins: (config.plugins || []).concat(function() { - this.hooks.compilation.tap("TestCasesTest", compilation => { - [ - "optimize", - "optimizeModulesBasic", - "optimizeChunksBasic", - "afterOptimizeTree", - "afterOptimizeAssets" - ].forEach(hook => { - compilation.hooks[hook].tap("TestCasesTest", () => - compilation.checkConstraints() - ); - }); - }); - }) - }; + ); + } + ], + experiments: { + asyncWebAssembly: true, + topLevelAwait: true, + backCompat: false, + ...(config.module ? { outputModule: true } : {}) + }, + infrastructureLogging: config.cache && { + debug: true, + console: createLogger(infraStructureLog, infraStructureErrors) + } + }); + + beforeAll((done) => { + rimraf(cacheDirectory, done); + }); + + /** @type {(() => void)[]} */ + const cleanups = []; + + afterAll(() => { + options = /** @type {EXPECTED_ANY} */ (undefined); + testConfig = /** @type {EXPECTED_ANY} */ (undefined); + for (const fn of cleanups) fn(); + }); + + if (config.cache) { it( - testName + " should compile", - done => { - const exportedTests = []; - webpack(options, (err, stats) => { - if (err) done(err); - const statOptions = Stats.presetToOptions("verbose"); - statOptions.colors = false; - mkdirp.sync(outputDirectory); - fs.writeFileSync( - path.join(outputDirectory, "stats.txt"), - stats.toString(statOptions), - "utf-8" - ); - const jsonStats = stats.toJson({ - errorDetails: true - }); + `${testName} should pre-compile to fill disk cache (1st)`, + (done) => { + const output = /** @type {EXPECTED_ANY} */ (options.output); + const oldPath = output.path; + output.path = path.join( + /** @type {string} */ (output.path), + "cache1" + ); + infraStructureLog.length = 0; + infraStructureErrors.length = 0; + const deprecationTracker = deprecationTracking.start(); + + const webpack = require(".."); + + webpack(options, (err) => { + deprecationTracker(); + output.path = oldPath; + if (err) return done(err); + const infrastructureLogErrors = [ + ...filterInfraStructureErrors(infraStructureLog, { + run: 1, + options + }), + ...infraStructureErrors.map((message) => ({ message })) + ]; if ( + infrastructureLogErrors.length && checkArrayExpectation( testDirectory, - jsonStats, - "error", - "Error", + { infrastructureLogs: infrastructureLogErrors }, + "infrastructureLog", + "infrastructure-log", + "InfrastructureLog", + options, done ) - ) + ) { return; + } + done(); + }); + }, + testConfig.timeout || 60000 + ); + + it( + `${testName} should pre-compile to fill disk cache (2nd)`, + (done) => { + const output2 = /** @type {EXPECTED_ANY} */ (options.output); + const oldPath = output2.path; + output2.path = path.join( + /** @type {string} */ (output2.path), + "cache2" + ); + infraStructureLog.length = 0; + infraStructureErrors.length = 0; + const deprecationTracker = deprecationTracking.start(); + + const webpack = require(".."); + + webpack(options, (err) => { + deprecationTracker(); + output2.path = oldPath; + if (err) return done(err); + const infrastructureLogErrors = [ + ...filterInfraStructureErrors(infraStructureLog, { + run: 2, + options + }), + ...infraStructureErrors.map((message) => ({ message })) + ]; if ( + infrastructureLogErrors.length && checkArrayExpectation( testDirectory, - jsonStats, - "warning", - "Warning", + { infrastructureLogs: infrastructureLogErrors }, + "infrastructureLog", + "infrastructure-log", + "InfrastructureLog", + options, done ) - ) + ) { return; - - function _it(title, fn) { - exportedTests.push({ title, fn, timeout: 10000 }); } + done(); + }); + }, + testConfig.cachedTimeout || testConfig.timeout || 10000 + ); + } - function _require(module) { - if (module.substr(0, 2) === "./") { - const p = path.join(outputDirectory, module); - const fn = vm.runInThisContext( - "(function(require, module, exports, __dirname, it, expect) {" + - "global.expect = expect;" + - fs.readFileSync(p, "utf-8") + - "\n})", - p - ); - const m = { - exports: {}, - webpackTestSuiteModule: true - }; - fn.call( - m.exports, - _require, - m, - m.exports, - outputDirectory, - _it, - expect - ); - return m.exports; - } else return require(module); - } - _require.webpackTestSuiteRequire = true; - _require("./bundle.js"); - if (exportedTests.length === 0) - return done(new Error("No tests exported by test case")); + it( + `${testName} should compile`, + (done) => { + infraStructureLog.length = 0; - const asyncSuite = describe(`${config.name} ${ - category.name - } ${testName} exported tests`, () => { - exportedTests.forEach( - ({ title, fn, timeout }) => - fn - ? fit(title, fn, timeout) - : fit(title, () => {}).pend("Skipped") + const webpack = require(".."); + + const compiler = webpack(options); + const run = () => { + const deprecationTracker = deprecationTracking.start(); + compiler.run((err, _stats) => { + const stats = /** @type {import("../").Stats} */ (_stats); + const deprecations = deprecationTracker(); + if (err) return done(err); + const infrastructureLogErrors = [ + ...filterInfraStructureErrors(infraStructureLog, { + run: 3, + options + }), + ...infraStructureErrors.map((message) => ({ message })) + ]; + if ( + infrastructureLogErrors.length && + checkArrayExpectation( + testDirectory, + { infrastructureLogs: infrastructureLogErrors }, + "infrastructureLog", + "infrastructure-log", + "InfrastructureLog", + options, + done + ) + ) { + return; + } + compiler.close((err) => { + if (err) return done(err); + const statOptions = { + preset: "verbose", + colors: false, + modules: true, + reasonsSpace: 1000 + }; + fs.mkdirSync(outputDirectory, { recursive: true }); + fs.writeFileSync( + path.join(outputDirectory, "stats.txt"), + stats.toString(statOptions), + "utf8" ); + const jsonStats = stats.toJson({ + errorDetails: true, + modules: false, + assets: false, + chunks: false + }); + if ( + checkArrayExpectation( + testDirectory, + jsonStats, + "error", + "Error", + options, + done + ) + ) { + return; + } + if ( + checkArrayExpectation( + testDirectory, + jsonStats, + "warning", + "Warning", + options, + done + ) + ) { + return; + } + const infrastructureLogging = stderr.toString(); + if (infrastructureLogging) { + done( + new Error( + `Errors/Warnings during build:\n${infrastructureLogging}` + ) + ); + } + + expect(deprecations).toEqual(config.deprecations || []); + + Promise.resolve().then(done); }); - // workaround for jest running clearSpies on the wrong suite (invoked by clearResourcesForRunnable) - asyncSuite.disabled = true; + }); + }; + if (config.cache) { + // pre-compile to fill memory cache + const deprecationTracker = deprecationTracking.start(); + compiler.run((err) => { + deprecationTracker(); + if (err) return done(err); + run(); + }); + } else { + run(); + } + }, + testConfig.cachedTimeout || + testConfig.timeout || + (config.cache ? 20000 : 60000) + ); - jasmine - .getEnv() - .execute([asyncSuite.id], asyncSuite) - .then(done, done); + it(`${testName} should load the compiled tests`, (done) => { + const { results } = TestRunner.runBundles({ + optionsArr: [options], + outputDirectory, + testConfig, + category, + testName, + setupRunner: ({ runner }) => { + runner.mergeModuleScope({ + it: _it }); + if (testConfig.moduleScope) { + testConfig.moduleScope(runner._moduleScope, options); + } + const runnerRequire = /** @type {EXPECTED_ANY} */ ( + runner.require + ); + runnerRequire.webpackTestSuiteRequire = true; }, - 60000 - ); - }); + getBundlePaths: (i, options) => + /** @type {NonNullable} */ ( + testConfig.findBundle + )(i, options) + }); + Promise.all(results).then(() => { + if (getNumberOfTests() === 0) { + return done(new Error("No tests exported by test case")); + } + done(); + }, done); + }, 10000); + + const { it: _it, getNumberOfTests } = createLazyTestEnv( + testConfig.timeout || 10000 + ); }); + } }); - }); + } }); }; +// eslint-disable-next-line jest/no-export module.exports.describeCases = describeCases; diff --git a/test/TestCasesAllCombined.longtest.js b/test/TestCasesAllCombined.longtest.js new file mode 100644 index 00000000000..df50e899c0d --- /dev/null +++ b/test/TestCasesAllCombined.longtest.js @@ -0,0 +1,26 @@ +"use strict"; + +const { describeCases } = require("./TestCases.template"); + +describe("TestCases", () => { + describeCases( + /** @type {EXPECTED_ANY} */ ({ + name: "all-combined", + mode: "production", + devtool: "source-map", + minimize: true, + optimization: { + moduleIds: "named", + chunkIds: "named" + }, + plugins: [ + /** @param {import("../").Compiler} c compiler */ + (c) => { + const webpack = require(".."); + + new webpack.HotModuleReplacementPlugin().apply(c); + } + ] + }) + ); +}); diff --git a/test/TestCasesAllCombined.test.js b/test/TestCasesAllCombined.test.js deleted file mode 100644 index e413ff2c257..00000000000 --- a/test/TestCasesAllCombined.test.js +++ /dev/null @@ -1,16 +0,0 @@ -const { describeCases } = require("./TestCases.template"); -const webpack = require("../lib/webpack"); - -describe("TestCases", () => { - describeCases({ - name: "all-combined", - mode: "production", - devtool: "#@source-map", - minimize: true, - plugins: [ - new webpack.HotModuleReplacementPlugin(), - new webpack.NamedModulesPlugin(), - new webpack.NamedChunksPlugin() - ] - }); -}); diff --git a/test/TestCasesCachePack.longtest.js b/test/TestCasesCachePack.longtest.js new file mode 100644 index 00000000000..efdecbc108d --- /dev/null +++ b/test/TestCasesCachePack.longtest.js @@ -0,0 +1,24 @@ +"use strict"; + +const path = require("path"); +const { describeCases } = require("./TestCases.template"); + +describe("TestCases", () => { + describeCases({ + name: "cache pack", + cache: { + type: "filesystem", + buildDependencies: { + defaultWebpack: [] + } + }, + snapshot: { + managedPaths: [path.resolve(__dirname, "../node_modules")] + }, + optimization: { + innerGraph: true, + usedExports: true, + concatenateModules: true + } + }); +}); diff --git a/test/TestCasesDevelopment.test.js b/test/TestCasesDevelopment.test.js index 5d1ec312356..81f3cdfe69d 100644 --- a/test/TestCasesDevelopment.test.js +++ b/test/TestCasesDevelopment.test.js @@ -1,9 +1,11 @@ +"use strict"; + const { describeCases } = require("./TestCases.template"); describe("TestCases", () => { describeCases({ name: "development", mode: "development", - devtool: "none" + devtool: false }); }); diff --git a/test/TestCasesDevtoolCheapEvalModuleSourceMap.test.js b/test/TestCasesDevtoolCheapEvalModuleSourceMap.test.js deleted file mode 100644 index 9a951ea0f1c..00000000000 --- a/test/TestCasesDevtoolCheapEvalModuleSourceMap.test.js +++ /dev/null @@ -1,8 +0,0 @@ -const { describeCases } = require("./TestCases.template"); - -describe("TestCases", () => { - describeCases({ - name: "devtool-cheap-eval-module-source-map", - devtool: "cheap-eval-module-source-map" - }); -}); diff --git a/test/TestCasesDevtoolCheapEvalSourceMap.test.js b/test/TestCasesDevtoolCheapEvalSourceMap.test.js deleted file mode 100644 index 3bccf31af95..00000000000 --- a/test/TestCasesDevtoolCheapEvalSourceMap.test.js +++ /dev/null @@ -1,8 +0,0 @@ -const { describeCases } = require("./TestCases.template"); - -describe("TestCases", () => { - describeCases({ - name: "devtool-cheap-eval-source-map", - devtool: "cheap-eval-source-map" - }); -}); diff --git a/test/TestCasesDevtoolCheapInlineSourceMap.test.js b/test/TestCasesDevtoolCheapInlineSourceMap.test.js deleted file mode 100644 index ce5a4151676..00000000000 --- a/test/TestCasesDevtoolCheapInlineSourceMap.test.js +++ /dev/null @@ -1,8 +0,0 @@ -const { describeCases } = require("./TestCases.template"); - -describe("TestCases", () => { - describeCases({ - name: "devtool-cheap-inline-source-map", - devtool: "cheap-inline-source-map" - }); -}); diff --git a/test/TestCasesDevtoolCheapSourceMap.test.js b/test/TestCasesDevtoolCheapSourceMap.test.js index dd38edcab0b..63c81175de4 100644 --- a/test/TestCasesDevtoolCheapSourceMap.test.js +++ b/test/TestCasesDevtoolCheapSourceMap.test.js @@ -1,3 +1,5 @@ +"use strict"; + const { describeCases } = require("./TestCases.template"); describe("TestCases", () => { diff --git a/test/TestCasesDevtoolEval.test.js b/test/TestCasesDevtoolEval.test.js index d03f129781d..2e9148d54e4 100644 --- a/test/TestCasesDevtoolEval.test.js +++ b/test/TestCasesDevtoolEval.test.js @@ -1,3 +1,5 @@ +"use strict"; + const { describeCases } = require("./TestCases.template"); describe("TestCases", () => { diff --git a/test/TestCasesDevtoolEvalCheapModuleSourceMap.test.js b/test/TestCasesDevtoolEvalCheapModuleSourceMap.test.js new file mode 100644 index 00000000000..d68e8e3ea6d --- /dev/null +++ b/test/TestCasesDevtoolEvalCheapModuleSourceMap.test.js @@ -0,0 +1,10 @@ +"use strict"; + +const { describeCases } = require("./TestCases.template"); + +describe("TestCases", () => { + describeCases({ + name: "devtool-eval-cheap-module-source-map", + devtool: "eval-cheap-module-source-map" + }); +}); diff --git a/test/TestCasesDevtoolEvalCheapSourceMap.test.js b/test/TestCasesDevtoolEvalCheapSourceMap.test.js new file mode 100644 index 00000000000..99a90e30553 --- /dev/null +++ b/test/TestCasesDevtoolEvalCheapSourceMap.test.js @@ -0,0 +1,10 @@ +"use strict"; + +const { describeCases } = require("./TestCases.template"); + +describe("TestCases", () => { + describeCases({ + name: "devtool-eval-cheap-source-map", + devtool: "eval-cheap-source-map" + }); +}); diff --git a/test/TestCasesDevtoolEvalDeterministicModuleIds.test.js b/test/TestCasesDevtoolEvalDeterministicModuleIds.test.js new file mode 100644 index 00000000000..b91356b3dce --- /dev/null +++ b/test/TestCasesDevtoolEvalDeterministicModuleIds.test.js @@ -0,0 +1,13 @@ +"use strict"; + +const { describeCases } = require("./TestCases.template"); + +describe("TestCases", () => { + describeCases({ + name: "devtool-eval-deterministic-module-ids", + devtool: "eval", + optimization: { + moduleIds: "deterministic" + } + }); +}); diff --git a/test/TestCasesDevtoolEvalNamedModules.test.js b/test/TestCasesDevtoolEvalNamedModules.test.js index a3c1cd6997e..554ea8f5351 100644 --- a/test/TestCasesDevtoolEvalNamedModules.test.js +++ b/test/TestCasesDevtoolEvalNamedModules.test.js @@ -1,10 +1,14 @@ +"use strict"; + const { describeCases } = require("./TestCases.template"); -const webpack = require("../lib/webpack"); describe("TestCases", () => { describeCases({ name: "devtool-eval-named-modules", devtool: "eval", - plugins: [new webpack.NamedModulesPlugin()] + optimization: { + moduleIds: "named", + chunkIds: "named" + } }); }); diff --git a/test/TestCasesDevtoolEvalSourceMap.test.js b/test/TestCasesDevtoolEvalSourceMap.test.js index bb68ab810cd..a0ae72eaae7 100644 --- a/test/TestCasesDevtoolEvalSourceMap.test.js +++ b/test/TestCasesDevtoolEvalSourceMap.test.js @@ -1,8 +1,10 @@ +"use strict"; + const { describeCases } = require("./TestCases.template"); describe("TestCases", () => { describeCases({ name: "devtool-eval-source-map", - devtool: "#eval-source-map" + devtool: "eval-source-map" }); }); diff --git a/test/TestCasesDevtoolInlineCheapSourceMap.test.js b/test/TestCasesDevtoolInlineCheapSourceMap.test.js new file mode 100644 index 00000000000..c54b226d7a7 --- /dev/null +++ b/test/TestCasesDevtoolInlineCheapSourceMap.test.js @@ -0,0 +1,10 @@ +"use strict"; + +const { describeCases } = require("./TestCases.template"); + +describe("TestCases", () => { + describeCases({ + name: "devtool-inline-cheap-source-map", + devtool: "inline-cheap-source-map" + }); +}); diff --git a/test/TestCasesDevtoolInlineSourceMap.longtest.js b/test/TestCasesDevtoolInlineSourceMap.longtest.js new file mode 100644 index 00000000000..ace6d539f6c --- /dev/null +++ b/test/TestCasesDevtoolInlineSourceMap.longtest.js @@ -0,0 +1,10 @@ +"use strict"; + +const { describeCases } = require("./TestCases.template"); + +describe("TestCases", () => { + describeCases({ + name: "devtool-inline-source-map", + devtool: "inline-source-map" + }); +}); diff --git a/test/TestCasesDevtoolInlineSourceMap.test.js b/test/TestCasesDevtoolInlineSourceMap.test.js deleted file mode 100644 index de3dc71272c..00000000000 --- a/test/TestCasesDevtoolInlineSourceMap.test.js +++ /dev/null @@ -1,8 +0,0 @@ -const { describeCases } = require("./TestCases.template"); - -describe("TestCases", () => { - describeCases({ - name: "devtool-inline-source-map", - devtool: "inline-source-map" - }); -}); diff --git a/test/TestCasesDevtoolSourceMap.longtest.js b/test/TestCasesDevtoolSourceMap.longtest.js new file mode 100644 index 00000000000..db49aa5ed29 --- /dev/null +++ b/test/TestCasesDevtoolSourceMap.longtest.js @@ -0,0 +1,10 @@ +"use strict"; + +const { describeCases } = require("./TestCases.template"); + +describe("TestCases", () => { + describeCases({ + name: "devtool-source-map", + devtool: "source-map" + }); +}); diff --git a/test/TestCasesDevtoolSourceMap.test.js b/test/TestCasesDevtoolSourceMap.test.js deleted file mode 100644 index b204305ea67..00000000000 --- a/test/TestCasesDevtoolSourceMap.test.js +++ /dev/null @@ -1,8 +0,0 @@ -const { describeCases } = require("./TestCases.template"); - -describe("TestCases", () => { - describeCases({ - name: "devtool-source-map", - devtool: "#@source-map" - }); -}); diff --git a/test/TestCasesHot.test.js b/test/TestCasesHot.test.js index 30c0f2f6c06..e3ce3f229d1 100644 --- a/test/TestCasesHot.test.js +++ b/test/TestCasesHot.test.js @@ -1,5 +1,7 @@ +"use strict"; + +const webpack = require(".."); const { describeCases } = require("./TestCases.template"); -const webpack = require("../lib/webpack"); describe("TestCases", () => { describeCases({ diff --git a/test/TestCasesHotMultiStep.test.js b/test/TestCasesHotMultiStep.test.js deleted file mode 100644 index 981c1b544c1..00000000000 --- a/test/TestCasesHotMultiStep.test.js +++ /dev/null @@ -1,13 +0,0 @@ -const { describeCases } = require("./TestCases.template"); -const webpack = require("../lib/webpack"); - -describe("TestCases", () => { - describeCases({ - name: "hot-multi-step", - plugins: [ - new webpack.HotModuleReplacementPlugin({ - multiStep: true - }) - ] - }); -}); diff --git a/test/TestCasesMinimizedHashedModules.test.js b/test/TestCasesMinimizedHashedModules.test.js deleted file mode 100644 index b28e926b92f..00000000000 --- a/test/TestCasesMinimizedHashedModules.test.js +++ /dev/null @@ -1,11 +0,0 @@ -const { describeCases } = require("./TestCases.template"); -const webpack = require("../lib/webpack"); - -describe("TestCases", () => { - describeCases({ - name: "minimized-hashed-modules", - mode: "production", - minimize: true, - plugins: [new webpack.HashedModuleIdsPlugin()] - }); -}); diff --git a/test/TestCasesMinimizedSourceMap.longtest.js b/test/TestCasesMinimizedSourceMap.longtest.js new file mode 100644 index 00000000000..2e3c9d6bb33 --- /dev/null +++ b/test/TestCasesMinimizedSourceMap.longtest.js @@ -0,0 +1,12 @@ +"use strict"; + +const { describeCases } = require("./TestCases.template"); + +describe("TestCases", () => { + describeCases({ + name: "minimized-source-map", + mode: "production", + devtool: "eval-cheap-module-source-map", + minimize: true + }); +}); diff --git a/test/TestCasesMinimizedSourceMap.test.js b/test/TestCasesMinimizedSourceMap.test.js deleted file mode 100644 index 9ec3b57178b..00000000000 --- a/test/TestCasesMinimizedSourceMap.test.js +++ /dev/null @@ -1,10 +0,0 @@ -const { describeCases } = require("./TestCases.template"); - -describe("TestCases", () => { - describeCases({ - name: "minimized-source-map", - mode: "production", - devtool: "eval-cheap-module-source-map", - minimize: true - }); -}); diff --git a/test/TestCasesModule.test.js b/test/TestCasesModule.test.js new file mode 100644 index 00000000000..2d328abaa4b --- /dev/null +++ b/test/TestCasesModule.test.js @@ -0,0 +1,17 @@ +"use strict"; + +const vm = require("vm"); +const { describeCases } = require("./TestCases.template"); + +describe("TestCases", () => { + if (!vm.SourceTextModule) { + throw new Error( + "Running this test requires '--experimental-vm-modules'.\nRun with 'node --experimental-vm-modules node_modules/jest-cli/bin/jest'." + ); + } + describeCases({ + name: "module", + target: "node14", + module: true + }); +}); diff --git a/test/TestCasesNormal.basictest.js b/test/TestCasesNormal.basictest.js new file mode 100644 index 00000000000..07ac41b4dc7 --- /dev/null +++ b/test/TestCasesNormal.basictest.js @@ -0,0 +1,9 @@ +"use strict"; + +const { describeCases } = require("./TestCases.template"); + +describe("TestCases", () => { + describeCases({ + name: "normal" + }); +}); diff --git a/test/TestCasesNormal.test.js b/test/TestCasesNormal.test.js deleted file mode 100644 index 9d975ad2882..00000000000 --- a/test/TestCasesNormal.test.js +++ /dev/null @@ -1,7 +0,0 @@ -const { describeCases } = require("./TestCases.template"); - -describe("TestCases", () => { - describeCases({ - name: "normal" - }); -}); diff --git a/test/TestCasesProdGlobalUsed.test.js b/test/TestCasesProdGlobalUsed.test.js new file mode 100644 index 00000000000..c76d3dd82d6 --- /dev/null +++ b/test/TestCasesProdGlobalUsed.test.js @@ -0,0 +1,14 @@ +"use strict"; + +const { describeCases } = require("./TestCases.template"); + +describe("TestCasesProdGlobalUsed", () => { + describeCases({ + name: "production with usedExports global", + mode: "production", + optimization: { + usedExports: "global", + minimize: false + } + }); +}); diff --git a/test/TestCasesProduction.longtest.js b/test/TestCasesProduction.longtest.js new file mode 100644 index 00000000000..7fae43c19c3 --- /dev/null +++ b/test/TestCasesProduction.longtest.js @@ -0,0 +1,11 @@ +"use strict"; + +const { describeCases } = require("./TestCases.template"); + +describe("TestCases", () => { + describeCases({ + name: "production", + mode: "production", + minimize: true + }); +}); diff --git a/test/TestCasesProduction.test.js b/test/TestCasesProduction.test.js deleted file mode 100644 index 8708f34d2a4..00000000000 --- a/test/TestCasesProduction.test.js +++ /dev/null @@ -1,8 +0,0 @@ -const { describeCases } = require("./TestCases.template"); - -describe("TestCases", () => { - describeCases({ - name: "production", - mode: "production" - }); -}); diff --git a/test/URLAbsoluteSpecifier.unittest.js b/test/URLAbsoluteSpecifier.unittest.js new file mode 100644 index 00000000000..c6f85e90039 --- /dev/null +++ b/test/URLAbsoluteSpecifier.unittest.js @@ -0,0 +1,87 @@ +"use strict"; + +const { getProtocol, getScheme } = require("../lib/util/URLAbsoluteSpecifier"); + +/** + * @type {{ specifier: string, expected: string | undefined }[]} + */ +const samples = [ + { + specifier: "@babel/core", + expected: undefined + }, + { + specifier: "webpack", + expected: undefined + }, + { + specifier: "1webpack:///c:/windows/dir", + expected: undefined + }, + { + specifier: "webpack:///c:/windows/dir", + expected: "webpack" + }, + { + specifier: "WEBPACK2020:///c:/windows/dir", + expected: "webpack2020" + }, + { + specifier: "my-data:image/jpg;base64", + expected: "my-data" + }, + { + specifier: "My+Data:image/jpg;base64", + expected: "my+data" + }, + { + specifier: "mY+dATA:image/jpg;base64", + expected: "my+data" + }, + { + specifier: "my-data/next:image/", + expected: undefined + }, + { + specifier: "my-data\\next:image/", + expected: undefined + }, + { + specifier: "D:\\path\\file.js", + expected: undefined + }, + { + specifier: "d:/path/file.js", + expected: undefined + }, + { + specifier: "z:#foo", + expected: undefined + }, + { + specifier: "Z:?query", + expected: undefined + }, + { + specifier: "C:", + expected: undefined + } +]; + +describe("getScheme", () => { + for (const [_i, { specifier, expected }] of samples.entries()) { + it(`should handle ${specifier}`, () => { + expect(getScheme(specifier)).toBe(expected); + }); + } +}); + +describe("getProtocol", () => { + for (const [_i, { specifier, expected }] of samples.entries()) { + it(`should handle ${specifier}`, () => { + expect(getProtocol(specifier)).toBe( + expected ? `${expected}:` : undefined + ); + }); + } +}); diff --git a/test/Validation.test.js b/test/Validation.test.js index 6f29d6a9ee5..22eba4165ad 100644 --- a/test/Validation.test.js +++ b/test/Validation.test.js @@ -1,402 +1,912 @@ -/* globals describe, it */ "use strict"; -const webpack = require("../lib/webpack"); +require("./helpers/warmup-webpack"); describe("Validation", () => { - const testCases = [ + const createTestCase = ( + /** @type {string} */ name, + /** @type {EXPECTED_ANY} */ config, + /** @type {(msg: string) => void} */ fn, + /** @type {unknown} */ only = undefined + ) => { + it(`should fail validation for ${name}`, () => { + try { + const webpack = require(".."); + + webpack(config); + } catch (err) { + if (/** @type {EXPECTED_ANY} */ (err).name !== "ValidationError") { + throw err; + } + fn(/** @type {Error} */ (err).message); + + return; + } + + throw new Error("Validation didn't fail"); + }); + }; + + const createTestCaseOnlyValidate = ( + /** @type {string} */ name, + /** @type {EXPECTED_ANY} */ config, + /** @type {(msg: string) => void} */ fn + ) => { + it(`should fail validation for ${name}`, () => { + try { + const webpack = require(".."); + + webpack.validate(config); + } catch (err) { + if (/** @type {EXPECTED_ANY} */ (err).name !== "ValidationError") { + throw err; + } + fn(/** @type {Error} */ (err).message); + + return; + } + + throw new Error("Validation didn't fail"); + }); + }; + + const createTestCaseWithoutError = ( + /** @type {string} */ name, + /** @type {EXPECTED_ANY} */ config + ) => { + it(`should success validation for ${name}`, () => { + let errored; + + try { + const webpack = require(".."); + + webpack(config); + } catch (err) { + if (/** @type {EXPECTED_ANY} */ (err).name === "ValidationError") { + throw new Error("Validation didn't success", { + cause: /** @type {Error} */ (err) + }); + } + + errored = err; + + return; + } + + if (errored) { + throw new Error("Validation didn't success"); + } + }); + }; + + createTestCase("undefined configuration", undefined, (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration should be an object: + object { amd?, bail?, cache?, context?, dependencies?, devServer?, devtool?, dotenv?, entry?, experiments?, extends?, externals?, externalsPresets?, externalsType?, ignoreWarnings?, infrastructureLogging?, loader?, mode?, module?, name?, node?, optimization?, output?, parallelism?, performance?, plugins?, profile?, recordsInputPath?, recordsOutputPath?, recordsPath?, resolve?, resolveLoader?, snapshot?, stats?, target?, validate?, watch?, watchOptions? } + -> Options object as provided by the user." + `) + ); + + createTestCase("null configuration", null, (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration should be an object: + object { amd?, bail?, cache?, context?, dependencies?, devServer?, devtool?, dotenv?, entry?, experiments?, extends?, externals?, externalsPresets?, externalsType?, ignoreWarnings?, infrastructureLogging?, loader?, mode?, module?, name?, node?, optimization?, output?, parallelism?, performance?, plugins?, profile?, recordsInputPath?, recordsOutputPath?, recordsPath?, resolve?, resolveLoader?, snapshot?, stats?, target?, validate?, watch?, watchOptions? } + -> Options object as provided by the user." + `) + ); + + createTestCase( + "empty entry string", { - name: "undefined configuration", - config: undefined, - message: [" - configuration should be an object."] + entry: "" }, + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration.entry should be a non-empty string. + -> The string is resolved to a module which is loaded upon startup." + `) + ); + + createTestCase( + "empty entry bundle array", { - name: "null configuration", - config: null, - message: [" - configuration should be an object."] + entry: { + bundle: [] + } }, + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration.entry.bundle should be a non-empty array. + -> All modules are loaded upon startup. The last one is exported." + `) + ); + + createTestCase( + "invalid instanceof", { - name: "empty entry string", - config: { - entry: "" - }, - message: [ - " - configuration.entry should be one of these:", - " object { : non-empty string | [non-empty string] } | non-empty string | [non-empty string] | function", - " -> The entry point(s) of the compilation.", - " Details:", - " * configuration.entry should be an object.", - " -> Multiple entry bundles are created. The key is the chunk name. The value can be a string or an array.", - " * configuration.entry should not be empty.", - " -> An entry point without name. The string is resolved to a module which is loaded upon startup.", - " * configuration.entry should be an array:", - " [non-empty string]", - " * configuration.entry should be an instance of function", - " -> A Function returning an entry object, an entry string, an entry array or a promise to these things." - ] + entry: "a", + module: { + wrappedContextRegExp: 1337 + } }, + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration.module.wrappedContextRegExp should be an instance of RegExp. + -> Set the inner regular expression for partial dynamic dependencies. Deprecated: This option has moved to 'module.parser.javascript.wrappedContextRegExp'." + `) + ); + + createTestCase( + "invalid minimum", { - name: "empty entry bundle array", - config: { - entry: { - bundle: [] - } - }, - message: [ - " - configuration.entry should be one of these:", - " object { : non-empty string | [non-empty string] } | non-empty string | [non-empty string] | function", - " -> The entry point(s) of the compilation.", - " Details:", - " * configuration.entry['bundle'] should be a string.", - " -> The string is resolved to a module which is loaded upon startup.", - " * configuration.entry['bundle'] should not be empty.", - " * configuration.entry should be a string.", - " -> An entry point without name. The string is resolved to a module which is loaded upon startup.", - " * configuration.entry should be an array:", - " [non-empty string]", - " * configuration.entry should be an instance of function", - " -> A Function returning an entry object, an entry string, an entry array or a promise to these things." - ] + entry: "a", + parallelism: 0 + }, + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration.parallelism should be >= 1. + -> The number of parallel processed modules in the compilation." + `) + ); + + createTestCase( + "repeated value", + { + entry: ["abc", "def", "abc"] }, + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration.entry should not contain the item 'abc' twice. + -> All modules are loaded upon startup. The last one is exported." + `) + ); + + createTestCase( + "multiple errors", { - name: "invalid instanceof", - config: { + entry: [/a/], + output: { + filename: /a/ + } + }, + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration.entry[0] should be a non-empty string. + -> A module that is loaded upon startup. Only the last one is exported. + - configuration.output.filename should be one of these: + non-empty string | function + -> Specifies the filename template of output files on disk. You must **not** specify an absolute path here, but the path may contain folders separated by '/'! The specified path is joined with the value of the 'output.path' option to determine the location on disk. + Details: + * configuration.output.filename should be a non-empty string. + * configuration.output.filename should be an instance of function." + `) + ); + + createTestCase( + "multiple configurations", + [ + { + entry: [/a/] + }, + { entry: "a", - module: { - wrappedContextRegExp: 1337 + output: { + filename: /a/ } - }, - message: [ - " - configuration.module.wrappedContextRegExp should be an instance of RegExp", - " -> Set the inner regular expression for partial dynamic dependencies" - ] + } + ], + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration[0].entry[0] should be a non-empty string. + -> A module that is loaded upon startup. Only the last one is exported. + - configuration[1].output.filename should be one of these: + non-empty string | function + -> Specifies the filename template of output files on disk. You must **not** specify an absolute path here, but the path may contain folders separated by '/'! The specified path is joined with the value of the 'output.path' option to determine the location on disk. + Details: + * configuration[1].output.filename should be a non-empty string. + * configuration[1].output.filename should be an instance of function." + `) + ); + + createTestCase( + "deep error", + { + entry: "a", + module: { + rules: [ + { + oneOf: [ + { + test: "/a", + passer: { + amd: false + } + } + ] + } + ] + } }, + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration.module.rules[0].oneOf[0] has an unknown property 'passer'. These properties are valid: + object { assert?, compiler?, dependency?, descriptionData?, enforce?, exclude?, extractSourceMap?, generator?, include?, issuer?, issuerLayer?, layer?, loader?, mimetype?, oneOf?, options?, parser?, phase?, realResource?, resolve?, resource?, resourceFragment?, resourceQuery?, rules?, scheme?, sideEffects?, test?, type?, use?, with? } + -> A rule description with conditions and effects for modules." + `) + ); + + createTestCase( + "additional key on root", { - name: "invalid minimum", - config: { - entry: "a", - parallelism: 0 - }, - message: [ - " - configuration.parallelism should be >= 1.", - " -> The number of parallel processed modules in the compilation." - ] + entry: "a", + postcss: () => {} }, + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration has an unknown property 'postcss'. These properties are valid: + object { amd?, bail?, cache?, context?, dependencies?, devServer?, devtool?, dotenv?, entry?, experiments?, extends?, externals?, externalsPresets?, externalsType?, ignoreWarnings?, infrastructureLogging?, loader?, mode?, module?, name?, node?, optimization?, output?, parallelism?, performance?, plugins?, profile?, recordsInputPath?, recordsOutputPath?, recordsPath?, resolve?, resolveLoader?, snapshot?, stats?, target?, validate?, watch?, watchOptions? } + -> Options object as provided by the user. + For typos: please correct them. + For loader options: webpack >= v2.0.0 no longer allows custom properties in configuration. + Loaders should be updated to allow passing options via loader options in module.rules. + Until loaders are updated one can use the LoaderOptionsPlugin to pass these options to the loader: + plugins: [ + new webpack.LoaderOptionsPlugin({ + // test: /\\\\.xxx$/, // may apply this only for some modules + options: { + postcss: … + } + }) + ]" + `) + ); + + createTestCase( + "enum", { - name: "repeated value", - config: { - entry: ["abc", "def", "abc"] - }, - message: [ - " - configuration.entry should be one of these:", - " object { : non-empty string | [non-empty string] } | non-empty string | [non-empty string] | function", - " -> The entry point(s) of the compilation.", - " Details:", - " * configuration.entry should be an object.", - " -> Multiple entry bundles are created. The key is the chunk name. The value can be a string or an array.", - " * configuration.entry should be a string.", - " -> An entry point without name. The string is resolved to a module which is loaded upon startup.", - " * configuration.entry should not contain the item 'abc' twice.", - " * configuration.entry should be an instance of function", - " -> A Function returning an entry object, an entry string, an entry array or a promise to these things." - ] + entry: "a", + devtool: true }, + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration.devtool should be one of these: + [object { type, use }, ...] | false | \\"eval\\" | string (should match pattern \\"^(inline-|hidden-|eval-)?(nosources-)?(cheap-(module-)?)?source-map(-debugids)?$\\") + -> A developer tool to enhance debugging (false | eval | [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map). + Details: + * configuration.devtool should be an array: + [object { type, use }, ...] + * configuration.devtool should be one of these: + false | \\"eval\\" | string (should match pattern \\"^(inline-|hidden-|eval-)?(nosources-)?(cheap-(module-)?)?source-map(-debugids)?$\\") + -> A developer tool to enhance debugging (false | eval | [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map). + Details: + * configuration.devtool should be one of these: + false | \\"eval\\" + * configuration.devtool should be a string (should match pattern \\"^(inline-|hidden-|eval-)?(nosources-)?(cheap-(module-)?)?source-map(-debugids)?$\\")." + `) + ); + + createTestCase( + "! in path", { - name: "multiple errors", - config: { - entry: [/a/], - output: { - filename: /a/ - } - }, - message: [ - " - configuration.entry should be one of these:", - " object { : non-empty string | [non-empty string] } | non-empty string | [non-empty string] | function", - " -> The entry point(s) of the compilation.", - " Details:", - " * configuration.entry should be an object.", - " -> Multiple entry bundles are created. The key is the chunk name. The value can be a string or an array.", - " * configuration.entry should be a string.", - " -> An entry point without name. The string is resolved to a module which is loaded upon startup.", - " * configuration.entry[0] should be a string.", - " -> A non-empty string", - " * configuration.entry should be an instance of function", - " -> A Function returning an entry object, an entry string, an entry array or a promise to these things.", - " - configuration.output.filename should be one of these:", - " string | function", - " -> Specifies the name of each output file on disk. You must **not** specify an absolute path here! The `output.path` option determines the location on disk the files are written to, filename is used solely for naming the individual files.", - " Details:", - " * configuration.output.filename should be a string.", - " * configuration.output.filename should be an instance of function" - ] + entry: "foo.js", + output: { + path: "/somepath/!test", + filename: "bar" + } }, + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration.output.path: The provided value \\"/somepath/!test\\" contains exclamation mark (!) which is not allowed because it's reserved for loader syntax. + -> The output directory as **absolute path** (required)." + `) + ); + + createTestCase( + "relative path", { - name: "multiple configurations", - config: [ - { - entry: [/a/] - }, - { - entry: "a", - output: { - filename: /a/ - } - } - ], - message: [ - " - configuration[0].entry should be one of these:", - " object { : non-empty string | [non-empty string] } | non-empty string | [non-empty string] | function", - " -> The entry point(s) of the compilation.", - " Details:", - " * configuration[0].entry should be an object.", - " -> Multiple entry bundles are created. The key is the chunk name. The value can be a string or an array.", - " * configuration[0].entry should be a string.", - " -> An entry point without name. The string is resolved to a module which is loaded upon startup.", - " * configuration[0].entry[0] should be a string.", - " -> A non-empty string", - " * configuration[0].entry should be an instance of function", - " -> A Function returning an entry object, an entry string, an entry array or a promise to these things.", - " - configuration[1].output.filename should be one of these:", - " string | function", - " -> Specifies the name of each output file on disk. You must **not** specify an absolute path here! The `output.path` option determines the location on disk the files are written to, filename is used solely for naming the individual files.", - " Details:", - " * configuration[1].output.filename should be a string.", - " * configuration[1].output.filename should be an instance of function" - ] + entry: "foo.js", + output: { + filename: "/bar" + } }, + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration.output.filename: A relative path is expected. However, the provided value \\"/bar\\" is an absolute path! + Please use output.path to specify absolute path and output.filename for the file name." + `) + ); + + createTestCase( + "absolute path", { - name: "deep error", - config: { - entry: "a", - module: { - rules: [ - { - oneOf: [ - { - test: "/a", - passer: { - amd: false - } - } - ] - } - ] - } + entry: "foo.js", + output: { + filename: "bar" }, - message: [ - " - configuration.module.rules[0].oneOf[0] has an unknown property 'passer'. These properties are valid:", - " object { enforce?, exclude?, include?, issuer?, loader?, loaders?, oneOf?, options?, parser?, resolve?, sideEffects?, query?, type?, resource?, resourceQuery?, compiler?, rules?, test?, use? }", - " -> A rule" - ] + context: "baz" }, + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration.context: The provided value \\"baz\\" is not an absolute path! + -> The base directory (absolute path!) for resolving the \`entry\` option. If \`output.pathinfo\` is set, the included pathinfo is shortened to this directory." + `) + ); + + createTestCase( + "missing stats option", { - name: "additional key on root", - config: { - entry: "a", - postcss: () => {} - }, - message: [ - " - configuration has an unknown property 'postcss'. These properties are valid:", - " object { mode?, amd?, bail?, cache?, context?, dependencies?, devServer?, devtool?, entry?, externals?, " + - "loader?, module?, name?, node?, output?, optimization?, parallelism?, performance?, plugins?, profile?, recordsInputPath?, " + - "recordsOutputPath?, recordsPath?, resolve?, resolveLoader?, serve?, stats?, target?, watch?, watchOptions? }", - " For typos: please correct them.", - " For loader options: webpack >= v2.0.0 no longer allows custom properties in configuration.", - " Loaders should be updated to allow passing options via loader options in module.rules.", - " Until loaders are updated one can use the LoaderOptionsPlugin to pass these options to the loader:", - " plugins: [", - " new webpack.LoaderOptionsPlugin({", - " // test: /\\.xxx$/, // may apply this only for some modules", - " options: {", - " postcss: …", - " }", - " })", - " ]" - ] + entry: "foo.js", + stats: { + foobar: true + } }, + (msg) => { + expect( + msg + .replace(/object \{ .* \}/g, "object {...}") + .replace(/"none" \| .+/g, '"none" | ...') + ).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration.stats has an unknown property 'foobar'. These properties are valid: + object {...} + -> Stats options object." + `); + } + ); + + createTestCase( + "Invalid plugin provided: bool", { - name: "enum", - config: { - entry: "a", - devtool: true - }, - message: [ - " - configuration.devtool should be one of these:", - " string | false", - " -> A developer tool to enhance debugging.", - " Details:", - " * configuration.devtool should be a string.", - " * configuration.devtool should be false" - ] + entry: "foo.js", + plugins: [true] }, + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration.plugins[0] should be one of these: + false | 0 | \\"\\" | null | undefined | object { apply, … } | function + -> Plugin of type object or instanceof Function. + Details: + * configuration.plugins[0] should be one of these: + false | 0 | \\"\\" | null | undefined + -> These values will be ignored by webpack and created to be used with '&&' or '||' to improve readability of configurations. + * configuration.plugins[0] should be an object: + object { apply, … } + -> Plugin instance. + * configuration.plugins[0] should be an instance of function. + -> Function acting as plugin." + `) + ); + + createTestCase( + "Invalid plugin provided: array", { - name: "! in path", - config: { - entry: "foo.js", - output: { - path: "/somepath/!test", - filename: "bar" - } - }, - message: [ - ' - configuration.output.path: The provided value "/somepath/!test" contans exclamation mark (!) which is not allowed because it\'s reserved for loader syntax.', - " -> The output directory as **absolute path** (required)." - ] + entry: "foo.js", + plugins: [[]] }, + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration.plugins[0] should be one of these: + false | 0 | \\"\\" | null | undefined | object { apply, … } | function + -> Plugin of type object or instanceof Function. + Details: + * configuration.plugins[0] should be one of these: + false | 0 | \\"\\" | null | undefined + -> These values will be ignored by webpack and created to be used with '&&' or '||' to improve readability of configurations. + * configuration.plugins[0] should be an object: + object { apply, … } + -> Plugin instance. + * configuration.plugins[0] should be an instance of function. + -> Function acting as plugin." + `) + ); + + createTestCase( + "Invalid plugin provided: string", { - name: "relative path", - config: { - entry: "foo.js", - output: { - filename: "/bar" - } - }, - message: [ - ' - configuration.output.filename: A relative path is expected. However, the provided value "/bar" is an absolute path!', - " -> Specifies the name of each output file on disk. You must **not** specify an absolute path here! The `output.path` option determines the location on disk the files are written to, filename is used solely for naming the individual files.", - " Please use output.path to specify absolute path and output.filename for the file name." - ] + entry: "foo.js", + plugins: ["abc123"] }, + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration.plugins[0] should be one of these: + false | 0 | \\"\\" | null | undefined | object { apply, … } | function + -> Plugin of type object or instanceof Function. + Details: + * configuration.plugins[0] should be one of these: + false | 0 | \\"\\" | null | undefined + -> These values will be ignored by webpack and created to be used with '&&' or '||' to improve readability of configurations. + * configuration.plugins[0] should be an object: + object { apply, … } + -> Plugin instance. + * configuration.plugins[0] should be an instance of function. + -> Function acting as plugin." + `) + ); + + createTestCase( + "Invalid plugin provided: int", { - name: "absolute path", - config: { - entry: "foo.js", - output: { - filename: "bar" - }, - context: "baz" - }, - message: [ - ' - configuration.context: The provided value "baz" is not an absolute path!', - " -> The base directory (absolute path!) for resolving the `entry` option. If `output.pathinfo` is set, the included pathinfo is shortened to this directory." - ] + entry: "foo.js", + plugins: [12] + }, + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration.plugins[0] should be one of these: + false | 0 | \\"\\" | null | undefined | object { apply, … } | function + -> Plugin of type object or instanceof Function. + Details: + * configuration.plugins[0] should be one of these: + false | 0 | \\"\\" | null | undefined + -> These values will be ignored by webpack and created to be used with '&&' or '||' to improve readability of configurations. + * configuration.plugins[0] should be an object: + object { apply, … } + -> Plugin instance. + * configuration.plugins[0] should be an instance of function. + -> Function acting as plugin." + `) + ); + + createTestCase( + "Invalid plugin provided: object without apply function", + { + entry: "foo.js", + plugins: [{}] + }, + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration.plugins[0] misses the property 'apply'. Should be: + function + -> The run point of the plugin, required method." + `) + ); + + // cspell:Ignore protuction + createTestCase( + "invalid mode", + { + mode: "protuction" }, + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration.mode should be one of these: + \\"development\\" | \\"production\\" | \\"none\\" + -> Enable production optimizations or development hints." + `) + ); + + createTestCase( + "debug", + { + debug: true + }, + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration has an unknown property 'debug'. These properties are valid: + object { amd?, bail?, cache?, context?, dependencies?, devServer?, devtool?, dotenv?, entry?, experiments?, extends?, externals?, externalsPresets?, externalsType?, ignoreWarnings?, infrastructureLogging?, loader?, mode?, module?, name?, node?, optimization?, output?, parallelism?, performance?, plugins?, profile?, recordsInputPath?, recordsOutputPath?, recordsPath?, resolve?, resolveLoader?, snapshot?, stats?, target?, validate?, watch?, watchOptions? } + -> Options object as provided by the user. + The 'debug' property was removed in webpack 2.0.0. + Loaders should be updated to allow passing this option via loader options in module.rules. + Until loaders are updated one can use the LoaderOptionsPlugin to switch loaders into debug mode: + plugins: [ + new webpack.LoaderOptionsPlugin({ + debug: true + }) + ]" + `) + ); + + createTestCase( + "missing cache group name", { - name: "missing stats option", - config: { - entry: "foo.js", - stats: { - foobar: true + optimization: { + splitChunks: { + cacheGroups: { + test: /abc/ + } } - }, - test(err) { - expect(err.message).toMatch(/^Invalid configuration object./); - expect(err.message.split("\n").slice(1)[0]).toBe( - " - configuration.stats should be one of these:" - ); } }, + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration.optimization.splitChunks.cacheGroups should not be object { test, … }. + -> Using the cacheGroup shorthand syntax with a cache group named 'test' is a potential config error + Did you intent to define a cache group with a test instead? + cacheGroups: { + : { + test: ... + } + }. + object { : false | RegExp | string | function | object { automaticNameDelimiter?, chunks?, enforce?, enforceSizeThreshold?, filename?, idHint?, layer?, maxAsyncRequests?, maxAsyncSize?, maxInitialRequests?, maxInitialSize?, maxSize?, minChunks?, minRemainingSize?, minSize?, minSizeReduction?, name?, priority?, reuseExistingChunk?, test?, type?, usedExports? } } + -> Assign modules to a cache group (modules from different cache groups are tried to keep in separate chunks, default categories: 'default', 'defaultVendors')." + `) + ); + + createTestCase( + "holey array", + // eslint-disable-next-line no-sparse-arrays + [{ mode: "production" }, , { mode: "development" }], + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration[1] should be an object: + object { amd?, bail?, cache?, context?, dependencies?, devServer?, devtool?, dotenv?, entry?, experiments?, extends?, externals?, externalsPresets?, externalsType?, ignoreWarnings?, infrastructureLogging?, loader?, mode?, module?, name?, node?, optimization?, output?, parallelism?, performance?, plugins?, profile?, recordsInputPath?, recordsOutputPath?, recordsPath?, resolve?, resolveLoader?, snapshot?, stats?, target?, validate?, watch?, watchOptions? } + -> Options object as provided by the user." + `) + ); + + createTestCase( + "ecmaVersion", { - name: "Invalid plugin provided: bool", - config: { - entry: "foo.js", - plugins: [false] - }, - message: [ - " - configuration.plugins[0] should be one of these:", - " object { apply, … } | function", - " -> Plugin of type object or instanceof Function", - " Details:", - " * configuration.plugins[0] should be an object.", - " -> Plugin instance", - " * configuration.plugins[0] should be an instance of function", - " -> Function acting as plugin" - ] + output: { ecmaVersion: 2015 } }, + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration.output has an unknown property 'ecmaVersion'. These properties are valid: + object { amdContainer?, assetModuleFilename?, asyncChunks?, auxiliaryComment?, charset?, chunkFilename?, chunkFormat?, chunkLoadTimeout?, chunkLoading?, chunkLoadingGlobal?, clean?, compareBeforeEmit?, crossOriginLoading?, cssChunkFilename?, cssFilename?, devtoolFallbackModuleFilenameTemplate?, devtoolModuleFilenameTemplate?, devtoolNamespace?, enabledChunkLoadingTypes?, enabledLibraryTypes?, enabledWasmLoadingTypes?, environment?, filename?, globalObject?, hashDigest?, hashDigestLength?, hashFunction?, hashSalt?, hotUpdateChunkFilename?, hotUpdateGlobal?, hotUpdateMainFilename?, html?, htmlChunkFilename?, htmlFilename?, ignoreBrowserWarnings?, iife?, importFunctionName?, importMetaName?, library?, libraryExport?, libraryTarget?, module?, path?, pathinfo?, publicPath?, scriptType?, sourceMapFilename?, sourcePrefix?, strictModuleErrorHandling?, strictModuleExceptionHandling?, strictModuleResolution?, trustedTypes?, umdNamedDefine?, uniqueName?, wasmLoading?, webassemblyModuleFilename?, workerChunkFilename?, workerChunkLoading?, workerPublicPath?, workerWasmLoading? } + -> Options affecting the output of the compilation. \`output\` options tell webpack how to write the compiled files to disk. + Did you mean output.environment (output.ecmaVersion was a temporary configuration option during webpack 5 beta)?" + `) + ); + + createTestCase( + "devtool sourcemap", { - name: "Invalid plugin provided: array", - config: { - entry: "foo.js", - plugins: [[]] - }, - message: [ - " - configuration.plugins[0] should be one of these:", - " object { apply, … } | function", - " -> Plugin of type object or instanceof Function", - " Details:", - " * configuration.plugins[0] should be an object.", - " -> Plugin instance", - " * configuration.plugins[0] should be an instance of function", - " -> Function acting as plugin" - ] + devtool: "sourcemap" }, + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration.devtool should match pattern \\"^(inline-|hidden-|eval-)?(nosources-)?(cheap-(module-)?)?source-map(-debugids)?$\\". + BREAKING CHANGE since webpack 5: The devtool option is more strict. + Please strictly follow the order of the keywords in the pattern." + `) + ); + + createTestCase( + "devtool source-maps", { - name: "Invalid plugin provided: string", - config: { - entry: "foo.js", - plugins: ["abc123"] - }, - message: [ - " - configuration.plugins[0] should be one of these:", - " object { apply, … } | function", - " -> Plugin of type object or instanceof Function", - " Details:", - " * configuration.plugins[0] should be an object.", - " -> Plugin instance", - " * configuration.plugins[0] should be an instance of function", - " -> Function acting as plugin" - ] + devtool: "source-maps" }, + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration.devtool should match pattern \\"^(inline-|hidden-|eval-)?(nosources-)?(cheap-(module-)?)?source-map(-debugids)?$\\". + BREAKING CHANGE since webpack 5: The devtool option is more strict. + Please strictly follow the order of the keywords in the pattern." + `) + ); + + createTestCase( + "invalid watch options", { - name: "Invalid plugin provided: int", - config: { - entry: "foo.js", - plugins: [12] - }, - message: [ - " - configuration.plugins[0] should be one of these:", - " object { apply, … } | function", - " -> Plugin of type object or instanceof Function", - " Details:", - " * configuration.plugins[0] should be an object.", - " -> Plugin instance", - " * configuration.plugins[0] should be an instance of function", - " -> Function acting as plugin" - ] + watchOptions: true + }, + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration.watchOptions should be an object: + object { aggregateTimeout?, followSymlinks?, ignored?, poll?, stdin? } + -> Options for the watcher." + `) + ); + + createTestCase( + "devtool", + { + devtool: "cheap-eval-nosource-source-map" }, + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration.devtool should match pattern \\"^(inline-|hidden-|eval-)?(nosources-)?(cheap-(module-)?)?source-map(-debugids)?$\\". + BREAKING CHANGE since webpack 5: The devtool option is more strict. + Please strictly follow the order of the keywords in the pattern." + `) + ); + + createTestCaseOnlyValidate( + "devtool", { - name: "Invalid plugin provided: object without apply function", - config: { - entry: "foo.js", - plugins: [{}] + devtool: "cheap-eval-nosource-source-map" + }, + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration.devtool should match pattern \\"^(inline-|hidden-|eval-)?(nosources-)?(cheap-(module-)?)?source-map(-debugids)?$\\". + BREAKING CHANGE since webpack 5: The devtool option is more strict. + Please strictly follow the order of the keywords in the pattern." + `) + ); + + createTestCaseOnlyValidate( + "devtool", + [ + { + devtool: "cheap-eval-nosource-source-map" }, - message: [ - " - configuration.plugins[0] should be one of these:", - " object { apply, … } | function", - " -> Plugin of type object or instanceof Function", - " Details:", - " * configuration.plugins[0] misses the property 'apply'.", - " function", - " -> The run point of the plugin, required method.", - " * configuration.plugins[0] misses the property 'apply'.", - " function", - " -> The run point of the plugin, required method.", - " * configuration.plugins[0] should be an instance of function", - " -> Function acting as plugin" - ] + { + devtool: "unknown" + } + ], + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration[0].devtool should match pattern \\"^(inline-|hidden-|eval-)?(nosources-)?(cheap-(module-)?)?source-map(-debugids)?$\\". + - configuration[1].devtool should match pattern \\"^(inline-|hidden-|eval-)?(nosources-)?(cheap-(module-)?)?source-map(-debugids)?$\\"." + `) + ); + + createTestCaseOnlyValidate( + "resolve invalid", + { resolve: { tsconfig: 10 } }, + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration.resolve.tsconfig should be one of these: + object { alias?, aliasFields?, byDependency?, cache?, cachePredicate?, cacheWithContext?, conditionNames?, descriptionFiles?, enforceExtension?, exportsFields?, extensionAlias?, extensions?, fallback?, fileSystem?, fullySpecified?, importsFields?, mainFields?, mainFiles?, modules?, plugins?, preferAbsolute?, preferRelative?, resolver?, restrictions?, roots?, symlinks?, tsconfig?, unsafeCache?, useSyncFileSystemCalls? } + -> TypeScript config for paths mapping. Can be \`false\` (disabled), \`true\` (use default \`tsconfig.json\`), a string path to \`tsconfig.json\`, or an object with \`configFile\` and \`references\` options. + Details: + * configuration.resolve.tsconfig should be a boolean. + * configuration.resolve.tsconfig should be a string. + * configuration.resolve.tsconfig should be an object: + object { configFile?, references? }" + `) + ); + + createTestCaseWithoutError("experiments", { + experiments: { unknown: true } + }); + + createTestCaseWithoutError("resolve", { + resolve: { tsconfig: true } + }); + + createTestCaseWithoutError("unknown and validate", { + unknown: true, + validate: false + }); + + createTestCase( + "unknown and validate #1", + { + unknown: true, + validate: true + }, + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration has an unknown property 'unknown'. These properties are valid: + object { amd?, bail?, cache?, context?, dependencies?, devServer?, devtool?, dotenv?, entry?, experiments?, extends?, externals?, externalsPresets?, externalsType?, ignoreWarnings?, infrastructureLogging?, loader?, mode?, module?, name?, node?, optimization?, output?, parallelism?, performance?, plugins?, profile?, recordsInputPath?, recordsOutputPath?, recordsPath?, resolve?, resolveLoader?, snapshot?, stats?, target?, validate?, watch?, watchOptions? } + -> Options object as provided by the user. + For typos: please correct them. + For loader options: webpack >= v2.0.0 no longer allows custom properties in configuration. + Loaders should be updated to allow passing options via loader options in module.rules. + Until loaders are updated one can use the LoaderOptionsPlugin to pass these options to the loader: + plugins: [ + new webpack.LoaderOptionsPlugin({ + // test: /\\\\.xxx$/, // may apply this only for some modules + options: { + unknown: … + } + }) + ]" + `) + ); + + createTestCaseWithoutError("unknown and validate (multi compiler mode)", [ + { + unknown1: true, + validate: false + }, + { + unknown2: true, + validate: false } - ]; + ]); - testCases.forEach(testCase => { - it("should fail validation for " + testCase.name, () => { - try { - webpack(testCase.config); - } catch (err) { - if (err.name !== "WebpackOptionsValidationError") throw err; + createTestCase( + "unknown and validate with (multi compiler mode) #1", + [ + { + unknown1: true, + validate: true + }, + { + unknown2: true, + validate: false + } + ], + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration[0] has an unknown property 'unknown1'. These properties are valid: + object { amd?, bail?, cache?, context?, dependencies?, devServer?, devtool?, dotenv?, entry?, experiments?, extends?, externals?, externalsPresets?, externalsType?, ignoreWarnings?, infrastructureLogging?, loader?, mode?, module?, name?, node?, optimization?, output?, parallelism?, performance?, plugins?, profile?, recordsInputPath?, recordsOutputPath?, recordsPath?, resolve?, resolveLoader?, snapshot?, stats?, target?, validate?, watch?, watchOptions? } + -> Options object as provided by the user." + `) + ); + + createTestCase( + "unknown and validate with (multi compiler mode) #2", + [ + { + unknown1: true, + validate: false + }, + { + unknown2: true, + validate: true + } + ], + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration[1] has an unknown property 'unknown2'. These properties are valid: + object { amd?, bail?, cache?, context?, dependencies?, devServer?, devtool?, dotenv?, entry?, experiments?, extends?, externals?, externalsPresets?, externalsType?, ignoreWarnings?, infrastructureLogging?, loader?, mode?, module?, name?, node?, optimization?, output?, parallelism?, performance?, plugins?, profile?, recordsInputPath?, recordsOutputPath?, recordsPath?, resolve?, resolveLoader?, snapshot?, stats?, target?, validate?, watch?, watchOptions? } + -> Options object as provided by the user." + `) + ); + + createTestCase( + "unknown and validate with (multi compiler mode) #3", + [ + { + unknown1: true, + validate: true + }, + { + unknown2: true, + validate: true + } + ], + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration[0] has an unknown property 'unknown1'. These properties are valid: + object { amd?, bail?, cache?, context?, dependencies?, devServer?, devtool?, dotenv?, entry?, experiments?, extends?, externals?, externalsPresets?, externalsType?, ignoreWarnings?, infrastructureLogging?, loader?, mode?, module?, name?, node?, optimization?, output?, parallelism?, performance?, plugins?, profile?, recordsInputPath?, recordsOutputPath?, recordsPath?, resolve?, resolveLoader?, snapshot?, stats?, target?, validate?, watch?, watchOptions? } + -> Options object as provided by the user. + - configuration[1] has an unknown property 'unknown2'. These properties are valid: + object { amd?, bail?, cache?, context?, dependencies?, devServer?, devtool?, dotenv?, entry?, experiments?, extends?, externals?, externalsPresets?, externalsType?, ignoreWarnings?, infrastructureLogging?, loader?, mode?, module?, name?, node?, optimization?, output?, parallelism?, performance?, plugins?, profile?, recordsInputPath?, recordsOutputPath?, recordsPath?, resolve?, resolveLoader?, snapshot?, stats?, target?, validate?, watch?, watchOptions? } + -> Options object as provided by the user." + `) + ); - if (testCase.test) { - testCase.test(err); + describe("did you mean", () => { + createTestCase( + "module.rules", + { + rules: [] + }, + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration has an unknown property 'rules'. These properties are valid: + object { amd?, bail?, cache?, context?, dependencies?, devServer?, devtool?, dotenv?, entry?, experiments?, extends?, externals?, externalsPresets?, externalsType?, ignoreWarnings?, infrastructureLogging?, loader?, mode?, module?, name?, node?, optimization?, output?, parallelism?, performance?, plugins?, profile?, recordsInputPath?, recordsOutputPath?, recordsPath?, resolve?, resolveLoader?, snapshot?, stats?, target?, validate?, watch?, watchOptions? } + -> Options object as provided by the user. + Did you mean module.rules?" + `) + ); - return; + createTestCase( + "optimization.splitChunks", + { + splitChunks: false + }, + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration has an unknown property 'splitChunks'. These properties are valid: + object { amd?, bail?, cache?, context?, dependencies?, devServer?, devtool?, dotenv?, entry?, experiments?, extends?, externals?, externalsPresets?, externalsType?, ignoreWarnings?, infrastructureLogging?, loader?, mode?, module?, name?, node?, optimization?, output?, parallelism?, performance?, plugins?, profile?, recordsInputPath?, recordsOutputPath?, recordsPath?, resolve?, resolveLoader?, snapshot?, stats?, target?, validate?, watch?, watchOptions? } + -> Options object as provided by the user. + Did you mean optimization.splitChunks?" + `) + ); + + createTestCase( + "module.noParse", + { + noParse: /a/ + }, + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration has an unknown property 'noParse'. These properties are valid: + object { amd?, bail?, cache?, context?, dependencies?, devServer?, devtool?, dotenv?, entry?, experiments?, extends?, externals?, externalsPresets?, externalsType?, ignoreWarnings?, infrastructureLogging?, loader?, mode?, module?, name?, node?, optimization?, output?, parallelism?, performance?, plugins?, profile?, recordsInputPath?, recordsOutputPath?, recordsPath?, resolve?, resolveLoader?, snapshot?, stats?, target?, validate?, watch?, watchOptions? } + -> Options object as provided by the user. + Did you mean module.noParse?" + `) + ); + + createTestCase( + "optimization.moduleIds", + { + optimization: { + hashedModuleIds: true } + }, + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration.optimization has an unknown property 'hashedModuleIds'. These properties are valid: + object { avoidEntryIife?, checkWasmTypes?, chunkIds?, concatenateModules?, emitOnErrors?, flagIncludedChunks?, inlineExports?, innerGraph?, mangleExports?, mangleWasmImports?, mergeDuplicateChunks?, minimize?, minimizer?, moduleIds?, noEmitOnErrors?, nodeEnv?, portableRecords?, providedExports?, realContentHash?, removeAvailableModules?, removeEmptyChunks?, runtimeChunk?, sideEffects?, splitChunks?, usedExports? } + -> Enables/Disables integrated optimizations. + Did you mean optimization.moduleIds: \\"hashed\\" (BREAKING CHANGE since webpack 5)?" + `) + ); - expect(err.message).toMatch(/^Invalid configuration object./); - expect(err.message.split("\n").slice(1)).toEqual(testCase.message); + createTestCase( + "optimization.chunkIds", + { + optimization: { + namedChunks: true + } + }, + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration.optimization has an unknown property 'namedChunks'. These properties are valid: + object { avoidEntryIife?, checkWasmTypes?, chunkIds?, concatenateModules?, emitOnErrors?, flagIncludedChunks?, inlineExports?, innerGraph?, mangleExports?, mangleWasmImports?, mergeDuplicateChunks?, minimize?, minimizer?, moduleIds?, noEmitOnErrors?, nodeEnv?, portableRecords?, providedExports?, realContentHash?, removeAvailableModules?, removeEmptyChunks?, runtimeChunk?, sideEffects?, splitChunks?, usedExports? } + -> Enables/Disables integrated optimizations. + Did you mean optimization.chunkIds: \\"named\\" (BREAKING CHANGE since webpack 5)?" + `) + ); - return; - } + createTestCase( + "optimization.chunk/moduleIds", + { + optimization: { + occurrenceOrder: true + } + }, + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration.optimization has an unknown property 'occurrenceOrder'. These properties are valid: + object { avoidEntryIife?, checkWasmTypes?, chunkIds?, concatenateModules?, emitOnErrors?, flagIncludedChunks?, inlineExports?, innerGraph?, mangleExports?, mangleWasmImports?, mergeDuplicateChunks?, minimize?, minimizer?, moduleIds?, noEmitOnErrors?, nodeEnv?, portableRecords?, providedExports?, realContentHash?, removeAvailableModules?, removeEmptyChunks?, runtimeChunk?, sideEffects?, splitChunks?, usedExports? } + -> Enables/Disables integrated optimizations. + Did you mean optimization.chunkIds: \\"size\\" and optimization.moduleIds: \\"size\\" (BREAKING CHANGE since webpack 5)?" + `) + ); - throw new Error("Validation didn't fail"); - }); + createTestCase( + "optimization.idHint", + { + optimization: { + splitChunks: { + automaticNamePrefix: "vendor" + } + } + }, + (msg) => + expect(msg).toMatchInlineSnapshot(` + "Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema. + - configuration.optimization.splitChunks has an unknown property 'automaticNamePrefix'. These properties are valid: + object { automaticNameDelimiter?, cacheGroups?, chunks?, defaultSizeTypes?, enforceSizeThreshold?, fallbackCacheGroup?, filename?, hidePathInfo?, maxAsyncRequests?, maxAsyncSize?, maxInitialRequests?, maxInitialSize?, maxSize?, minChunks?, minRemainingSize?, minSize?, minSizeReduction?, name?, usedExports? } + -> Options object for splitting chunks into smaller chunks." + `) + ); }); }); diff --git a/test/WasmHashes.unittest.js b/test/WasmHashes.unittest.js new file mode 100644 index 00000000000..1af7ef71192 --- /dev/null +++ b/test/WasmHashes.unittest.js @@ -0,0 +1,186 @@ +"use strict"; + +const { createHash, randomBytes } = require("crypto"); + +const wasmHashes = { + xxhash64: () => { + const createHash = require("../lib/util/hash/xxhash64"); + const createReferenceHash = + // @ts-expect-error + require("hash-wasm/dist/xxhash64.umd.min").createXXHash64; + + return { + createHash, + createReferenceHash: async () => (await createReferenceHash()).init(), + regExp: /^[0-9a-f]{16}$/ + }; + }, + "xxhash64-createHash": () => { + const createXxHash = require("../lib/util/hash/xxhash64"); + const createHash = require("../lib/util/createHash"); + + return { + createHash: () => createHash("xxhash64"), + createReferenceHash: createXxHash, + regExp: /^[0-9a-f]{16}$/ + }; + }, + md4: () => { + const createMd4Hash = require("../lib/util/hash/md4"); + + return { + createHash: createMd4Hash, + createReferenceHash: + Number.parseInt(process.version.slice(1), 10) < 17 + ? async () => createHash("md4") + : createMd4Hash, + regExp: /^[0-9a-f]{32}$/ + }; + }, + "md4-createHash": () => { + const createMd4Hash = require("../lib/util/hash/md4"); + const createHash = require("../lib/util/createHash"); + + return { + createHash: () => createHash("md4"), + createReferenceHash: createMd4Hash, + regExp: /^[0-9a-f]{32}$/ + }; + } +}; + +for (const name of Object.keys(wasmHashes)) { + const { createHash, createReferenceHash, regExp } = + /** @type {Record { createHash: EXPECTED_ANY, createReferenceHash: EXPECTED_ANY, regExp: RegExp }>} */ ( + wasmHashes + )[name](); + + describe(name, () => { + const sizes = [ + 1, + 2, + 3, + 4, + 5, + 7, + 8, + 9, + 16, + 31, + 32, + 33, + 64 - 10, + 64 - 9, + 64 - 8, + 63, + 64, + 65, + 100, + 1000, + 65536 - 1, + 65536, + 65536 + 1, + 65536 + 31, + 65536 * 5, + 65536 * 7 - 1, + 65536 * 9 + 31 + ]; + + /** + * @param {string} name name + * @param {number[]} sizes sizes + */ + const test = (name, sizes) => { + it(`${name} should generate a hash from binary data`, async () => { + const hash = createHash(); + const hashString = createHash(); + const reference = await createReferenceHash(); + for (const size of sizes) { + const bytes = randomBytes(size); + const string = bytes.toString("base64"); + hash.update(bytes); + hashString.update(string, "base64"); + reference.update(bytes); + } + const result = hash.digest("hex"); + expect(result).toMatch(regExp); + const resultFromString = hashString.digest("hex"); + expect(resultFromString).toMatch(regExp); + const expected = reference.digest("hex"); + expect(result).toBe(expected); + expect(resultFromString).toBe(expected); + }); + }; + + test("empty hash", []); + + for (const size of sizes) { + test(`single update ${size} bytes`, [size]); + } + + for (const size1 of sizes) { + for (const size2 of sizes) { + test(`two updates ${size1} + ${size2} bytes`, [size1, size2]); + } + } + + test("many updates 1", sizes); + + test("many updates 2", [...sizes].reverse()); + + test("many updates 3", [...sizes, ...[...sizes].reverse()]); + + test("many updates 4", [...[...sizes].reverse(), ...sizes]); + + /** + * @param {string} name name + * @param {string | number[]} codePoints code points + */ + const unicodeTest = (name, codePoints) => { + it(`${name} should hash unicode chars correctly`, async () => { + const hash = createHash(); + const reference = await createReferenceHash(); + const str = + typeof codePoints === "string" + ? codePoints + : String.fromCodePoint(...codePoints); + hash.update(str); + reference.update(str); + const result = hash.digest("hex"); + expect(result).toMatch(regExp); + const expected = reference.digest("hex"); + expect(result).toBe(expected); + }); + }; + + /** + * @param {string} name name + * @param {number} start start + * @param {number} end end + */ + const unicodeRangeTest = (name, start, end) => { + const codePoints = []; + for (let i = start; i <= end; i++) { + codePoints.push(i); + } + unicodeTest(name, codePoints); + }; + + // cspell:word Thaana + unicodeRangeTest("Latin-1 Supplement", 0xa0, 0xff); + unicodeRangeTest("Latin Extended", 0x100, 0x24f); + unicodeRangeTest("Thaana", 0x780, 0x7bf); + unicodeRangeTest("Devanagari", 0x900, 0x97f); + unicodeRangeTest("Emoticons", 0x1f600, 0x1f64f); + + unicodeTest("with zero char", "abc\0💩"); + unicodeTest("weird code point after long code point", [1497, 243248]); + + for (let i = 0; i < 1000; i++) { + const chars = Array.from({ length: 20 }, () => + Math.floor(Math.random() * 0x10ffff) + ); + unicodeTest(`fuzzy ${JSON.stringify(chars)}`, chars); + } + }); +} diff --git a/test/Watch.test.js b/test/Watch.test.js new file mode 100644 index 00000000000..c51d3ef4263 --- /dev/null +++ b/test/Watch.test.js @@ -0,0 +1,116 @@ +"use strict"; + +require("./helpers/warmup-webpack"); + +const path = require("path"); +const { Volume, createFsFromVolume } = require("memfs"); +const webpack = require(".."); +const expectNoDeprecations = require("./helpers/expectNoDeprecations"); + +expectNoDeprecations(); + +describe("Watch", () => { + it("should only compile a single time", (done) => { + let counterBeforeCompile = 0; + let counterDone = 0; + let counterHandler = 0; + const compiler = /** @type {import("../").Compiler} */ ( + webpack( + { + context: path.resolve(__dirname, "fixtures/watch"), + watch: true, + mode: "development", + snapshot: { + managedPaths: [/^(.+?[\\/]node_modules[\\/])/] + }, + experiments: { + futureDefaults: true + }, + module: { + // unsafeCache: false, + rules: [ + { + test: /\.js$/, + use: "some-loader" + } + ] + }, + plugins: [ + (c) => { + c.hooks.beforeCompile.tap("test", () => { + counterBeforeCompile++; + }); + c.hooks.done.tap("test", () => { + counterDone++; + }); + } + ] + }, + (err, stats) => { + if (err) return done(err); + if (/** @type {import("../").Stats} */ (stats).hasErrors()) { + return done( + new Error(/** @type {import("../").Stats} */ (stats).toString()) + ); + } + counterHandler++; + } + ) + ); + compiler.outputFileSystem = /** @type {import("../").OutputFileSystem} */ ( + /** @type {unknown} */ (createFsFromVolume(new Volume())) + ); + setTimeout(() => { + expect(counterBeforeCompile).toBe(1); + expect(counterDone).toBe(1); + expect(counterHandler).toBe(1); + compiler.close(done); + }, 5000); + }); + + it("should correctly emit asset when invalidation occurs again", (done) => { + /** + * @param {unknown} err error + */ + function handleError(err) { + if (err) done(/** @type {Error} */ (err)); + } + let calls = 0; + const compiler = webpack({ + mode: "development", + context: path.resolve(__dirname, "fixtures/watch"), + plugins: [ + (c) => { + // Ensure the second invalidation can occur during compiler running + let once = false; + c.hooks.afterCompile.tapAsync("LongTask", (_, cb) => { + if (once) return cb(); + once = true; + setTimeout(() => { + cb(); + }, 1000); + }); + }, + (c) => { + c.hooks.done.tap("Test", () => { + // Should emit assets twice, instead of once + expect(calls).toBe(2); + done(); + }); + } + ] + }); + + compiler.watch({}, handleError); + compiler.hooks.emit.tap("Test", () => { + calls++; + }); + + // First invalidation + /** @type {import("../").Watching} */ (compiler.watching).invalidate(); + // Second invalidation while compiler is still running + setTimeout(() => { + /** @type {import("../").Watching} */ (compiler.watching).invalidate(); + }, 50); + }); +}); diff --git a/test/WatchCacheUnaffectedTestCases.longtest.js b/test/WatchCacheUnaffectedTestCases.longtest.js new file mode 100644 index 00000000000..8b5649240f0 --- /dev/null +++ b/test/WatchCacheUnaffectedTestCases.longtest.js @@ -0,0 +1,10 @@ +"use strict"; + +const { describeCases } = require("./WatchTestCases.template"); + +describeCases({ + name: "WatchCacheUnaffectedTestCases", + experiments: { + cacheUnaffected: true + } +}); diff --git a/test/WatchClose.test.js b/test/WatchClose.test.js new file mode 100644 index 00000000000..21fa6d45c00 --- /dev/null +++ b/test/WatchClose.test.js @@ -0,0 +1,77 @@ +"use strict"; + +require("./helpers/warmup-webpack"); + +const path = require("path"); +const expectNoDeprecations = require("./helpers/expectNoDeprecations"); + +expectNoDeprecations(); + +describe("WatchClose", () => { + describe("multiple calls watcher", () => { + const fixturePath = path.join(__dirname, "fixtures"); + const outputPath = path.join(__dirname, "js/WatchClose"); + const filePath = path.join(fixturePath, "a.js"); + + /** @type {import("../").Compiler | null} */ + let compiler; + /** @type {import("../").Watching} */ + let watcher; + + beforeEach(() => { + const webpack = require("../"); + + compiler = webpack({ + mode: "development", + entry: filePath, + output: { + path: outputPath, + filename: "bundle.js" + } + }); + watcher = /** @type {import("../").Watching} */ ( + /** @type {import("../").Compiler} */ (compiler).watch( + { poll: 300 }, + () => {} + ) + ); + }); + + afterEach(() => { + /** @type {{ close: () => void }} */ ( + /** @type {unknown} */ (watcher) + ).close(); + compiler = null; + }); + + /** + * @param {import("../").Watching} watcher watcher + * @param {(err?: null | Error) => void} callback callback + * @returns {Promise} + */ + function close(watcher, callback) { + return new Promise((res) => { + const onClose = () => { + callback(); + res(); + }; + watcher.close(onClose); + }); + } + + it("each callback should be called", async () => { + let num = 0; + + await Promise.all([ + close(watcher, () => (num += 1)), + close(watcher, () => (num += 10)) + ]); + await Promise.all([ + close(watcher, () => (num += 100)), + close(watcher, () => (num += 1000)) + ]); + + expect(num).toBe(1111); + }); + }); +}); diff --git a/test/WatchDetection.test.js b/test/WatchDetection.test.js index 590c56ef871..aadae1faced 100644 --- a/test/WatchDetection.test.js +++ b/test/WatchDetection.test.js @@ -1,20 +1,25 @@ "use strict"; -/*globals describe it */ const path = require("path"); -const fs = require("fs"); -const MemoryFs = require("memory-fs"); +const fs = require("graceful-fs"); +const { Volume, createFsFromVolume } = require("memfs"); -const webpack = require("../"); +const webpack = require(".."); +const expectNoDeprecations = require("./helpers/expectNoDeprecations"); + +expectNoDeprecations(); describe("WatchDetection", () => { if (process.env.NO_WATCH_TESTS) { + // eslint-disable-next-line jest/no-disabled-tests it.skip("long running tests excluded", () => {}); + return; } - jest.setTimeout(10000); - + createTestCase(100, true); + createTestCase(10, true); + createTestCase(600, true); for (let changeTimeout = 10; changeTimeout < 100; changeTimeout += 10) { createTestCase(changeTimeout); } @@ -22,12 +27,18 @@ describe("WatchDetection", () => { createTestCase(changeTimeout); } - function createTestCase(changeTimeout) { - describe(`time between changes ${changeTimeout}ms`, () => { + /** + * @param {number} changeTimeout change timeout + * @param {boolean=} invalidate need invalidate? + */ + function createTestCase(changeTimeout, invalidate) { + describe(`time between changes ${changeTimeout}ms${ + invalidate ? " with invalidate call" : "" + }`, () => { const fixturePath = path.join( __dirname, "fixtures", - "temp-" + changeTimeout + `temp-${changeTimeout}` ); const filePath = path.join(fixturePath, "file.js"); const file2Path = path.join(fixturePath, "file2.js"); @@ -36,108 +47,136 @@ describe("WatchDetection", () => { beforeAll(() => { try { fs.mkdirSync(fixturePath); - } catch (e) { + } catch (_err) { // empty } - fs.writeFileSync(filePath, "require('./file2')", "utf-8"); - fs.writeFileSync(file2Path, "original", "utf-8"); + fs.writeFileSync(filePath, "require('./file2')", "utf8"); + fs.writeFileSync(file2Path, "original", "utf8"); }); - afterAll(done => { + afterAll((done) => { setTimeout(() => { try { fs.unlinkSync(filePath); - } catch (e) { + } catch (_err) { // empty } try { fs.unlinkSync(file2Path); - } catch (e) { + } catch (_err) { // empty } try { fs.rmdirSync(fixturePath); - } catch (e) { + } catch (_err) { // empty } done(); }, 100); // cool down a bit }); - it("should build the bundle correctly", done => { + it("should build the bundle correctly", (done) => { const compiler = webpack({ mode: "development", - entry: loaderPath + "!" + filePath, + entry: `${loaderPath}!${filePath}`, output: { - path: "/", + path: "/directory", filename: "bundle.js" } }); - const memfs = (compiler.outputFileSystem = new MemoryFs()); + const memfs = (compiler.outputFileSystem = + /** @type {import("../").OutputFileSystem & import("memfs").IFs} */ ( + /** @type {unknown} */ (createFsFromVolume(new Volume())) + )); + /** @type {(() => void) | null | undefined} */ let onChange; compiler.hooks.done.tap("WatchDetectionTest", () => { if (onChange) onChange(); }); + /** @type {import("../").Watching} */ let watcher; step1(); + /** + * @returns {void} + */ function step1() { onChange = () => { if ( - memfs.readFileSync("/bundle.js") && + memfs.readFileSync("/directory/bundle.js") && memfs - .readFileSync("/bundle.js") + .readFileSync("/directory/bundle.js") .toString() - .indexOf("original") >= 0 - ) + .includes("original") + ) { step2(); + } }; - watcher = compiler.watch( - { - aggregateTimeout: 50 - }, - () => {} + watcher = /** @type {import("../").Watching} */ ( + compiler.watch( + { + aggregateTimeout: 50 + }, + () => {} + ) ); } + /** + * @returns {void} + */ function step2() { - onChange = null; + onChange = () => { + expect(compiler.modifiedFiles).toBeDefined(); + expect(compiler.removedFiles).toBeDefined(); + }; fs.writeFile( filePath, "require('./file2'); again", - "utf-8", + "utf8", handleError ); setTimeout(step3, changeTimeout); } + /** + * @returns {void} + */ function step3() { - onChange = null; - - fs.writeFile(file2Path, "wrong", "utf-8", handleError); + if (invalidate) watcher.invalidate(); + fs.writeFile(file2Path, "wrong", "utf8", handleError); setTimeout(step4, changeTimeout); } + /** + * @returns {void} + */ function step4() { onChange = () => { + expect(compiler.modifiedFiles).toBeDefined(); + expect(compiler.removedFiles).toBeDefined(); if ( memfs - .readFileSync("/bundle.js") + .readFileSync("/directory/bundle.js") .toString() - .indexOf("correct") >= 0 - ) + .includes("correct") + ) { step5(); + } }; - fs.writeFile(file2Path, "correct", "utf-8", handleError); + fs.writeFile(file2Path, "correct", "utf8", handleError); } + /** + * @returns {void} + */ function step5() { onChange = null; @@ -146,6 +185,9 @@ describe("WatchDetection", () => { }); } + /** + * @param {unknown} err err + */ function handleError(err) { if (err) done(err); } diff --git a/test/WatchSuspend.test.js b/test/WatchSuspend.test.js new file mode 100644 index 00000000000..db138f7f34d --- /dev/null +++ b/test/WatchSuspend.test.js @@ -0,0 +1,204 @@ +"use strict"; + +require("./helpers/warmup-webpack"); + +const fs = require("fs"); +const path = require("path"); +const expectNoDeprecations = require("./helpers/expectNoDeprecations"); + +expectNoDeprecations(); + +describe("WatchSuspend", () => { + if (process.env.NO_WATCH_TESTS) { + // eslint-disable-next-line jest/no-disabled-tests + it.skip("long running tests excluded", () => {}); + + return; + } + + describe("suspend and resume watcher", () => { + const fixturePath = path.join( + __dirname, + "fixtures", + `temp-watch-${Date.now()}` + ); + const filePath = path.join(fixturePath, "file.js"); + const file2Path = path.join(fixturePath, "file2.js"); + const file3Path = path.join(fixturePath, "file3.js"); + const outputPath = path.join(__dirname, "js/WatchSuspend"); + const outputFile = path.join(outputPath, "bundle.js"); + /** @type {import("../").Compiler} */ + let compiler = /** @type {import("../").Compiler} */ ( + /** @type {unknown} */ (null) + ); + /** @type {import("../").Watching} */ + let watching = /** @type {import("../").Watching} */ ( + /** @type {unknown} */ (null) + ); + /** @type {(() => void) | null} */ + let onChange = null; + + beforeAll(() => { + try { + fs.mkdirSync(fixturePath); + } catch (_err) { + // skip + } + try { + fs.writeFileSync(filePath, "'foo'", "utf8"); + fs.writeFileSync(file2Path, "'file2'", "utf8"); + fs.writeFileSync(file3Path, "'file3'", "utf8"); + } catch (_err) { + // skip + } + + const webpack = require("../"); + + compiler = webpack({ + mode: "development", + entry: filePath, + output: { + path: outputPath, + filename: "bundle.js" + } + }); + watching = /** @type {import("../").Watching} */ ( + compiler.watch({ aggregateTimeout: 50 }, () => {}) + ); + + compiler.hooks.done.tap("WatchSuspendTest", () => { + if (onChange) onChange(); + }); + }); + + afterAll(() => { + /** @type {{ close: () => void }} */ ( + /** @type {unknown} */ (watching) + ).close(); + compiler = /** @type {import("../").Compiler} */ ( + /** @type {unknown} */ (null) + ); + try { + fs.unlinkSync(filePath); + } catch (_err) { + // skip + } + try { + fs.rmdirSync(fixturePath); + } catch (_err) { + // skip + } + }); + + it("should compile successfully", (done) => { + onChange = () => { + expect(fs.readFileSync(outputFile, "utf8")).toContain("'foo'"); + onChange = null; + done(); + }; + }); + + it("should suspend compilation", (done) => { + onChange = jest.fn(); + watching.suspend(); + fs.writeFileSync(filePath, "'bar'", "utf8"); + setTimeout(() => { + expect(onChange).not.toHaveBeenCalled(); + onChange = null; + done(); + }, 1000); + }); + + it("should resume compilation", (done) => { + onChange = () => { + expect(fs.readFileSync(outputFile, "utf8")).toContain("'bar'"); + onChange = null; + done(); + }; + watching.resume(); + }); + + for (const changeBefore of [false, true]) { + for (const delay of [200, 1500]) { + // eslint-disable-next-line no-loop-func + it(`should not ignore changes during resumed compilation (changeBefore: ${changeBefore}, delay: ${delay}ms)`, async () => { + // aggregateTimeout must be long enough for this test + // So set-up new watcher and wait when initial compilation is done + await new Promise((resolve) => { + watching.close(() => { + watching = /** @type {import("../").Watching} */ ( + compiler.watch({ aggregateTimeout: 1000 }, () => { + resolve(undefined); + }) + ); + }); + }); + return /** @type {Promise} */ ( + new Promise((resolve) => { + if (changeBefore) fs.writeFileSync(filePath, "'bar'", "utf8"); + setTimeout(() => { + watching.suspend(); + fs.writeFileSync(filePath, "'baz'", "utf8"); + + onChange = /** @type {null} */ ( + /** @type {unknown} */ ("throw") + ); + setTimeout(() => { + onChange = () => { + expect(fs.readFileSync(outputFile, "utf8")).toContain( + "'baz'" + ); + expect( + compiler.modifiedFiles && + [...compiler.modifiedFiles].sort() + ).toEqual([filePath]); + expect( + compiler.removedFiles && [...compiler.removedFiles] + ).toEqual([]); + onChange = null; + resolve(); + }; + watching.resume(); + }, delay); + }, 200); + }) + ); + }); + } + } + + it("should not drop changes when suspended", (done) => { + const aggregateTimeout = 50; + // Trigger initial compilation with file2.js (assuming correct) + fs.writeFileSync( + filePath, + 'require("./file2.js"); require("./file3.js")', + "utf8" + ); + + onChange = () => { + // Initial compilation is done, start the test + watching.suspend(); + + // Trigger the first change (works as expected): + fs.writeFileSync(file2Path, "'foo'", "utf8"); + + // Trigger the second change _after_ aggregation timeout of the first + setTimeout(() => { + fs.writeFileSync(file3Path, "'bar'", "utf8"); + + // Wait when the file3 edit is settled and re-compile + setTimeout(() => { + watching.resume(); + + onChange = () => { + onChange = null; + expect(fs.readFileSync(outputFile, "utf8")).toContain("'bar'"); + done(); + }; + }, 200); + }, aggregateTimeout + 50); + }; + }); + }); +}); diff --git a/test/WatchTestCases.longtest.js b/test/WatchTestCases.longtest.js new file mode 100644 index 00000000000..23e8f27837c --- /dev/null +++ b/test/WatchTestCases.longtest.js @@ -0,0 +1,7 @@ +"use strict"; + +const { describeCases } = require("./WatchTestCases.template"); + +describeCases({ + name: "WatchTestCases" +}); diff --git a/test/WatchTestCases.template.js b/test/WatchTestCases.template.js new file mode 100644 index 00000000000..9defc5e22cf --- /dev/null +++ b/test/WatchTestCases.template.js @@ -0,0 +1,484 @@ +"use strict"; + +require("./helpers/warmup-webpack"); + +/** @typedef {Record} Env */ +/** @typedef {{ testPath: string, srcPath: string }} TestOptions */ +/** + * @typedef {object} SuiteConfig + * @property {string} name suite name + * @property {EXPECTED_ANY=} experiments experiments overrides + * @property {EXPECTED_ANY=} optimization optimization overrides + */ +/** + * @typedef {object} WatchTestConfig + * @property {((i: EXPECTED_ANY, options: EXPECTED_ANY) => string)=} findBundle + * @property {boolean=} noTests + */ + +const path = require("path"); +const fs = require("graceful-fs"); +/** @type {{ sync: (p: string) => void, (p: string, cb: (err: EXPECTED_ANY) => void): void }} */ +const rimraf = require("rimraf"); +const { parseResource } = require("../lib/util/identifier"); +const checkArrayExpectation = require("./checkArrayExpectation"); +const { TestRunner } = require("./harness/runner"); +const createLazyTestEnv = require("./helpers/createLazyTestEnv"); +const deprecationTracking = require("./helpers/deprecationTracking"); +const prepareOptions = require("./helpers/prepareOptions"); +const { remove } = require("./helpers/remove"); +const supportsObjectHasOwn = require("./helpers/supportsObjectHasOwn"); +const supportsOptionalChaining = require("./helpers/supportsOptionalChaining"); + +/** + * @param {string} src src + * @param {string} dest dest + * @param {boolean} initial is initial? + */ +function copyDiff(src, dest, initial) { + if (!fs.existsSync(dest)) fs.mkdirSync(dest); + const files = fs.readdirSync(src); + for (const filename of files) { + const srcFile = path.join(src, filename); + const destFile = path.join(dest, filename); + const directory = fs.statSync(srcFile).isDirectory(); + if (directory) { + copyDiff(srcFile, destFile, initial); + } else { + const content = fs.readFileSync(srcFile); + if (/^DELETE\s*$/.test(content.toString("utf8"))) { + fs.unlinkSync(destFile); + } else if (/^DELETE_DIRECTORY\s*$/.test(content.toString("utf8"))) { + rimraf.sync(destFile); + } else { + fs.writeFileSync(destFile, content); + if (initial) { + const longTimeAgo = Date.now() - 1000 * 60 * 60 * 24; + fs.utimesSync( + destFile, + Date.now() - longTimeAgo, + Date.now() - longTimeAgo + ); + } + } + } + } +} + +/** + * @param {SuiteConfig} config suite config + */ +const describeCases = (config) => { + describe(config.name, () => { + beforeAll(() => { + let dest = path.join(__dirname, "js"); + if (!fs.existsSync(dest)) fs.mkdirSync(dest); + dest = path.join(__dirname, "js", `${config.name}-src`); + if (!fs.existsSync(dest)) fs.mkdirSync(dest); + }); + + if (process.env.NO_WATCH_TESTS) { + // eslint-disable-next-line jest/no-disabled-tests + it.skip("long running tests excluded", () => {}); + + return; + } + + const casesPath = path.join(__dirname, "watchCases"); + const categories = fs.readdirSync(casesPath).map((cat) => ({ + name: cat, + tests: fs + .readdirSync(path.join(casesPath, cat)) + .filter((folder) => !folder.includes("_")) + .filter((testName) => { + const testDirectory = path.join(casesPath, cat, testName); + const filterPath = path.join(testDirectory, "test.filter.js"); + if (fs.existsSync(filterPath) && !require(filterPath)(config)) { + // eslint-disable-next-line jest/no-disabled-tests, jest/valid-describe-callback + describe.skip(testName, () => it("filtered", () => {})); + + return false; + } + return true; + }) + .sort() + })); + + for (const category of categories) { + // eslint-disable-next-line jest/prefer-hooks-on-top, jest/no-duplicate-hooks + beforeAll(() => { + const dest = path.join( + __dirname, + "js", + `${config.name}-src`, + category.name + ); + if (!fs.existsSync(dest)) fs.mkdirSync(dest); + }); + + describe(category.name, () => { + for (const testName of category.tests) { + describe(testName, () => { + const tempDirectory = path.join( + __dirname, + "js", + `${config.name}-src`, + category.name, + testName + ); + const testDirectory = path.join(casesPath, category.name, testName); + /** @type {{ name: string, done?: boolean, stats?: import("../").Stats, it?: EXPECTED_ANY, getNumberOfTests?: () => number }[]} */ + const runs = fs + .readdirSync(testDirectory) + .sort() + .filter((name) => + fs.statSync(path.join(testDirectory, name)).isDirectory() + ) + .map((name) => ({ name })); + + beforeAll((done) => { + rimraf(tempDirectory, done); + }); + + it(`${testName} should compile`, (done) => { + const outputDirectory = path.join( + __dirname, + "js", + config.name, + category.name, + testName + ); + + rimraf.sync(outputDirectory); + + let options = {}; + const configPath = path.join(testDirectory, "webpack.config.js"); + if (fs.existsSync(configPath)) { + options = prepareOptions(require(configPath), { + testPath: outputDirectory, + srcPath: tempDirectory + }); + } + const applyConfig = ( + /** @type {import("../").Configuration} */ options, + /** @type {number} */ idx + ) => { + if (!options.mode) options.mode = "development"; + if (!options.context) options.context = tempDirectory; + if (!options.entry) options.entry = "./index.js"; + if (!options.target) options.target = "async-node"; + if (!options.output) options.output = {}; + if (!options.output.environment) { + options.output.environment = {}; + } + if ( + options.output.environment.optionalChaining === undefined && + !supportsOptionalChaining() + ) { + // generated runtime runs in this Node.js process; avoid `?.` on Node < 14 + options.output.environment.optionalChaining = false; + } + if ( + options.output.environment.hasOwn === undefined && + !supportsObjectHasOwn() + ) { + // generated runtime runs in this Node.js process; avoid `Object.hasOwn` on Node < 16.9 + options.output.environment.hasOwn = false; + } + if (options.output.clean === undefined) { + options.output.clean = true; + } + if (!options.output.path) options.output.path = outputDirectory; + if (typeof options.output.pathinfo === "undefined") { + options.output.pathinfo = true; + } + if (!options.output.filename) { + options.output.filename = `bundle${ + options.experiments && options.experiments.outputModule + ? ".mjs" + : ".js" + }`; + } + if ( + options.cache && + /** @type {import("../").FileCacheOptions} */ (options.cache) + .type === "filesystem" + ) { + const cacheDirectory = path.join(tempDirectory, ".cache"); + /** @type {import("../").FileCacheOptions} */ ( + options.cache + ).cacheDirectory = cacheDirectory; + /** @type {import("../").FileCacheOptions} */ ( + options.cache + ).name = `config-${idx}`; + } + if (config.experiments) { + if (!options.experiments) options.experiments = {}; + for (const key of Object.keys(config.experiments)) { + if ( + /** @type {EXPECTED_ANY} */ (options.experiments)[key] === + undefined + ) { + /** @type {EXPECTED_ANY} */ (options.experiments)[key] = + config.experiments[key]; + } + } + } + if (config.optimization) { + if (!options.optimization) options.optimization = {}; + for (const key of Object.keys(config.optimization)) { + if ( + /** @type {EXPECTED_ANY} */ (options.optimization)[ + key + ] === undefined + ) { + /** @type {EXPECTED_ANY} */ (options.optimization)[key] = + config.optimization[key]; + } + } + } + }; + if (Array.isArray(options)) { + for (const [idx, item] of options.entries()) { + applyConfig(item, idx); + } + } else { + applyConfig(options, 0); + } + + const state = {}; + let runIdx = 0; + let waitMode = false; + let run = runs[runIdx]; + /** @type {string | null | undefined} */ + let triggeringFilename; + let lastHash = ""; + + const currentWatchStepModule = require("./helpers/currentWatchStep"); + + /** @type {(err?: Error | null) => void} */ + let compilationFinished = /** @type {EXPECTED_ANY} */ (done); + /** @type {{ step: string | undefined }} */ ( + currentWatchStepModule + ).step = run.name; + copyDiff(path.join(testDirectory, run.name), tempDirectory, true); + + setTimeout(() => { + const deprecationTracker = deprecationTracking.start(); + + const webpack = require(".."); + + const compiler = webpack(options); + compiler.hooks.invalid.tap( + "WatchTestCasesTest", + (filename, _mtime) => { + triggeringFilename = filename; + } + ); + compiler.watch( + { + aggregateTimeout: 1000 + }, + async (err, stats) => { + if (err) return compilationFinished(err); + if (!stats) { + return compilationFinished( + new Error("No stats reported from Compiler") + ); + } + if (stats.hash === lastHash) return; + lastHash = stats.hash; + if (run.done && lastHash !== stats.hash) { + return compilationFinished( + new Error( + `Compilation changed but no change was issued ${lastHash} != ${stats.hash} (run ${runIdx})\n` + + `Triggering change: ${triggeringFilename}` + ) + ); + } + if (waitMode) return; + run.done = true; + run.stats = stats; + if (err) return compilationFinished(err); + const statOptions = { + preset: "verbose", + cached: true, + cachedAssets: true, + cachedModules: true, + colors: false + }; + fs.mkdirSync(outputDirectory, { recursive: true }); + fs.writeFileSync( + path.join( + outputDirectory, + `stats.${runs[runIdx] && runs[runIdx].name}.txt` + ), + stats.toString(statOptions), + "utf8" + ); + const jsonStats = stats.toJson({ + errorDetails: true + }); + if ( + checkArrayExpectation( + path.join(testDirectory, run.name), + jsonStats, + "error", + "Error", + options, + compilationFinished + ) + ) { + return; + } + if ( + checkArrayExpectation( + path.join(testDirectory, run.name), + jsonStats, + "warning", + "Warning", + options, + compilationFinished + ) + ) { + return; + } + + /** @type {WatchTestConfig} */ + let testConfig = { + findBundle(_, options) { + const ext = path.extname( + parseResource(options.output.filename).path + ); + return `./bundle${ext}`; + } + }; + try { + // try to load a test file + testConfig = Object.assign( + testConfig, + require(path.join(testDirectory, "test.config.js")) + ); + } catch (_err) { + // empty + } + + if (testConfig.noTests) { + return process.nextTick(compilationFinished); + } + const { results } = TestRunner.runBundles({ + optionsArr: Array.isArray(options) ? options : [options], + outputDirectory, + testConfig: { + ...testConfig, + evaluateScriptOnAttached: true + }, + category, + testName, + setupRunner: ({ runner }) => { + runner.mergeModuleScope({ + it: run.it, + beforeEach: _beforeEach, + afterEach: _afterEach, + STATS_JSON: jsonStats, + STATE: state, + WATCH_STEP: run.name + }); + }, + getBundlePaths: (i, options) => + /** @type {NonNullable} */ ( + testConfig.findBundle + )(i, options) + }); + await Promise.all(results); + + if ( + /** @type {() => number} */ (run.getNumberOfTests)() < 1 + ) { + return compilationFinished( + new Error("No tests exported by test case") + ); + } + + /** @type {EXPECTED_ANY} */ (run.it)( + "should compile the next step", + (/** @type {(err?: Error | null) => void} */ done) => { + runIdx++; + if (runIdx < runs.length) { + run = runs[runIdx]; + waitMode = true; + setTimeout(() => { + waitMode = false; + compilationFinished = done; + /** @type {{ step: string | undefined }} */ ( + currentWatchStepModule + ).step = run.name; + copyDiff( + path.join(testDirectory, run.name), + tempDirectory, + false + ); + }, 1500); + } else { + const deprecations = deprecationTracker(); + if ( + checkArrayExpectation( + testDirectory, + { deprecations }, + "deprecation", + "Deprecation", + options, + done + ) + ) { + compiler.close(() => {}); + return; + } + compiler.close(done); + } + }, + 45000 + ); + + compilationFinished(); + } + ); + }, 300); + }, 45000); + + for (const run of runs) { + const { it: _it, getNumberOfTests } = createLazyTestEnv( + 10000, + run.name + ); + run.it = _it; + run.getNumberOfTests = getNumberOfTests; + + it(`${run.name} should allow to read stats`, (done) => { + if (run.stats) { + run.stats.toString({ all: true }); + run.stats = undefined; + } + done(); + }); + } + + // eslint-disable-next-line jest/prefer-hooks-on-top + afterAll(() => { + remove(tempDirectory); + }); + + const { + it: _it, + beforeEach: _beforeEach, + afterEach: _afterEach + } = createLazyTestEnv(10000); + }); + } + }); + } + }); +}; + +// eslint-disable-next-line jest/no-export +module.exports.describeCases = describeCases; diff --git a/test/WatchTestCases.test.js b/test/WatchTestCases.test.js deleted file mode 100644 index a1d957aacbc..00000000000 --- a/test/WatchTestCases.test.js +++ /dev/null @@ -1,361 +0,0 @@ -/* global beforeAll expect fit */ -"use strict"; - -const path = require("path"); -const fs = require("fs"); -const vm = require("vm"); -const mkdirp = require("mkdirp"); -const rimraf = require("rimraf"); -const checkArrayExpectation = require("./checkArrayExpectation"); -const { remove } = require("./helpers/remove"); - -const Stats = require("../lib/Stats"); -const webpack = require("../lib/webpack"); - -function copyDiff(src, dest, initial) { - if (!fs.existsSync(dest)) fs.mkdirSync(dest); - const files = fs.readdirSync(src); - files.forEach(filename => { - const srcFile = path.join(src, filename); - const destFile = path.join(dest, filename); - const directory = fs.statSync(srcFile).isDirectory(); - if (directory) { - copyDiff(srcFile, destFile, initial); - } else { - var content = fs.readFileSync(srcFile); - if (/^DELETE\s*$/.test(content.toString("utf-8"))) { - fs.unlinkSync(destFile); - } else { - fs.writeFileSync(destFile, content); - if (initial) { - const longTimeAgo = Date.now() - 1000 * 60 * 60 * 24; - fs.utimesSync( - destFile, - Date.now() - longTimeAgo, - Date.now() - longTimeAgo - ); - } - } - } - }); -} - -describe("WatchTestCases", () => { - if (process.env.NO_WATCH_TESTS) { - it.skip("long running tests excluded", () => {}); - return; - } - - const casesPath = path.join(__dirname, "watchCases"); - let categories = fs.readdirSync(casesPath); - - categories = categories.map(cat => { - return { - name: cat, - tests: fs - .readdirSync(path.join(casesPath, cat)) - .filter(folder => folder.indexOf("_") < 0) - .filter(testName => { - const testDirectory = path.join(casesPath, cat, testName); - const filterPath = path.join(testDirectory, "test.filter.js"); - if (fs.existsSync(filterPath) && !require(filterPath)()) { - describe.skip(testName, () => it("filtered")); - return false; - } - return true; - }) - .sort() - }; - }); - beforeAll(() => { - let dest = path.join(__dirname, "js"); - if (!fs.existsSync(dest)) fs.mkdirSync(dest); - dest = path.join(__dirname, "js", "watch-src"); - if (!fs.existsSync(dest)) fs.mkdirSync(dest); - }); - categories.forEach(category => { - beforeAll(() => { - const dest = path.join(__dirname, "js", "watch-src", category.name); - if (!fs.existsSync(dest)) fs.mkdirSync(dest); - }); - describe(category.name, () => { - category.tests.forEach(testName => { - describe(testName, () => { - const tempDirectory = path.join( - __dirname, - "js", - "watch-src", - category.name, - testName - ); - const testDirectory = path.join(casesPath, category.name, testName); - const runs = fs - .readdirSync(testDirectory) - .sort() - .filter(name => { - return fs.statSync(path.join(testDirectory, name)).isDirectory(); - }) - .map(name => ({ name })); - - beforeAll(done => { - rimraf(tempDirectory, done); - }); - - it( - testName + " should compile", - done => { - const outputDirectory = path.join( - __dirname, - "js", - "watch", - category.name, - testName - ); - - let options = {}; - const configPath = path.join(testDirectory, "webpack.config.js"); - if (fs.existsSync(configPath)) options = require(configPath); - const applyConfig = options => { - if (!options.mode) options.mode = "development"; - if (!options.context) options.context = tempDirectory; - if (!options.entry) options.entry = "./index.js"; - if (!options.target) options.target = "async-node"; - if (!options.output) options.output = {}; - if (!options.output.path) options.output.path = outputDirectory; - if (typeof options.output.pathinfo === "undefined") - options.output.pathinfo = true; - if (!options.output.filename) - options.output.filename = "bundle.js"; - }; - if (Array.isArray(options)) { - options.forEach(applyConfig); - } else { - applyConfig(options); - } - - const state = {}; - let runIdx = 0; - let waitMode = false; - let run = runs[runIdx]; - let triggeringFilename; - let lastHash = ""; - const currentWatchStepModule = require("./helpers/currentWatchStep"); - currentWatchStepModule.step = run.name; - copyDiff(path.join(testDirectory, run.name), tempDirectory, true); - - setTimeout(() => { - const compiler = webpack(options); - compiler.hooks.invalid.tap( - "WatchTestCasesTest", - (filename, mtime) => { - triggeringFilename = filename; - } - ); - const watching = compiler.watch( - { - aggregateTimeout: 1000 - }, - (err, stats) => { - if (err) return done(err); - if (!stats) - return done(new Error("No stats reported from Compiler")); - if (stats.hash === lastHash) return; - lastHash = stats.hash; - if (run.done && lastHash !== stats.hash) { - return done( - new Error( - "Compilation changed but no change was issued " + - lastHash + - " != " + - stats.hash + - " (run " + - runIdx + - ")\n" + - "Triggering change: " + - triggeringFilename - ) - ); - } - if (waitMode) return; - run.done = true; - if (err) return done(err); - const statOptions = Stats.presetToOptions("verbose"); - statOptions.colors = false; - mkdirp.sync(outputDirectory); - fs.writeFileSync( - path.join(outputDirectory, "stats.txt"), - stats.toString(statOptions), - "utf-8" - ); - const jsonStats = stats.toJson({ - errorDetails: true - }); - if ( - checkArrayExpectation( - path.join(testDirectory, run.name), - jsonStats, - "error", - "Error", - done - ) - ) - return; - if ( - checkArrayExpectation( - path.join(testDirectory, run.name), - jsonStats, - "warning", - "Warning", - done - ) - ) - return; - - const exportedTests = []; - - function _it(title, fn) { - exportedTests.push({ title, fn, timeout: 45000 }); - } - - const globalContext = { - console: console, - expect: expect - }; - - function _require(currentDirectory, module) { - if (Array.isArray(module) || /^\.\.?\//.test(module)) { - let fn; - let content; - let p; - if (Array.isArray(module)) { - p = path.join(currentDirectory, module[0]); - content = module - .map(arg => { - p = path.join(currentDirectory, arg); - return fs.readFileSync(p, "utf-8"); - }) - .join("\n"); - } else { - p = path.join(currentDirectory, module); - content = fs.readFileSync(p, "utf-8"); - } - if ( - options.target === "web" || - options.target === "webworker" - ) { - fn = vm.runInNewContext( - "(function(require, module, exports, __dirname, __filename, it, WATCH_STEP, STATS_JSON, STATE, expect, window) {" + - content + - "\n})", - globalContext, - p - ); - } else { - fn = vm.runInThisContext( - "(function(require, module, exports, __dirname, __filename, it, WATCH_STEP, STATS_JSON, STATE, expect) {" + - "global.expect = expect;" + - content + - "\n})", - p - ); - } - const m = { - exports: {} - }; - fn.call( - m.exports, - _require.bind(null, path.dirname(p)), - m, - m.exports, - path.dirname(p), - p, - _it, - run.name, - jsonStats, - state, - expect, - globalContext - ); - return module.exports; - } else if ( - testConfig.modules && - module in testConfig.modules - ) { - return testConfig.modules[module]; - } else return require.requireActual(module); - } - - let testConfig = {}; - try { - // try to load a test file - testConfig = require(path.join( - testDirectory, - "test.config.js" - )); - } catch (e) { - // empty - } - - if (testConfig.noTests) return process.nextTick(done); - _require( - outputDirectory, - testConfig.bundlePath || "./bundle.js" - ); - - if (exportedTests.length < 1) - return done(new Error("No tests exported by test case")); - - const continueStep = () => { - runIdx++; - if (runIdx < runs.length) { - run = runs[runIdx]; - waitMode = true; - setTimeout(() => { - waitMode = false; - currentWatchStepModule.step = run.name; - copyDiff( - path.join(testDirectory, run.name), - tempDirectory, - false - ); - }, 1500); - } else { - watching.close(); - - done(); - } - }; - - // Run the tests - const asyncSuite = describe(`WatchTestCases ${ - category.name - } ${testName} step ${run.name}`, () => { - exportedTests.forEach( - ({ title, fn, timeout }) => - fn - ? fit(title, fn, timeout) - : fit(title, () => {}).pend("Skipped") - ); - }); - // workaround for jest running clearSpies on the wrong suite (invoked by clearResourcesForRunnable) - asyncSuite.disabled = true; - - jasmine - .getEnv() - .execute([asyncSuite.id], asyncSuite) - .then(continueStep, done); - } - ); - }, 300); - }, - 45000 - ); - - afterAll(() => { - remove(tempDirectory); - }); - }); - }); - }); - }); -}); diff --git a/test/WatcherEvents.test.js b/test/WatcherEvents.test.js index 00fc41dbfe5..5743741494b 100644 --- a/test/WatcherEvents.test.js +++ b/test/WatcherEvents.test.js @@ -1,73 +1,97 @@ "use strict"; -/* globals describe it */ const path = require("path"); -const MemoryFs = require("memory-fs"); -const webpack = require("../"); +const { Volume, createFsFromVolume } = require("memfs"); +const webpack = require(".."); +const expectNoDeprecations = require("./helpers/expectNoDeprecations"); -const createCompiler = config => { - const compiler = webpack(config); - compiler.outputFileSystem = new MemoryFs(); +/** + * @param {import("../").Configuration | import("../").MultiConfiguration} config webpack config + * @returns {import("../").Compiler | import("../").MultiCompiler} compiler instance + */ +const createCompiler = (config) => { + const compiler = + /** @type {import("../").Compiler | import("../").MultiCompiler} */ ( + webpack( + /** @type {import("../").Configuration} */ ( + /** @type {unknown} */ (config) + ) + ) + ); + /** @type {import("../").Compiler} */ (compiler).outputFileSystem = + /** @type {import("../").OutputFileSystem} */ ( + /** @type {unknown} */ (createFsFromVolume(new Volume())) + ); return compiler; }; -const createSingleCompiler = () => { - return createCompiler({ +const createSingleCompiler = () => + createCompiler({ context: path.join(__dirname, "fixtures"), entry: "./a.js" }); -}; -const createMultiCompiler = () => { - return createCompiler([ +const createMultiCompiler = () => + createCompiler([ { context: path.join(__dirname, "fixtures"), entry: "./a.js" } ]); -}; + +expectNoDeprecations(); describe("WatcherEvents", () => { if (process.env.NO_WATCH_TESTS) { + // eslint-disable-next-line jest/no-disabled-tests it.skip("long running tests excluded", () => {}); + return; } - jest.setTimeout(10000); - - it("should emit 'watch-close' when using single-compiler mode and the compiler is not running", done => { + it("should emit 'watch-close' when using single-compiler mode and the compiler is not running", (done) => { let called = false; const compiler = createSingleCompiler(); - const watcher = compiler.watch({}, (err, stats) => { - expect(called).toBe(true); - done(err); - }); + const watcher = /** @type {import("../").Compiler} */ (compiler).watch( + {}, + (err, _stats) => { + expect(called).toBe(true); + done(err); + } + ); compiler.hooks.watchClose.tap("WatcherEventsTest", () => { called = true; }); compiler.hooks.done.tap("WatcherEventsTest", () => { - watcher.close(); + /** @type {{ close: () => void }} */ ( + /** @type {unknown} */ (watcher) + ).close(); }); }); - it("should emit 'watch-close' when using multi-compiler mode and the compiler is not running", done => { + it("should emit 'watch-close' when using multi-compiler mode and the compiler is not running", (done) => { let called = false; const compiler = createMultiCompiler(); - const watcher = compiler.watch({}, (err, stats) => { - expect(called).toBe(true); - done(err); - }); + const watcher = /** @type {import("../").MultiCompiler} */ (compiler).watch( + {}, + (err, _stats) => { + expect(called).toBe(true); + done(err); + } + ); compiler.hooks.watchClose.tap("WatcherEventsTest", () => { called = true; }); compiler.hooks.done.tap("WatcherEventsTest", () => { - watcher.close(); + /** @type {{ close: () => void }} */ ( + /** @type {unknown} */ (watcher) + ).close(); }); }); }); diff --git a/test/WebEnvironmentPlugin.unittest.js b/test/WebEnvironmentPlugin.unittest.js deleted file mode 100644 index 137956a0093..00000000000 --- a/test/WebEnvironmentPlugin.unittest.js +++ /dev/null @@ -1,23 +0,0 @@ -"use strict"; - -const WebEnvironmentPlugin = require("../lib/web/WebEnvironmentPlugin"); - -describe("WebEnvironmentPlugin", () => { - describe("apply", () => { - const WebEnvironmentPluginInstance = new WebEnvironmentPlugin( - "inputFileSystem", - "outputFileSystem" - ); - const compileSpy = { - outputFileSystem: "otherOutputFileSystem" - }; - - WebEnvironmentPluginInstance.apply(compileSpy); - - it("should set compiler.outputFileSystem information with the same as set in WebEnvironmentPlugin", () => { - expect(compileSpy.outputFileSystem).toBe( - WebEnvironmentPluginInstance.outputFileSystem - ); - }); - }); -}); diff --git a/test/WebpackCli.longtest.js b/test/WebpackCli.longtest.js new file mode 100644 index 00000000000..4f2f8d6143c --- /dev/null +++ b/test/WebpackCli.longtest.js @@ -0,0 +1,282 @@ +"use strict"; + +const fs = require("fs"); +const os = require("os"); +const path = require("path"); + +const MOCK_WEBPACK = path.resolve( + __dirname, + "fixtures/webpack-cli/mock-webpack.js" +); + +// webpack-cli requires a newer Node than webpack itself, so skip on Node versions +// it does not support (derived from webpack-cli's own `engines` field). +const [reqMajor, reqMinor = 0, reqPatch = 0] = /** @type {RegExpMatchArray} */ ( + require("webpack-cli/package.json").engines.node.match(/\d+(?:\.\d+)*/) +)[0] + .split(".") + .map(Number); + +const [major, minor, patch] = process.versions.node.split(".").map(Number); +const supportsWebpackCli = + major !== reqMajor + ? major > reqMajor + : minor !== reqMinor + ? minor > reqMinor + : patch >= reqPatch; + +// Shape captured by mock-webpack: the mock schema's synthetic flags plus the +// `name`/`mode` the defineConfig fixtures set. `pattern` is a RegExp serialized +// to a plain marker by the JSON round-trip. +/** + * @typedef {object} CapturedConfig + * @property {string=} name + * @property {string=} mode + * @property {boolean=} flag + * @property {number=} count + * @property {string=} output + * @property {{ source: string, flags: string }=} pattern + * @property {("info" | "warn")=} level + * @property {string[]=} list + * @property {boolean=} boolConst + * @property {number=} numConst + * @property {string=} whenProd + * @property {boolean=} whenDev + */ + +// Runs webpack-cli in-process against a mock webpack module injected via the +// documented WEBPACK_PACKAGE env var (webpack-cli loads webpack through a native +// import jest.mock cannot intercept). process.exit/console.error are spied so a +// validation failure surfaces as an exit code plus the captured messages. +/** + * @param {string[]} args args + * @returns {Promise<{ config: CapturedConfig, exitCode: number, errors: string }>} result + */ +const run = async (args) => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "wp-cli-")); + const capture = path.join(dir, "config.json"); + process.env.WEBPACK_PACKAGE = MOCK_WEBPACK; + process.env.WEBPACK_CLI_TEST_CAPTURE = capture; + jest.resetModules(); + + /** @type {EXPECTED_ANY} */ + const WebpackCLI = require("webpack-cli").default; + + const exitSpy = jest.spyOn(process, "exit").mockImplementation((code) => { + throw new Error(`__exit__ ${code}`); + }); + /** @type {string[]} */ + const errors = []; + const errorSpy = jest + .spyOn(console, "error") + .mockImplementation((message) => errors.push(`${message}`)); + let exitCode = 0; + try { + await new WebpackCLI().run(["node", "webpack", "build", ...args]); + } catch (error) { + const match = /__exit__ (\d+)/.exec(/** @type {Error} */ (error).message); + if (!match) throw error; + exitCode = Number(match[1]); + } finally { + exitSpy.mockRestore(); + errorSpy.mockRestore(); + delete process.env.WEBPACK_PACKAGE; + delete process.env.WEBPACK_CLI_TEST_CAPTURE; + } + const config = fs.existsSync(capture) + ? JSON.parse(fs.readFileSync(capture, "utf8")) + : undefined; + fs.rmSync(dir, { recursive: true, force: true }); + return { config, exitCode, errors: errors.join("\n") }; +}; + +describe("WebpackCLI integration", () => { + // webpack-cli requires a newer Node than webpack itself; skip on older versions. + if (!supportsWebpackCli) { + // eslint-disable-next-line jest/no-disabled-tests + it.skip("requires a Node version supported by webpack-cli", () => {}); + + return; + } + + // TODO Bun 1.3.11 hard-segfaults while running the webpack-cli child process + // (crash is at execution, not load); skip the whole suite on Bun. + if (process.versions.bun) { + // eslint-disable-next-line jest/no-disabled-tests + it.skip("segfaults under Bun", () => {}); + + return; + } + + it("parses every cli argument type into the webpack config", async () => { + const { config } = await run([ + "--flag", + "--count", + "42", + "--name", + "hello", + "--output", + "rel/p", + "--pattern", + "/ab?c/i", + "--level", + "warn", + "--list", + "a", + "--list", + "b", + "--mode", + "production" + ]); + expect(config.flag).toBe(true); // boolean + expect(config.count).toBe(42); // number + expect(config.name).toBe("hello"); // string + expect(config.output).toBe(path.resolve("rel/p")); // path + expect(config.pattern).toEqual({ source: "ab?c", flags: "i" }); // RegExp + expect(config.level).toBe("warn"); // enum + expect(config.list).toEqual(["a", "b"]); // array (multiple) + expect(config.mode).toBe("production"); // const + }); + + it("resets an array via the reset flag", async () => { + const { config } = await run(["--list-reset"]); + expect(config.list).toEqual([]); + }); + + it("collects multiple values for a multiple flag", async () => { + const { config } = await run(["--list", "a", "--list", "b", "--list", "c"]); + expect(config.list).toEqual(["a", "b", "c"]); + }); + + it("negates a boolean flag via --no-flag", async () => { + const { config } = await run(["--no-flag"]); + expect(config.flag).toBe(false); + }); + + it("accepts the other allowed enum value", async () => { + const { config } = await run(["--level", "info"]); + expect(config.level).toBe("info"); + }); + + it("treats a boolean `const` as a boolean flag", async () => { + const { config } = await run(["--bool-const"]); + expect(config.boolConst).toBe(true); + }); + + it("accepts a numeric `const` and rejects other numbers", async () => { + const ok = await run(["--num-const", "5"]); + expect(ok.config.numConst).toBe(5); + + const bad = await run(["--num-const", "6"]); + expect(bad.exitCode).toBe(2); + expect(bad.errors).toMatch( + /Invalid value '6' for the '--num-const' option/ + ); + expect(bad.errors).toMatch(/Expected: '5'/); + }); + + it("registers flags from both branches of an if/then/else schema", async () => { + const { config } = await run(["--when-prod", "hello", "--when-dev"]); + expect(config.whenProd).toBe("hello"); // from `then` + expect(config.whenDev).toBe(true); // from `else` + }); + + it("rejects an enum value outside the allowed set", async () => { + const { exitCode, errors } = await run(["--level", "nope"]); + expect(exitCode).toBe(2); + expect(errors).toMatch(/Invalid value 'nope' for the '--level' option/); + expect(errors).toMatch(/Expected: 'info \| warn'/); + }); + + it("rejects a value other than the `const`", async () => { + const { exitCode, errors } = await run(["--mode", "development"]); + expect(exitCode).toBe(2); + expect(errors).toMatch( + /Invalid value 'development' for the '--mode' option/ + ); + expect(errors).toMatch(/Expected: 'production'/); + }); + + // `webpack.defineConfig` is a runtime identity; the user config requires the + // real webpack while the CLI builds against the mock, so the capture proves + // every config shape is loaded and resolved end-to-end through webpack-cli. + const CONFIG = path.resolve(__dirname, "fixtures/webpack-cli/define-config"); + + /** + * @param {string} file fixture file name in the define-config directory + * @param {string[]=} args extra cli args prepended before --config + * @returns {Promise} the single resolved config + */ + const loadConfig = async (file, args = []) => + (await run([...args, "--config", path.join(CONFIG, file)])).config; + + /** + * @param {string} file fixture file name in the define-config directory + * @param {string[]=} args extra cli args prepended before --config + * @returns {Promise} the resolved multi-compiler config + */ + const loadMultiConfig = async (file, args) => + /** @type {CapturedConfig[]} */ ( + /** @type {unknown} */ (await loadConfig(file, args)) + ); + + it("loads a defineConfig object configuration", async () => { + const config = await loadConfig("object.js"); + expect(config.name).toBe("object"); + }); + + it("loads a defineConfig multi-compiler configuration", async () => { + const config = await loadMultiConfig("multi.js"); + expect(config.map((c) => c.name)).toEqual(["first", "second"]); + }); + + it("loads a defineConfig function configuration", async () => { + const config = await loadConfig("function.js"); + expect(config.name).toBe("function"); + }); + + it("loads a defineConfig function returning an array", async () => { + const config = await loadMultiConfig("function-multi.js"); + expect(config.map((c) => c.name)).toEqual(["first", "second"]); + }); + + it("loads a defineConfig async function configuration", async () => { + const config = await loadConfig("async-function.js"); + expect(config.name).toBe("async-function"); + }); + + it("loads a defineConfig array of functions configuration", async () => { + const config = await loadMultiConfig("array-functions.js"); + expect(config.map((c) => c.name)).toEqual(["first", "second"]); + }); + + it("loads a defineConfig promise configuration", async () => { + const config = await loadConfig("promise.js"); + expect(config.name).toBe("promise"); + }); + + it("loads a defineConfig promise returning an array", async () => { + const config = await loadMultiConfig("promise-multi.js"); + expect(config.map((c) => c.name)).toEqual(["first", "second"]); + }); + + it("threads --env and argv into a defineConfig function", async () => { + const config = await loadConfig("env-args.js", [ + "--env", + "name=demo", + "--mode", + "production" + ]); + expect(config.name).toBe("demo:production"); + }); + + it("threads --env into a defineConfig function returning an array", async () => { + const config = await loadMultiConfig("env-args-multi.js", [ + "--env", + "first=a", + "--env", + "second=b" + ]); + expect(config.map((c) => c.name)).toEqual(["a", "b"]); + }); +}); diff --git a/test/WebpackError.unittest.js b/test/WebpackError.unittest.js index cbf90fab499..076e4615714 100644 --- a/test/WebpackError.unittest.js +++ b/test/WebpackError.unittest.js @@ -1,13 +1,10 @@ "use strict"; -const path = require("path"); -const util = require("util"); - -const WebpackError = require("../lib/WebpackError"); +const WebpackError = require("../lib/errors/WebpackError"); describe("WebpackError", () => { class CustomError extends WebpackError { - constructor(message) { + constructor() { super(); this.name = "CustomError"; @@ -18,12 +15,9 @@ describe("WebpackError", () => { } } - it("Should provide inspect method for use by for util.inspect", () => { - const errorStr = util.inspect(new CustomError("Message")); - const errorArr = errorStr.split("\n"); - - expect(errorArr[0]).toBe("CustomError: CustomMessage"); - expect(errorArr[1]).toMatch(path.basename(__filename)); - expect(errorArr[errorArr.length - 1]).toBe("CustomDetails"); + it("should provide inspect method for use by for util.inspect", () => { + const error = new CustomError(); + expect(error.toString()).toContain("CustomError: CustomMessage"); + expect(error.stack).toContain(__filename); }); }); diff --git a/test/WebpackMissingModule.unittest.js b/test/WebpackMissingModule.unittest.js deleted file mode 100644 index 73ea0a790c8..00000000000 --- a/test/WebpackMissingModule.unittest.js +++ /dev/null @@ -1,33 +0,0 @@ -/* globals describe, it */ -"use strict"; - -const WebpackMissingModule = require("../lib/dependencies/WebpackMissingModule"); - -describe("WebpackMissingModule", () => { - describe("#moduleCode", () => { - it("returns an error message based on given error message", () => { - const errorMessage = WebpackMissingModule.moduleCode("mock message"); - expect(errorMessage).toBe( - "var e = new Error(\"Cannot find module 'mock message'\"); e.code = 'MODULE_NOT_FOUND'; throw e;" - ); - }); - }); - - describe("#promise", () => { - it("returns an error message based on given error message", () => { - const errorMessage = WebpackMissingModule.promise("mock message"); - expect(errorMessage).toBe( - "Promise.reject(function webpackMissingModule() { var e = new Error(\"Cannot find module 'mock message'\"); e.code = 'MODULE_NOT_FOUND'; return e; }())" - ); - }); - }); - - describe("#module", () => { - it("returns an error message based on given error message", () => { - const errorMessage = WebpackMissingModule.module("mock message"); - expect(errorMessage).toBe( - "!(function webpackMissingModule() { var e = new Error(\"Cannot find module 'mock message'\"); e.code = 'MODULE_NOT_FOUND'; throw e; }())" - ); - }); - }); -}); diff --git a/test/__snapshots__/Cli.basictest.js.snap b/test/__snapshots__/Cli.basictest.js.snap new file mode 100644 index 00000000000..00630254388 --- /dev/null +++ b/test/__snapshots__/Cli.basictest.js.snap @@ -0,0 +1,13511 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`Cli getArguments should generate flags for a schema using \`const\` 1`] = ` +Object { + "mode": Object { + "configs": Array [ + Object { + "description": "the const mode value", + "multiple": false, + "path": "mode", + "type": "enum", + "values": Array [ + "production", + ], + }, + ], + "description": "the const mode value", + "multiple": false, + "simpleType": "string", + }, +} +`; + +exports[`Cli getArguments should generate flags for both branches of an \`if/then/else\` schema 1`] = ` +Object { + "mode": Object { + "configs": Array [ + Object { + "description": "the mode", + "multiple": false, + "path": "mode", + "type": "enum", + "values": Array [ + "a", + "b", + ], + }, + ], + "description": "the mode", + "multiple": false, + "simpleType": "string", + }, + "only-when-a": Object { + "configs": Array [ + Object { + "description": "only when a", + "multiple": false, + "path": "only-when-a", + "type": "string", + }, + ], + "description": "only when a", + "multiple": false, + "simpleType": "string", + }, + "only-when-b": Object { + "configs": Array [ + Object { + "description": "only when b", + "multiple": false, + "path": "only-when-b", + "type": "boolean", + }, + ], + "description": "only when b", + "multiple": false, + "simpleType": "boolean", + }, +} +`; + +exports[`Cli getArguments should generate the correct cli flags 1`] = ` +Object { + "amd": Object { + "configs": Array [ + Object { + "description": "You can pass \`false\` to disable AMD support.", + "multiple": false, + "path": "amd", + "type": "enum", + "values": Array [ + false, + ], + }, + ], + "description": "You can pass \`false\` to disable AMD support.", + "multiple": false, + "simpleType": "boolean", + }, + "bail": Object { + "configs": Array [ + Object { + "description": "Report the first error as a hard error instead of tolerating it.", + "multiple": false, + "path": "bail", + "type": "boolean", + }, + ], + "description": "Report the first error as a hard error instead of tolerating it.", + "multiple": false, + "simpleType": "boolean", + }, + "cache": Object { + "configs": Array [ + Object { + "description": "Enable in memory caching.", + "multiple": false, + "path": "cache", + "type": "enum", + "values": Array [ + true, + ], + }, + Object { + "description": "Disable caching.", + "multiple": false, + "path": "cache", + "type": "enum", + "values": Array [ + false, + ], + }, + ], + "description": "Enable in memory caching. Disable caching.", + "multiple": false, + "simpleType": "boolean", + }, + "cache-allow-collecting-memory": Object { + "configs": Array [ + Object { + "description": "Allows to collect unused memory allocated during deserialization. This requires copying data into smaller buffers and has a performance cost.", + "multiple": false, + "path": "cache.allowCollectingMemory", + "type": "boolean", + }, + ], + "description": "Allows to collect unused memory allocated during deserialization. This requires copying data into smaller buffers and has a performance cost.", + "multiple": false, + "simpleType": "boolean", + }, + "cache-cache-directory": Object { + "configs": Array [ + Object { + "description": "Base directory for the cache (defaults to node_modules/.cache/webpack).", + "multiple": false, + "path": "cache.cacheDirectory", + "type": "path", + }, + ], + "description": "Base directory for the cache (defaults to node_modules/.cache/webpack).", + "multiple": false, + "simpleType": "string", + }, + "cache-cache-location": Object { + "configs": Array [ + Object { + "description": "Locations for the cache (defaults to cacheDirectory / name).", + "multiple": false, + "path": "cache.cacheLocation", + "type": "path", + }, + ], + "description": "Locations for the cache (defaults to cacheDirectory / name).", + "multiple": false, + "simpleType": "string", + }, + "cache-cache-unaffected": Object { + "configs": Array [ + Object { + "description": "Additionally cache computation of modules that are unchanged and reference only unchanged modules.", + "multiple": false, + "path": "cache.cacheUnaffected", + "type": "boolean", + }, + ], + "description": "Additionally cache computation of modules that are unchanged and reference only unchanged modules.", + "multiple": false, + "simpleType": "boolean", + }, + "cache-compression": Object { + "configs": Array [ + Object { + "description": "Compression type used for the cache files.", + "multiple": false, + "path": "cache.compression", + "type": "enum", + "values": Array [ + false, + "gzip", + "brotli", + ], + }, + ], + "description": "Compression type used for the cache files.", + "multiple": false, + "simpleType": "string", + }, + "cache-hash-algorithm": Object { + "configs": Array [ + Object { + "description": "Algorithm used for generation the hash (see node.js crypto package).", + "multiple": false, + "path": "cache.hashAlgorithm", + "type": "string", + }, + ], + "description": "Algorithm used for generation the hash (see node.js crypto package).", + "multiple": false, + "simpleType": "string", + }, + "cache-idle-timeout": Object { + "configs": Array [ + Object { + "description": "Time in ms after which idle period the cache storing should happen.", + "multiple": false, + "path": "cache.idleTimeout", + "type": "number", + }, + ], + "description": "Time in ms after which idle period the cache storing should happen.", + "multiple": false, + "simpleType": "number", + }, + "cache-idle-timeout-after-large-changes": Object { + "configs": Array [ + Object { + "description": "Time in ms after which idle period the cache storing should happen when larger changes has been detected (cumulative build time > 2 x avg cache store time).", + "multiple": false, + "path": "cache.idleTimeoutAfterLargeChanges", + "type": "number", + }, + ], + "description": "Time in ms after which idle period the cache storing should happen when larger changes has been detected (cumulative build time > 2 x avg cache store time).", + "multiple": false, + "simpleType": "number", + }, + "cache-idle-timeout-for-initial-store": Object { + "configs": Array [ + Object { + "description": "Time in ms after which idle period the initial cache storing should happen.", + "multiple": false, + "path": "cache.idleTimeoutForInitialStore", + "type": "number", + }, + ], + "description": "Time in ms after which idle period the initial cache storing should happen.", + "multiple": false, + "simpleType": "number", + }, + "cache-immutable-paths": Object { + "configs": Array [ + Object { + "description": "A RegExp matching an immutable directory (usually a package manager cache directory, including the tailing slash)", + "multiple": true, + "path": "cache.immutablePaths[]", + "type": "RegExp", + }, + Object { + "description": "A path to an immutable directory (usually a package manager cache directory).", + "multiple": true, + "path": "cache.immutablePaths[]", + "type": "path", + }, + ], + "description": "A RegExp matching an immutable directory (usually a package manager cache directory, including the tailing slash) A path to an immutable directory (usually a package manager cache directory).", + "multiple": true, + "simpleType": "string", + }, + "cache-immutable-paths-reset": Object { + "configs": Array [ + Object { + "description": "Clear all items provided in 'cache.immutablePaths' configuration. List of paths that are managed by a package manager and contain a version or hash in its path so all files are immutable.", + "multiple": false, + "path": "cache.immutablePaths", + "type": "reset", + }, + ], + "description": "Clear all items provided in 'cache.immutablePaths' configuration. List of paths that are managed by a package manager and contain a version or hash in its path so all files are immutable.", + "multiple": false, + "simpleType": "boolean", + }, + "cache-managed-paths": Object { + "configs": Array [ + Object { + "description": "A RegExp matching a managed directory (usually a node_modules directory, including the tailing slash)", + "multiple": true, + "path": "cache.managedPaths[]", + "type": "RegExp", + }, + Object { + "description": "A path to a managed directory (usually a node_modules directory).", + "multiple": true, + "path": "cache.managedPaths[]", + "type": "path", + }, + ], + "description": "A RegExp matching a managed directory (usually a node_modules directory, including the tailing slash) A path to a managed directory (usually a node_modules directory).", + "multiple": true, + "simpleType": "string", + }, + "cache-managed-paths-reset": Object { + "configs": Array [ + Object { + "description": "Clear all items provided in 'cache.managedPaths' configuration. List of paths that are managed by a package manager and can be trusted to not be modified otherwise.", + "multiple": false, + "path": "cache.managedPaths", + "type": "reset", + }, + ], + "description": "Clear all items provided in 'cache.managedPaths' configuration. List of paths that are managed by a package manager and can be trusted to not be modified otherwise.", + "multiple": false, + "simpleType": "boolean", + }, + "cache-max-age": Object { + "configs": Array [ + Object { + "description": "Time for which unused cache entries stay in the filesystem cache at minimum (in milliseconds).", + "multiple": false, + "path": "cache.maxAge", + "type": "number", + }, + ], + "description": "Time for which unused cache entries stay in the filesystem cache at minimum (in milliseconds).", + "multiple": false, + "simpleType": "number", + }, + "cache-max-generations": Object { + "configs": Array [ + Object { + "description": "Number of generations unused cache entries stay in memory cache at minimum (1 = may be removed after unused for a single compilation, ..., Infinity: kept forever).", + "multiple": false, + "path": "cache.maxGenerations", + "type": "number", + }, + ], + "description": "Number of generations unused cache entries stay in memory cache at minimum (1 = may be removed after unused for a single compilation, ..., Infinity: kept forever).", + "multiple": false, + "simpleType": "number", + }, + "cache-max-memory-generations": Object { + "configs": Array [ + Object { + "description": "Number of generations unused cache entries stay in memory cache at minimum (0 = no memory cache used, 1 = may be removed after unused for a single compilation, ..., Infinity: kept forever). Cache entries will be deserialized from disk when removed from memory cache.", + "multiple": false, + "path": "cache.maxMemoryGenerations", + "type": "number", + }, + ], + "description": "Number of generations unused cache entries stay in memory cache at minimum (0 = no memory cache used, 1 = may be removed after unused for a single compilation, ..., Infinity: kept forever). Cache entries will be deserialized from disk when removed from memory cache.", + "multiple": false, + "simpleType": "number", + }, + "cache-memory-cache-unaffected": Object { + "configs": Array [ + Object { + "description": "Additionally cache computation of modules that are unchanged and reference only unchanged modules in memory.", + "multiple": false, + "path": "cache.memoryCacheUnaffected", + "type": "boolean", + }, + ], + "description": "Additionally cache computation of modules that are unchanged and reference only unchanged modules in memory.", + "multiple": false, + "simpleType": "boolean", + }, + "cache-name": Object { + "configs": Array [ + Object { + "description": "Name for the cache. Different names will lead to different coexisting caches.", + "multiple": false, + "path": "cache.name", + "type": "string", + }, + ], + "description": "Name for the cache. Different names will lead to different coexisting caches.", + "multiple": false, + "simpleType": "string", + }, + "cache-profile": Object { + "configs": Array [ + Object { + "description": "Track and log detailed timing information for individual cache items.", + "multiple": false, + "path": "cache.profile", + "type": "boolean", + }, + ], + "description": "Track and log detailed timing information for individual cache items.", + "multiple": false, + "simpleType": "boolean", + }, + "cache-readonly": Object { + "configs": Array [ + Object { + "description": "Enable/disable readonly mode.", + "multiple": false, + "path": "cache.readonly", + "type": "boolean", + }, + ], + "description": "Enable/disable readonly mode.", + "multiple": false, + "simpleType": "boolean", + }, + "cache-store": Object { + "configs": Array [ + Object { + "description": "When to store data to the filesystem. (pack: Store data when compiler is idle in a single file).", + "multiple": false, + "path": "cache.store", + "type": "enum", + "values": Array [ + "pack", + ], + }, + ], + "description": "When to store data to the filesystem. (pack: Store data when compiler is idle in a single file).", + "multiple": false, + "simpleType": "string", + }, + "cache-type": Object { + "configs": Array [ + Object { + "description": "In memory caching.", + "multiple": false, + "path": "cache.type", + "type": "enum", + "values": Array [ + "memory", + ], + }, + Object { + "description": "Filesystem caching.", + "multiple": false, + "path": "cache.type", + "type": "enum", + "values": Array [ + "filesystem", + ], + }, + ], + "description": "In memory caching. Filesystem caching.", + "multiple": false, + "simpleType": "string", + }, + "cache-version": Object { + "configs": Array [ + Object { + "description": "Version of the cache data. Different versions won't allow to reuse the cache and override existing content. Update the version when config changed in a way which doesn't allow to reuse cache. This will invalidate the cache.", + "multiple": false, + "path": "cache.version", + "type": "string", + }, + ], + "description": "Version of the cache data. Different versions won't allow to reuse the cache and override existing content. Update the version when config changed in a way which doesn't allow to reuse cache. This will invalidate the cache.", + "multiple": false, + "simpleType": "string", + }, + "context": Object { + "configs": Array [ + Object { + "description": "The base directory (absolute path!) for resolving the \`entry\` option. If \`output.pathinfo\` is set, the included pathinfo is shortened to this directory.", + "multiple": false, + "path": "context", + "type": "path", + }, + ], + "description": "The base directory (absolute path!) for resolving the \`entry\` option. If \`output.pathinfo\` is set, the included pathinfo is shortened to this directory.", + "multiple": false, + "simpleType": "string", + }, + "dependencies": Object { + "configs": Array [ + Object { + "description": "References to another configuration to depend on.", + "multiple": true, + "path": "dependencies[]", + "type": "string", + }, + ], + "description": "References to another configuration to depend on.", + "multiple": true, + "simpleType": "string", + }, + "dependencies-reset": Object { + "configs": Array [ + Object { + "description": "Clear all items provided in 'dependencies' configuration. References to other configurations to depend on.", + "multiple": false, + "path": "dependencies", + "type": "reset", + }, + ], + "description": "Clear all items provided in 'dependencies' configuration. References to other configurations to depend on.", + "multiple": false, + "simpleType": "boolean", + }, + "dev-server": Object { + "configs": Array [ + Object { + "description": "Disable dev server.", + "multiple": false, + "path": "devServer", + "type": "enum", + "values": Array [ + false, + ], + }, + ], + "description": "Disable dev server.", + "multiple": false, + "simpleType": "boolean", + }, + "devtool": Object { + "configs": Array [ + Object { + "description": "A developer tool to enhance debugging (false | eval | [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map).", + "multiple": false, + "path": "devtool", + "type": "enum", + "values": Array [ + false, + "eval", + ], + }, + Object { + "description": "A developer tool to enhance debugging (false | eval | [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map).", + "multiple": false, + "path": "devtool", + "type": "string", + }, + ], + "description": "A developer tool to enhance debugging (false | eval | [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map).", + "multiple": false, + "simpleType": "string", + }, + "devtool-reset": Object { + "configs": Array [ + Object { + "description": "Clear all items provided in 'devtool' configuration. A developer tool to enhance debugging (false | eval | [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map).", + "multiple": false, + "path": "devtool", + "type": "reset", + }, + ], + "description": "Clear all items provided in 'devtool' configuration. A developer tool to enhance debugging (false | eval | [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map).", + "multiple": false, + "simpleType": "boolean", + }, + "devtool-type": Object { + "configs": Array [ + Object { + "description": "Which asset type should receive this devtool value.", + "multiple": true, + "path": "devtool[].type", + "type": "enum", + "values": Array [ + "all", + "javascript", + "css", + ], + }, + ], + "description": "Which asset type should receive this devtool value.", + "multiple": true, + "simpleType": "string", + }, + "devtool-use": Object { + "configs": Array [ + Object { + "description": "A developer tool to enhance debugging (false | eval | [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map).", + "multiple": true, + "path": "devtool[].use", + "type": "enum", + "values": Array [ + false, + "eval", + ], + }, + Object { + "description": "A developer tool to enhance debugging (false | eval | [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map).", + "multiple": true, + "path": "devtool[].use", + "type": "string", + }, + ], + "description": "A developer tool to enhance debugging (false | eval | [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map).", + "multiple": true, + "simpleType": "string", + }, + "dotenv": Object { + "configs": Array [ + Object { + "description": "Enable Dotenv plugin with default options.", + "multiple": false, + "path": "dotenv", + "type": "boolean", + }, + ], + "description": "Enable Dotenv plugin with default options.", + "multiple": false, + "simpleType": "boolean", + }, + "dotenv-dir": Object { + "configs": Array [ + Object { + "description": "The directory from which .env files are loaded. Can be an absolute path, false will disable the .env file loading.", + "multiple": false, + "path": "dotenv.dir", + "type": "enum", + "values": Array [ + false, + ], + }, + Object { + "description": "The directory from which .env files are loaded. Can be an absolute path, false will disable the .env file loading.", + "multiple": false, + "path": "dotenv.dir", + "type": "path", + }, + ], + "description": "The directory from which .env files are loaded. Can be an absolute path, false will disable the .env file loading.", + "multiple": false, + "simpleType": "string", + }, + "dotenv-prefix": Object { + "configs": Array [ + Object { + "description": "A prefix that environment variables must start with to be exposed.", + "multiple": true, + "path": "dotenv.prefix[]", + "type": "string", + }, + ], + "description": "A prefix that environment variables must start with to be exposed.", + "multiple": true, + "simpleType": "string", + }, + "dotenv-prefix-reset": Object { + "configs": Array [ + Object { + "description": "Clear all items provided in 'dotenv.prefix' configuration. Only expose environment variables that start with these prefixes. Defaults to 'WEBPACK_'.", + "multiple": false, + "path": "dotenv.prefix", + "type": "reset", + }, + ], + "description": "Clear all items provided in 'dotenv.prefix' configuration. Only expose environment variables that start with these prefixes. Defaults to 'WEBPACK_'.", + "multiple": false, + "simpleType": "boolean", + }, + "dotenv-template": Object { + "configs": Array [ + Object { + "description": "A template pattern for .env file names.", + "multiple": true, + "path": "dotenv.template[]", + "type": "string", + }, + ], + "description": "A template pattern for .env file names.", + "multiple": true, + "simpleType": "string", + }, + "dotenv-template-reset": Object { + "configs": Array [ + Object { + "description": "Clear all items provided in 'dotenv.template' configuration. Template patterns for .env file names. Use [mode] as placeholder for the webpack mode. Defaults to ['.env', '.env.local', '.env.[mode]', '.env.[mode].local'].", + "multiple": false, + "path": "dotenv.template", + "type": "reset", + }, + ], + "description": "Clear all items provided in 'dotenv.template' configuration. Template patterns for .env file names. Use [mode] as placeholder for the webpack mode. Defaults to ['.env', '.env.local', '.env.[mode]', '.env.[mode].local'].", + "multiple": false, + "simpleType": "boolean", + }, + "entry": Object { + "configs": Array [ + Object { + "description": "A module that is loaded upon startup. Only the last one is exported.", + "multiple": true, + "path": "entry[]", + "type": "string", + }, + ], + "description": "A module that is loaded upon startup. Only the last one is exported.", + "multiple": true, + "simpleType": "string", + }, + "entry-reset": Object { + "configs": Array [ + Object { + "description": "Clear all items provided in 'entry' configuration. All modules are loaded upon startup. The last one is exported.", + "multiple": false, + "path": "entry", + "type": "reset", + }, + ], + "description": "Clear all items provided in 'entry' configuration. All modules are loaded upon startup. The last one is exported.", + "multiple": false, + "simpleType": "boolean", + }, + "experiments-async-web-assembly": Object { + "configs": Array [ + Object { + "description": "Support WebAssembly as asynchronous EcmaScript Module.", + "multiple": false, + "path": "experiments.asyncWebAssembly", + "type": "boolean", + }, + ], + "description": "Support WebAssembly as asynchronous EcmaScript Module.", + "multiple": false, + "simpleType": "boolean", + }, + "experiments-back-compat": Object { + "configs": Array [ + Object { + "description": "Enable backward-compat layer with deprecation warnings for many webpack 4 APIs.", + "multiple": false, + "path": "experiments.backCompat", + "type": "boolean", + }, + ], + "description": "Enable backward-compat layer with deprecation warnings for many webpack 4 APIs.", + "multiple": false, + "simpleType": "boolean", + }, + "experiments-build-http-allowed-uris": Object { + "configs": Array [ + Object { + "description": "Allowed URI pattern.", + "multiple": true, + "path": "experiments.buildHttp.allowedUris[]", + "type": "RegExp", + }, + Object { + "description": "Allowed URI (resp. the beginning of it).", + "multiple": true, + "path": "experiments.buildHttp.allowedUris[]", + "type": "string", + }, + ], + "description": "Allowed URI pattern. Allowed URI (resp. the beginning of it).", + "multiple": true, + "simpleType": "string", + }, + "experiments-build-http-allowed-uris-reset": Object { + "configs": Array [ + Object { + "description": "Clear all items provided in 'experiments.buildHttp.allowedUris' configuration. List of allowed URIs (resp. the beginning of them).", + "multiple": false, + "path": "experiments.buildHttp.allowedUris", + "type": "reset", + }, + ], + "description": "Clear all items provided in 'experiments.buildHttp.allowedUris' configuration. List of allowed URIs (resp. the beginning of them).", + "multiple": false, + "simpleType": "boolean", + }, + "experiments-build-http-cache-location": Object { + "configs": Array [ + Object { + "description": "Location where resource content is stored for lockfile entries. It's also possible to disable storing by passing false.", + "multiple": false, + "path": "experiments.buildHttp.cacheLocation", + "type": "enum", + "values": Array [ + false, + ], + }, + Object { + "description": "Location where resource content is stored for lockfile entries. It's also possible to disable storing by passing false.", + "multiple": false, + "path": "experiments.buildHttp.cacheLocation", + "type": "path", + }, + ], + "description": "Location where resource content is stored for lockfile entries. It's also possible to disable storing by passing false.", + "multiple": false, + "simpleType": "string", + }, + "experiments-build-http-frozen": Object { + "configs": Array [ + Object { + "description": "When set, anything that would lead to a modification of the lockfile or any resource content, will result in an error.", + "multiple": false, + "path": "experiments.buildHttp.frozen", + "type": "boolean", + }, + ], + "description": "When set, anything that would lead to a modification of the lockfile or any resource content, will result in an error.", + "multiple": false, + "simpleType": "boolean", + }, + "experiments-build-http-lockfile-location": Object { + "configs": Array [ + Object { + "description": "Location of the lockfile.", + "multiple": false, + "path": "experiments.buildHttp.lockfileLocation", + "type": "path", + }, + ], + "description": "Location of the lockfile.", + "multiple": false, + "simpleType": "string", + }, + "experiments-build-http-proxy": Object { + "configs": Array [ + Object { + "description": "Proxy configuration, which can be used to specify a proxy server to use for HTTP requests.", + "multiple": false, + "path": "experiments.buildHttp.proxy", + "type": "string", + }, + ], + "description": "Proxy configuration, which can be used to specify a proxy server to use for HTTP requests.", + "multiple": false, + "simpleType": "string", + }, + "experiments-build-http-upgrade": Object { + "configs": Array [ + Object { + "description": "When set, resources of existing lockfile entries will be fetched and entries will be upgraded when resource content has changed.", + "multiple": false, + "path": "experiments.buildHttp.upgrade", + "type": "boolean", + }, + ], + "description": "When set, resources of existing lockfile entries will be fetched and entries will be upgraded when resource content has changed.", + "multiple": false, + "simpleType": "boolean", + }, + "experiments-cache-unaffected": Object { + "configs": Array [ + Object { + "description": "Enable additional in memory caching of modules that are unchanged and reference only unchanged modules.", + "multiple": false, + "path": "experiments.cacheUnaffected", + "type": "boolean", + }, + ], + "description": "Enable additional in memory caching of modules that are unchanged and reference only unchanged modules.", + "multiple": false, + "simpleType": "boolean", + }, + "experiments-css": Object { + "configs": Array [ + Object { + "description": "Enable css support.", + "multiple": false, + "path": "experiments.css", + "type": "boolean", + }, + ], + "description": "Enable css support.", + "multiple": false, + "simpleType": "boolean", + }, + "experiments-defer-import": Object { + "configs": Array [ + Object { + "description": "Enable experimental tc39 proposal https://github.com/tc39/proposal-defer-import-eval. This allows to defer execution of a module until it's first use.", + "multiple": false, + "path": "experiments.deferImport", + "type": "boolean", + }, + ], + "description": "Enable experimental tc39 proposal https://github.com/tc39/proposal-defer-import-eval. This allows to defer execution of a module until it's first use.", + "multiple": false, + "simpleType": "boolean", + }, + "experiments-future-defaults": Object { + "configs": Array [ + Object { + "description": "Apply defaults of next major version.", + "multiple": false, + "path": "experiments.futureDefaults", + "type": "boolean", + }, + ], + "description": "Apply defaults of next major version.", + "multiple": false, + "simpleType": "boolean", + }, + "experiments-html": Object { + "configs": Array [ + Object { + "description": "Enable HTML entry support. Treats \`.html\` files as a first-class module type so they can be used directly as entry points.", + "multiple": false, + "path": "experiments.html", + "type": "boolean", + }, + ], + "description": "Enable HTML entry support. Treats \`.html\` files as a first-class module type so they can be used directly as entry points.", + "multiple": false, + "simpleType": "boolean", + }, + "experiments-lazy-compilation": Object { + "configs": Array [ + Object { + "description": "Compile entrypoints and import()s only when they are accessed.", + "multiple": false, + "path": "experiments.lazyCompilation", + "type": "boolean", + }, + ], + "description": "Compile entrypoints and import()s only when they are accessed.", + "multiple": false, + "simpleType": "boolean", + }, + "experiments-lazy-compilation-backend-client": Object { + "configs": Array [ + Object { + "description": "A custom client.", + "multiple": false, + "path": "experiments.lazyCompilation.backend.client", + "type": "string", + }, + ], + "description": "A custom client.", + "multiple": false, + "simpleType": "string", + }, + "experiments-lazy-compilation-backend-listen": Object { + "configs": Array [ + Object { + "description": "A port.", + "multiple": false, + "path": "experiments.lazyCompilation.backend.listen", + "type": "number", + }, + ], + "description": "A port.", + "multiple": false, + "simpleType": "number", + }, + "experiments-lazy-compilation-backend-listen-host": Object { + "configs": Array [ + Object { + "description": "A host.", + "multiple": false, + "path": "experiments.lazyCompilation.backend.listen.host", + "type": "string", + }, + ], + "description": "A host.", + "multiple": false, + "simpleType": "string", + }, + "experiments-lazy-compilation-backend-listen-port": Object { + "configs": Array [ + Object { + "description": "A port.", + "multiple": false, + "path": "experiments.lazyCompilation.backend.listen.port", + "type": "number", + }, + ], + "description": "A port.", + "multiple": false, + "simpleType": "number", + }, + "experiments-lazy-compilation-backend-protocol": Object { + "configs": Array [ + Object { + "description": "Specifies the protocol the client should use to connect to the server.", + "multiple": false, + "path": "experiments.lazyCompilation.backend.protocol", + "type": "enum", + "values": Array [ + "http", + "https", + ], + }, + ], + "description": "Specifies the protocol the client should use to connect to the server.", + "multiple": false, + "simpleType": "string", + }, + "experiments-lazy-compilation-entries": Object { + "configs": Array [ + Object { + "description": "Enable/disable lazy compilation for entries.", + "multiple": false, + "path": "experiments.lazyCompilation.entries", + "type": "boolean", + }, + ], + "description": "Enable/disable lazy compilation for entries.", + "multiple": false, + "simpleType": "boolean", + }, + "experiments-lazy-compilation-imports": Object { + "configs": Array [ + Object { + "description": "Enable/disable lazy compilation for import() modules.", + "multiple": false, + "path": "experiments.lazyCompilation.imports", + "type": "boolean", + }, + ], + "description": "Enable/disable lazy compilation for import() modules.", + "multiple": false, + "simpleType": "boolean", + }, + "experiments-lazy-compilation-test": Object { + "configs": Array [ + Object { + "description": "Specify which entrypoints or import()ed modules should be lazily compiled. This is matched with the imported module and not the entrypoint name.", + "multiple": false, + "path": "experiments.lazyCompilation.test", + "type": "RegExp", + }, + Object { + "description": "Specify which entrypoints or import()ed modules should be lazily compiled. This is matched with the imported module and not the entrypoint name.", + "multiple": false, + "path": "experiments.lazyCompilation.test", + "type": "string", + }, + ], + "description": "Specify which entrypoints or import()ed modules should be lazily compiled. This is matched with the imported module and not the entrypoint name.", + "multiple": false, + "simpleType": "string", + }, + "experiments-output-module": Object { + "configs": Array [ + Object { + "description": "Allow output javascript files as module source type.", + "multiple": false, + "path": "experiments.outputModule", + "type": "boolean", + }, + ], + "description": "Allow output javascript files as module source type.", + "multiple": false, + "simpleType": "boolean", + }, + "experiments-source-import": Object { + "configs": Array [ + Object { + "description": "Enable experimental tc39 proposal https://github.com/tc39/proposal-source-phase-imports. This allows importing modules at source phase.", + "multiple": false, + "path": "experiments.sourceImport", + "type": "boolean", + }, + ], + "description": "Enable experimental tc39 proposal https://github.com/tc39/proposal-source-phase-imports. This allows importing modules at source phase.", + "multiple": false, + "simpleType": "boolean", + }, + "experiments-sync-web-assembly": Object { + "configs": Array [ + Object { + "description": "Support WebAssembly as synchronous EcmaScript Module (outdated).", + "multiple": false, + "path": "experiments.syncWebAssembly", + "type": "boolean", + }, + ], + "description": "Support WebAssembly as synchronous EcmaScript Module (outdated).", + "multiple": false, + "simpleType": "boolean", + }, + "experiments-typescript": Object { + "configs": Array [ + Object { + "description": "Enable typescript support.", + "multiple": false, + "path": "experiments.typescript", + "type": "boolean", + }, + ], + "description": "Enable typescript support.", + "multiple": false, + "simpleType": "boolean", + }, + "extends": Object { + "configs": Array [ + Object { + "description": "Path to the configuration to be extended (only works when using webpack-cli).", + "multiple": true, + "path": "extends[]", + "type": "string", + }, + ], + "description": "Path to the configuration to be extended (only works when using webpack-cli).", + "multiple": true, + "simpleType": "string", + }, + "extends-reset": Object { + "configs": Array [ + Object { + "description": "Clear all items provided in 'extends' configuration. Extend configuration from another configuration (only works when using webpack-cli).", + "multiple": false, + "path": "extends", + "type": "reset", + }, + ], + "description": "Clear all items provided in 'extends' configuration. Extend configuration from another configuration (only works when using webpack-cli).", + "multiple": false, + "simpleType": "boolean", + }, + "externals": Object { + "configs": Array [ + Object { + "description": "Every matched dependency becomes external.", + "multiple": true, + "path": "externals[]", + "type": "RegExp", + }, + Object { + "description": "An exact matched dependency becomes external. The same string is used as external dependency.", + "multiple": true, + "path": "externals[]", + "type": "string", + }, + ], + "description": "Every matched dependency becomes external. An exact matched dependency becomes external. The same string is used as external dependency.", + "multiple": true, + "simpleType": "string", + }, + "externals-presets-bun": Object { + "configs": Array [ + Object { + "description": "Treat bun built-in modules like 'bun', 'bun:sqlite' or 'bun:ffi' and node.js built-in modules as external and load them via import when used (for the Bun runtime).", + "multiple": false, + "path": "externalsPresets.bun", + "type": "boolean", + }, + ], + "description": "Treat bun built-in modules like 'bun', 'bun:sqlite' or 'bun:ffi' and node.js built-in modules as external and load them via import when used (for the Bun runtime).", + "multiple": false, + "simpleType": "boolean", + }, + "externals-presets-deno": Object { + "configs": Array [ + Object { + "description": "Treat node.js built-in modules like fs, path or vm as external and load them via the required 'node:' specifier when used (for the Deno runtime).", + "multiple": false, + "path": "externalsPresets.deno", + "type": "boolean", + }, + ], + "description": "Treat node.js built-in modules like fs, path or vm as external and load them via the required 'node:' specifier when used (for the Deno runtime).", + "multiple": false, + "simpleType": "boolean", + }, + "externals-presets-electron": Object { + "configs": Array [ + Object { + "description": "Treat common electron built-in modules in main and preload context like 'electron', 'ipc' or 'shell' as external and load them via require() when used.", + "multiple": false, + "path": "externalsPresets.electron", + "type": "boolean", + }, + ], + "description": "Treat common electron built-in modules in main and preload context like 'electron', 'ipc' or 'shell' as external and load them via require() when used.", + "multiple": false, + "simpleType": "boolean", + }, + "externals-presets-electron-main": Object { + "configs": Array [ + Object { + "description": "Treat electron built-in modules in the main context like 'app', 'ipc-main' or 'shell' as external and load them via require() when used.", + "multiple": false, + "path": "externalsPresets.electronMain", + "type": "boolean", + }, + ], + "description": "Treat electron built-in modules in the main context like 'app', 'ipc-main' or 'shell' as external and load them via require() when used.", + "multiple": false, + "simpleType": "boolean", + }, + "externals-presets-electron-preload": Object { + "configs": Array [ + Object { + "description": "Treat electron built-in modules in the preload context like 'web-frame', 'ipc-renderer' or 'shell' as external and load them via require() when used.", + "multiple": false, + "path": "externalsPresets.electronPreload", + "type": "boolean", + }, + ], + "description": "Treat electron built-in modules in the preload context like 'web-frame', 'ipc-renderer' or 'shell' as external and load them via require() when used.", + "multiple": false, + "simpleType": "boolean", + }, + "externals-presets-electron-renderer": Object { + "configs": Array [ + Object { + "description": "Treat electron built-in modules in the renderer context like 'web-frame', 'ipc-renderer' or 'shell' as external and load them via require() when used.", + "multiple": false, + "path": "externalsPresets.electronRenderer", + "type": "boolean", + }, + ], + "description": "Treat electron built-in modules in the renderer context like 'web-frame', 'ipc-renderer' or 'shell' as external and load them via require() when used.", + "multiple": false, + "simpleType": "boolean", + }, + "externals-presets-node": Object { + "configs": Array [ + Object { + "description": "Treat node.js built-in modules like fs, path or vm as external and load them via require() when used.", + "multiple": false, + "path": "externalsPresets.node", + "type": "boolean", + }, + ], + "description": "Treat node.js built-in modules like fs, path or vm as external and load them via require() when used.", + "multiple": false, + "simpleType": "boolean", + }, + "externals-presets-nwjs": Object { + "configs": Array [ + Object { + "description": "Treat NW.js legacy nw.gui module as external and load it via require() when used.", + "multiple": false, + "path": "externalsPresets.nwjs", + "type": "boolean", + }, + ], + "description": "Treat NW.js legacy nw.gui module as external and load it via require() when used.", + "multiple": false, + "simpleType": "boolean", + }, + "externals-presets-web": Object { + "configs": Array [ + Object { + "description": "Treat references to 'http(s)://...' and 'std:...' as external and load them via import when used (Note that this changes execution order as externals are executed before any other code in the chunk).", + "multiple": false, + "path": "externalsPresets.web", + "type": "boolean", + }, + ], + "description": "Treat references to 'http(s)://...' and 'std:...' as external and load them via import when used (Note that this changes execution order as externals are executed before any other code in the chunk).", + "multiple": false, + "simpleType": "boolean", + }, + "externals-presets-web-async": Object { + "configs": Array [ + Object { + "description": "Treat references to 'http(s)://...' and 'std:...' as external and load them via async import() when used (Note that this external type is an async module, which has various effects on the execution).", + "multiple": false, + "path": "externalsPresets.webAsync", + "type": "boolean", + }, + ], + "description": "Treat references to 'http(s)://...' and 'std:...' as external and load them via async import() when used (Note that this external type is an async module, which has various effects on the execution).", + "multiple": false, + "simpleType": "boolean", + }, + "externals-reset": Object { + "configs": Array [ + Object { + "description": "Clear all items provided in 'externals' configuration. Specify dependencies that shouldn't be resolved by webpack, but should become dependencies of the resulting bundle. The kind of the dependency depends on \`output.libraryTarget\`.", + "multiple": false, + "path": "externals", + "type": "reset", + }, + ], + "description": "Clear all items provided in 'externals' configuration. Specify dependencies that shouldn't be resolved by webpack, but should become dependencies of the resulting bundle. The kind of the dependency depends on \`output.libraryTarget\`.", + "multiple": false, + "simpleType": "boolean", + }, + "externals-type": Object { + "configs": Array [ + Object { + "description": "Specifies the default type of externals ('amd*', 'umd*', 'system' and 'jsonp' depend on output.libraryTarget set to the same value).", + "multiple": false, + "path": "externalsType", + "type": "enum", + "values": Array [ + "var", + "module", + "assign", + "this", + "window", + "self", + "global", + "commonjs", + "commonjs2", + "commonjs-module", + "commonjs-static", + "amd", + "amd-require", + "umd", + "umd2", + "jsonp", + "system", + "promise", + "import", + "module-import", + "script", + "node-commonjs", + "asset", + "asset-url", + "css-import", + "css-url", + ], + }, + ], + "description": "Specifies the default type of externals ('amd*', 'umd*', 'system' and 'jsonp' depend on output.libraryTarget set to the same value).", + "multiple": false, + "simpleType": "string", + }, + "ignore-warnings": Object { + "configs": Array [ + Object { + "description": "A RegExp to select the warning message.", + "multiple": true, + "path": "ignoreWarnings[]", + "type": "RegExp", + }, + ], + "description": "A RegExp to select the warning message.", + "multiple": true, + "simpleType": "string", + }, + "ignore-warnings-file": Object { + "configs": Array [ + Object { + "description": "A RegExp to select the origin file for the warning.", + "multiple": true, + "path": "ignoreWarnings[].file", + "type": "RegExp", + }, + ], + "description": "A RegExp to select the origin file for the warning.", + "multiple": true, + "simpleType": "string", + }, + "ignore-warnings-message": Object { + "configs": Array [ + Object { + "description": "A RegExp to select the warning message.", + "multiple": true, + "path": "ignoreWarnings[].message", + "type": "RegExp", + }, + ], + "description": "A RegExp to select the warning message.", + "multiple": true, + "simpleType": "string", + }, + "ignore-warnings-module": Object { + "configs": Array [ + Object { + "description": "A RegExp to select the origin module for the warning.", + "multiple": true, + "path": "ignoreWarnings[].module", + "type": "RegExp", + }, + ], + "description": "A RegExp to select the origin module for the warning.", + "multiple": true, + "simpleType": "string", + }, + "ignore-warnings-reset": Object { + "configs": Array [ + Object { + "description": "Clear all items provided in 'ignoreWarnings' configuration. Ignore specific warnings.", + "multiple": false, + "path": "ignoreWarnings", + "type": "reset", + }, + ], + "description": "Clear all items provided in 'ignoreWarnings' configuration. Ignore specific warnings.", + "multiple": false, + "simpleType": "boolean", + }, + "infrastructure-logging-append-only": Object { + "configs": Array [ + Object { + "description": "Only appends lines to the output. Avoids updating existing output e. g. for status messages. This option is only used when no custom console is provided.", + "multiple": false, + "path": "infrastructureLogging.appendOnly", + "type": "boolean", + }, + ], + "description": "Only appends lines to the output. Avoids updating existing output e. g. for status messages. This option is only used when no custom console is provided.", + "multiple": false, + "simpleType": "boolean", + }, + "infrastructure-logging-colors": Object { + "configs": Array [ + Object { + "description": "Enables/Disables colorful output. This option is only used when no custom console is provided.", + "multiple": false, + "path": "infrastructureLogging.colors", + "type": "boolean", + }, + ], + "description": "Enables/Disables colorful output. This option is only used when no custom console is provided.", + "multiple": false, + "simpleType": "boolean", + }, + "infrastructure-logging-debug": Object { + "configs": Array [ + Object { + "description": "Enable/Disable debug logging for all loggers.", + "multiple": false, + "path": "infrastructureLogging.debug", + "type": "boolean", + }, + Object { + "description": "Enable debug logging for specific loggers.", + "multiple": true, + "path": "infrastructureLogging.debug[]", + "type": "RegExp", + }, + Object { + "description": "Enable debug logging for specific loggers.", + "multiple": true, + "path": "infrastructureLogging.debug[]", + "type": "string", + }, + ], + "description": "Enable/Disable debug logging for all loggers. Enable debug logging for specific loggers.", + "multiple": true, + "simpleType": "string", + }, + "infrastructure-logging-debug-reset": Object { + "configs": Array [ + Object { + "description": "Clear all items provided in 'infrastructureLogging.debug' configuration. Enable debug logging for specific loggers.", + "multiple": false, + "path": "infrastructureLogging.debug", + "type": "reset", + }, + ], + "description": "Clear all items provided in 'infrastructureLogging.debug' configuration. Enable debug logging for specific loggers.", + "multiple": false, + "simpleType": "boolean", + }, + "infrastructure-logging-level": Object { + "configs": Array [ + Object { + "description": "Log level.", + "multiple": false, + "path": "infrastructureLogging.level", + "type": "enum", + "values": Array [ + "none", + "error", + "warn", + "info", + "log", + "verbose", + ], + }, + ], + "description": "Log level.", + "multiple": false, + "simpleType": "string", + }, + "mode": Object { + "configs": Array [ + Object { + "description": "Enable production optimizations or development hints.", + "multiple": false, + "path": "mode", + "type": "enum", + "values": Array [ + "development", + "production", + "none", + ], + }, + ], + "description": "Enable production optimizations or development hints.", + "multiple": false, + "simpleType": "string", + }, + "module-expr-context-critical": Object { + "configs": Array [ + Object { + "description": "Enable warnings for full dynamic dependencies.", + "multiple": false, + "path": "module.exprContextCritical", + "type": "boolean", + }, + ], + "description": "Enable warnings for full dynamic dependencies.", + "multiple": false, + "simpleType": "boolean", + }, + "module-expr-context-recursive": Object { + "configs": Array [ + Object { + "description": "Enable recursive directory lookup for full dynamic dependencies. Deprecated: This option has moved to 'module.parser.javascript.exprContextRecursive'.", + "multiple": false, + "path": "module.exprContextRecursive", + "type": "boolean", + }, + ], + "description": "Enable recursive directory lookup for full dynamic dependencies. Deprecated: This option has moved to 'module.parser.javascript.exprContextRecursive'.", + "multiple": false, + "simpleType": "boolean", + }, + "module-expr-context-reg-exp": Object { + "configs": Array [ + Object { + "description": "Sets the default regular expression for full dynamic dependencies. Deprecated: This option has moved to 'module.parser.javascript.exprContextRegExp'.", + "multiple": false, + "path": "module.exprContextRegExp", + "type": "RegExp", + }, + Object { + "description": "Sets the default regular expression for full dynamic dependencies. Deprecated: This option has moved to 'module.parser.javascript.exprContextRegExp'.", + "multiple": false, + "path": "module.exprContextRegExp", + "type": "boolean", + }, + ], + "description": "Sets the default regular expression for full dynamic dependencies. Deprecated: This option has moved to 'module.parser.javascript.exprContextRegExp'.", + "multiple": false, + "simpleType": "string", + }, + "module-expr-context-request": Object { + "configs": Array [ + Object { + "description": "Set the default request for full dynamic dependencies. Deprecated: This option has moved to 'module.parser.javascript.exprContextRequest'.", + "multiple": false, + "path": "module.exprContextRequest", + "type": "string", + }, + ], + "description": "Set the default request for full dynamic dependencies. Deprecated: This option has moved to 'module.parser.javascript.exprContextRequest'.", + "multiple": false, + "simpleType": "string", + }, + "module-generator-asset-binary": Object { + "configs": Array [ + Object { + "description": "Whether or not this asset module should be considered binary. This can be set to 'false' to treat this asset module as text.", + "multiple": false, + "path": "module.generator.asset.binary", + "type": "boolean", + }, + ], + "description": "Whether or not this asset module should be considered binary. This can be set to 'false' to treat this asset module as text.", + "multiple": false, + "simpleType": "boolean", + }, + "module-generator-asset-data-url-encoding": Object { + "configs": Array [ + Object { + "description": "Asset encoding (defaults to base64).", + "multiple": false, + "path": "module.generator.asset.dataUrl.encoding", + "type": "enum", + "values": Array [ + false, + "base64", + ], + }, + ], + "description": "Asset encoding (defaults to base64).", + "multiple": false, + "simpleType": "string", + }, + "module-generator-asset-data-url-mimetype": Object { + "configs": Array [ + Object { + "description": "Asset mimetype (getting from file extension by default).", + "multiple": false, + "path": "module.generator.asset.dataUrl.mimetype", + "type": "string", + }, + ], + "description": "Asset mimetype (getting from file extension by default).", + "multiple": false, + "simpleType": "string", + }, + "module-generator-asset-emit": Object { + "configs": Array [ + Object { + "description": "Emit an output asset from this asset module. This can be set to 'false' to omit emitting e. g. for SSR.", + "multiple": false, + "path": "module.generator.asset.emit", + "type": "boolean", + }, + ], + "description": "Emit an output asset from this asset module. This can be set to 'false' to omit emitting e. g. for SSR.", + "multiple": false, + "simpleType": "boolean", + }, + "module-generator-asset-filename": Object { + "configs": Array [ + Object { + "description": "The filename of asset modules as relative path inside the 'output.path' directory.", + "multiple": false, + "path": "module.generator.asset.filename", + "type": "string", + }, + ], + "description": "The filename of asset modules as relative path inside the 'output.path' directory.", + "multiple": false, + "simpleType": "string", + }, + "module-generator-asset-inline-binary": Object { + "configs": Array [ + Object { + "description": "Whether or not this asset module should be considered binary. This can be set to 'false' to treat this asset module as text.", + "multiple": false, + "path": "module.generator.asset/inline.binary", + "type": "boolean", + }, + ], + "description": "Whether or not this asset module should be considered binary. This can be set to 'false' to treat this asset module as text.", + "multiple": false, + "simpleType": "boolean", + }, + "module-generator-asset-inline-data-url-encoding": Object { + "configs": Array [ + Object { + "description": "Asset encoding (defaults to base64).", + "multiple": false, + "path": "module.generator.asset/inline.dataUrl.encoding", + "type": "enum", + "values": Array [ + false, + "base64", + ], + }, + ], + "description": "Asset encoding (defaults to base64).", + "multiple": false, + "simpleType": "string", + }, + "module-generator-asset-inline-data-url-mimetype": Object { + "configs": Array [ + Object { + "description": "Asset mimetype (getting from file extension by default).", + "multiple": false, + "path": "module.generator.asset/inline.dataUrl.mimetype", + "type": "string", + }, + ], + "description": "Asset mimetype (getting from file extension by default).", + "multiple": false, + "simpleType": "string", + }, + "module-generator-asset-output-path": Object { + "configs": Array [ + Object { + "description": "Emit the asset in the specified folder relative to 'output.path'. This should only be needed when custom 'publicPath' is specified to match the folder structure there.", + "multiple": false, + "path": "module.generator.asset.outputPath", + "type": "string", + }, + ], + "description": "Emit the asset in the specified folder relative to 'output.path'. This should only be needed when custom 'publicPath' is specified to match the folder structure there.", + "multiple": false, + "simpleType": "string", + }, + "module-generator-asset-public-path": Object { + "configs": Array [ + Object { + "description": "The 'publicPath' specifies the public URL address of the output files when referenced in a browser.", + "multiple": false, + "path": "module.generator.asset.publicPath", + "type": "string", + }, + ], + "description": "The 'publicPath' specifies the public URL address of the output files when referenced in a browser.", + "multiple": false, + "simpleType": "string", + }, + "module-generator-asset-resource-binary": Object { + "configs": Array [ + Object { + "description": "Whether or not this asset module should be considered binary. This can be set to 'false' to treat this asset module as text.", + "multiple": false, + "path": "module.generator.asset/resource.binary", + "type": "boolean", + }, + ], + "description": "Whether or not this asset module should be considered binary. This can be set to 'false' to treat this asset module as text.", + "multiple": false, + "simpleType": "boolean", + }, + "module-generator-asset-resource-emit": Object { + "configs": Array [ + Object { + "description": "Emit an output asset from this asset module. This can be set to 'false' to omit emitting e. g. for SSR.", + "multiple": false, + "path": "module.generator.asset/resource.emit", + "type": "boolean", + }, + ], + "description": "Emit an output asset from this asset module. This can be set to 'false' to omit emitting e. g. for SSR.", + "multiple": false, + "simpleType": "boolean", + }, + "module-generator-asset-resource-filename": Object { + "configs": Array [ + Object { + "description": "The filename of asset modules as relative path inside the 'output.path' directory.", + "multiple": false, + "path": "module.generator.asset/resource.filename", + "type": "string", + }, + ], + "description": "The filename of asset modules as relative path inside the 'output.path' directory.", + "multiple": false, + "simpleType": "string", + }, + "module-generator-asset-resource-output-path": Object { + "configs": Array [ + Object { + "description": "Emit the asset in the specified folder relative to 'output.path'. This should only be needed when custom 'publicPath' is specified to match the folder structure there.", + "multiple": false, + "path": "module.generator.asset/resource.outputPath", + "type": "string", + }, + ], + "description": "Emit the asset in the specified folder relative to 'output.path'. This should only be needed when custom 'publicPath' is specified to match the folder structure there.", + "multiple": false, + "simpleType": "string", + }, + "module-generator-asset-resource-public-path": Object { + "configs": Array [ + Object { + "description": "The 'publicPath' specifies the public URL address of the output files when referenced in a browser.", + "multiple": false, + "path": "module.generator.asset/resource.publicPath", + "type": "string", + }, + ], + "description": "The 'publicPath' specifies the public URL address of the output files when referenced in a browser.", + "multiple": false, + "simpleType": "string", + }, + "module-generator-css-auto-es-module": Object { + "configs": Array [ + Object { + "description": "Configure the generated JS modules that use the ES modules syntax.", + "multiple": false, + "path": "module.generator.css/auto.esModule", + "type": "boolean", + }, + ], + "description": "Configure the generated JS modules that use the ES modules syntax.", + "multiple": false, + "simpleType": "boolean", + }, + "module-generator-css-auto-export-type": Object { + "configs": Array [ + Object { + "description": "Configure how CSS content is exported as default.", + "multiple": false, + "path": "module.generator.css/auto.exportType", + "type": "enum", + "values": Array [ + "link", + "text", + "css-style-sheet", + "style", + ], + }, + ], + "description": "Configure how CSS content is exported as default.", + "multiple": false, + "simpleType": "string", + }, + "module-generator-css-auto-exports-convention": Object { + "configs": Array [ + Object { + "description": "Specifies the convention of exported names.", + "multiple": false, + "path": "module.generator.css/auto.exportsConvention", + "type": "enum", + "values": Array [ + "as-is", + "camel-case", + "camel-case-only", + "dashes", + "dashes-only", + ], + }, + ], + "description": "Specifies the convention of exported names.", + "multiple": false, + "simpleType": "string", + }, + "module-generator-css-auto-exports-only": Object { + "configs": Array [ + Object { + "description": "Avoid generating and loading a stylesheet and only embed exports from css into output javascript files.", + "multiple": false, + "path": "module.generator.css/auto.exportsOnly", + "type": "boolean", + }, + ], + "description": "Avoid generating and loading a stylesheet and only embed exports from css into output javascript files.", + "multiple": false, + "simpleType": "boolean", + }, + "module-generator-css-auto-local-ident-hash-digest": Object { + "configs": Array [ + Object { + "description": "Digest types used for the hash.", + "multiple": false, + "path": "module.generator.css/auto.localIdentHashDigest", + "type": "string", + }, + ], + "description": "Digest types used for the hash.", + "multiple": false, + "simpleType": "string", + }, + "module-generator-css-auto-local-ident-hash-digest-length": Object { + "configs": Array [ + Object { + "description": "Number of chars which are used for the hash.", + "multiple": false, + "path": "module.generator.css/auto.localIdentHashDigestLength", + "type": "number", + }, + ], + "description": "Number of chars which are used for the hash.", + "multiple": false, + "simpleType": "number", + }, + "module-generator-css-auto-local-ident-hash-function": Object { + "configs": Array [ + Object { + "description": "Algorithm used for generation the hash (see node.js crypto package).", + "multiple": false, + "path": "module.generator.css/auto.localIdentHashFunction", + "type": "string", + }, + ], + "description": "Algorithm used for generation the hash (see node.js crypto package).", + "multiple": false, + "simpleType": "string", + }, + "module-generator-css-auto-local-ident-hash-salt": Object { + "configs": Array [ + Object { + "description": "Any string which is added to the hash to salt it.", + "multiple": false, + "path": "module.generator.css/auto.localIdentHashSalt", + "type": "string", + }, + ], + "description": "Any string which is added to the hash to salt it.", + "multiple": false, + "simpleType": "string", + }, + "module-generator-css-auto-local-ident-name": Object { + "configs": Array [ + Object { + "description": "Configure the generated local ident name.", + "multiple": false, + "path": "module.generator.css/auto.localIdentName", + "type": "string", + }, + ], + "description": "Configure the generated local ident name.", + "multiple": false, + "simpleType": "string", + }, + "module-generator-css-es-module": Object { + "configs": Array [ + Object { + "description": "Configure the generated JS modules that use the ES modules syntax.", + "multiple": false, + "path": "module.generator.css.esModule", + "type": "boolean", + }, + ], + "description": "Configure the generated JS modules that use the ES modules syntax.", + "multiple": false, + "simpleType": "boolean", + }, + "module-generator-css-exports-only": Object { + "configs": Array [ + Object { + "description": "Avoid generating and loading a stylesheet and only embed exports from css into output javascript files.", + "multiple": false, + "path": "module.generator.css.exportsOnly", + "type": "boolean", + }, + ], + "description": "Avoid generating and loading a stylesheet and only embed exports from css into output javascript files.", + "multiple": false, + "simpleType": "boolean", + }, + "module-generator-css-global-es-module": Object { + "configs": Array [ + Object { + "description": "Configure the generated JS modules that use the ES modules syntax.", + "multiple": false, + "path": "module.generator.css/global.esModule", + "type": "boolean", + }, + ], + "description": "Configure the generated JS modules that use the ES modules syntax.", + "multiple": false, + "simpleType": "boolean", + }, + "module-generator-css-global-export-type": Object { + "configs": Array [ + Object { + "description": "Configure how CSS content is exported as default.", + "multiple": false, + "path": "module.generator.css/global.exportType", + "type": "enum", + "values": Array [ + "link", + "text", + "css-style-sheet", + "style", + ], + }, + ], + "description": "Configure how CSS content is exported as default.", + "multiple": false, + "simpleType": "string", + }, + "module-generator-css-global-exports-convention": Object { + "configs": Array [ + Object { + "description": "Specifies the convention of exported names.", + "multiple": false, + "path": "module.generator.css/global.exportsConvention", + "type": "enum", + "values": Array [ + "as-is", + "camel-case", + "camel-case-only", + "dashes", + "dashes-only", + ], + }, + ], + "description": "Specifies the convention of exported names.", + "multiple": false, + "simpleType": "string", + }, + "module-generator-css-global-exports-only": Object { + "configs": Array [ + Object { + "description": "Avoid generating and loading a stylesheet and only embed exports from css into output javascript files.", + "multiple": false, + "path": "module.generator.css/global.exportsOnly", + "type": "boolean", + }, + ], + "description": "Avoid generating and loading a stylesheet and only embed exports from css into output javascript files.", + "multiple": false, + "simpleType": "boolean", + }, + "module-generator-css-global-local-ident-hash-digest": Object { + "configs": Array [ + Object { + "description": "Digest types used for the hash.", + "multiple": false, + "path": "module.generator.css/global.localIdentHashDigest", + "type": "string", + }, + ], + "description": "Digest types used for the hash.", + "multiple": false, + "simpleType": "string", + }, + "module-generator-css-global-local-ident-hash-digest-length": Object { + "configs": Array [ + Object { + "description": "Number of chars which are used for the hash.", + "multiple": false, + "path": "module.generator.css/global.localIdentHashDigestLength", + "type": "number", + }, + ], + "description": "Number of chars which are used for the hash.", + "multiple": false, + "simpleType": "number", + }, + "module-generator-css-global-local-ident-hash-function": Object { + "configs": Array [ + Object { + "description": "Algorithm used for generation the hash (see node.js crypto package).", + "multiple": false, + "path": "module.generator.css/global.localIdentHashFunction", + "type": "string", + }, + ], + "description": "Algorithm used for generation the hash (see node.js crypto package).", + "multiple": false, + "simpleType": "string", + }, + "module-generator-css-global-local-ident-hash-salt": Object { + "configs": Array [ + Object { + "description": "Any string which is added to the hash to salt it.", + "multiple": false, + "path": "module.generator.css/global.localIdentHashSalt", + "type": "string", + }, + ], + "description": "Any string which is added to the hash to salt it.", + "multiple": false, + "simpleType": "string", + }, + "module-generator-css-global-local-ident-name": Object { + "configs": Array [ + Object { + "description": "Configure the generated local ident name.", + "multiple": false, + "path": "module.generator.css/global.localIdentName", + "type": "string", + }, + ], + "description": "Configure the generated local ident name.", + "multiple": false, + "simpleType": "string", + }, + "module-generator-css-module-es-module": Object { + "configs": Array [ + Object { + "description": "Configure the generated JS modules that use the ES modules syntax.", + "multiple": false, + "path": "module.generator.css/module.esModule", + "type": "boolean", + }, + ], + "description": "Configure the generated JS modules that use the ES modules syntax.", + "multiple": false, + "simpleType": "boolean", + }, + "module-generator-css-module-export-type": Object { + "configs": Array [ + Object { + "description": "Configure how CSS content is exported as default.", + "multiple": false, + "path": "module.generator.css/module.exportType", + "type": "enum", + "values": Array [ + "link", + "text", + "css-style-sheet", + "style", + ], + }, + ], + "description": "Configure how CSS content is exported as default.", + "multiple": false, + "simpleType": "string", + }, + "module-generator-css-module-exports-convention": Object { + "configs": Array [ + Object { + "description": "Specifies the convention of exported names.", + "multiple": false, + "path": "module.generator.css/module.exportsConvention", + "type": "enum", + "values": Array [ + "as-is", + "camel-case", + "camel-case-only", + "dashes", + "dashes-only", + ], + }, + ], + "description": "Specifies the convention of exported names.", + "multiple": false, + "simpleType": "string", + }, + "module-generator-css-module-exports-only": Object { + "configs": Array [ + Object { + "description": "Avoid generating and loading a stylesheet and only embed exports from css into output javascript files.", + "multiple": false, + "path": "module.generator.css/module.exportsOnly", + "type": "boolean", + }, + ], + "description": "Avoid generating and loading a stylesheet and only embed exports from css into output javascript files.", + "multiple": false, + "simpleType": "boolean", + }, + "module-generator-css-module-local-ident-hash-digest": Object { + "configs": Array [ + Object { + "description": "Digest types used for the hash.", + "multiple": false, + "path": "module.generator.css/module.localIdentHashDigest", + "type": "string", + }, + ], + "description": "Digest types used for the hash.", + "multiple": false, + "simpleType": "string", + }, + "module-generator-css-module-local-ident-hash-digest-length": Object { + "configs": Array [ + Object { + "description": "Number of chars which are used for the hash.", + "multiple": false, + "path": "module.generator.css/module.localIdentHashDigestLength", + "type": "number", + }, + ], + "description": "Number of chars which are used for the hash.", + "multiple": false, + "simpleType": "number", + }, + "module-generator-css-module-local-ident-hash-function": Object { + "configs": Array [ + Object { + "description": "Algorithm used for generation the hash (see node.js crypto package).", + "multiple": false, + "path": "module.generator.css/module.localIdentHashFunction", + "type": "string", + }, + ], + "description": "Algorithm used for generation the hash (see node.js crypto package).", + "multiple": false, + "simpleType": "string", + }, + "module-generator-css-module-local-ident-hash-salt": Object { + "configs": Array [ + Object { + "description": "Any string which is added to the hash to salt it.", + "multiple": false, + "path": "module.generator.css/module.localIdentHashSalt", + "type": "string", + }, + ], + "description": "Any string which is added to the hash to salt it.", + "multiple": false, + "simpleType": "string", + }, + "module-generator-css-module-local-ident-name": Object { + "configs": Array [ + Object { + "description": "Configure the generated local ident name.", + "multiple": false, + "path": "module.generator.css/module.localIdentName", + "type": "string", + }, + ], + "description": "Configure the generated local ident name.", + "multiple": false, + "simpleType": "string", + }, + "module-generator-html-extract": Object { + "configs": Array [ + Object { + "description": "Emit the parsed and URL-rewritten HTML as a standalone \`.html\` output file alongside the module's JavaScript export. When unset, extraction defaults to \`true\` for HTML modules used as compilation entries (HTML entry points) and \`false\` for HTML modules imported from JavaScript. Filenames follow \`output.htmlFilename\` / \`output.htmlChunkFilename\`.", + "multiple": false, + "path": "module.generator.html.extract", + "type": "boolean", + }, + ], + "description": "Emit the parsed and URL-rewritten HTML as a standalone \`.html\` output file alongside the module's JavaScript export. When unset, extraction defaults to \`true\` for HTML modules used as compilation entries (HTML entry points) and \`false\` for HTML modules imported from JavaScript. Filenames follow \`output.htmlFilename\` / \`output.htmlChunkFilename\`.", + "multiple": false, + "simpleType": "boolean", + }, + "module-generator-json-json-parse": Object { + "configs": Array [ + Object { + "description": "Use \`JSON.parse\` when the JSON string is longer than 20 characters.", + "multiple": false, + "path": "module.generator.json.JSONParse", + "type": "boolean", + }, + ], + "description": "Use \`JSON.parse\` when the JSON string is longer than 20 characters.", + "multiple": false, + "simpleType": "boolean", + }, + "module-no-parse": Object { + "configs": Array [ + Object { + "description": "A regular expression, when matched the module is not parsed.", + "multiple": true, + "path": "module.noParse[]", + "type": "RegExp", + }, + Object { + "description": "An absolute path, when the module starts with this path it is not parsed.", + "multiple": true, + "path": "module.noParse[]", + "type": "path", + }, + ], + "description": "A regular expression, when matched the module is not parsed. An absolute path, when the module starts with this path it is not parsed.", + "multiple": true, + "simpleType": "string", + }, + "module-no-parse-reset": Object { + "configs": Array [ + Object { + "description": "Clear all items provided in 'module.noParse' configuration. Don't parse files matching. It's matched against the full resolved request.", + "multiple": false, + "path": "module.noParse", + "type": "reset", + }, + ], + "description": "Clear all items provided in 'module.noParse' configuration. Don't parse files matching. It's matched against the full resolved request.", + "multiple": false, + "simpleType": "boolean", + }, + "module-parser-asset-data-url-condition-max-size": Object { + "configs": Array [ + Object { + "description": "Maximum size of asset that should be inline as modules. Default: 8kb.", + "multiple": false, + "path": "module.parser.asset.dataUrlCondition.maxSize", + "type": "number", + }, + ], + "description": "Maximum size of asset that should be inline as modules. Default: 8kb.", + "multiple": false, + "simpleType": "number", + }, + "module-parser-css-as": Object { + "configs": Array [ + Object { + "description": "Configure how the CSS source is parsed: as a full stylesheet (default) or as a block's contents (e.g. the content of an HTML \`style\` attribute).", + "multiple": false, + "path": "module.parser.css.as", + "type": "enum", + "values": Array [ + "stylesheet", + "block-contents", + ], + }, + ], + "description": "Configure how the CSS source is parsed: as a full stylesheet (default) or as a block's contents (e.g. the content of an HTML \`style\` attribute).", + "multiple": false, + "simpleType": "string", + }, + "module-parser-css-auto-animation": Object { + "configs": Array [ + Object { + "description": "Enable/disable renaming of \`@keyframes\`.", + "multiple": false, + "path": "module.parser.css/auto.animation", + "type": "boolean", + }, + ], + "description": "Enable/disable renaming of \`@keyframes\`.", + "multiple": false, + "simpleType": "boolean", + }, + "module-parser-css-auto-as": Object { + "configs": Array [ + Object { + "description": "Configure how the CSS source is parsed: as a full stylesheet (default) or as a block's contents (e.g. the content of an HTML \`style\` attribute).", + "multiple": false, + "path": "module.parser.css/auto.as", + "type": "enum", + "values": Array [ + "stylesheet", + "block-contents", + ], + }, + ], + "description": "Configure how the CSS source is parsed: as a full stylesheet (default) or as a block's contents (e.g. the content of an HTML \`style\` attribute).", + "multiple": false, + "simpleType": "string", + }, + "module-parser-css-auto-container": Object { + "configs": Array [ + Object { + "description": "Enable/disable renaming of \`@container\` names.", + "multiple": false, + "path": "module.parser.css/auto.container", + "type": "boolean", + }, + ], + "description": "Enable/disable renaming of \`@container\` names.", + "multiple": false, + "simpleType": "boolean", + }, + "module-parser-css-auto-custom-idents": Object { + "configs": Array [ + Object { + "description": "Enable/disable renaming of custom identifiers.", + "multiple": false, + "path": "module.parser.css/auto.customIdents", + "type": "boolean", + }, + ], + "description": "Enable/disable renaming of custom identifiers.", + "multiple": false, + "simpleType": "boolean", + }, + "module-parser-css-auto-dashed-idents": Object { + "configs": Array [ + Object { + "description": "Enable/disable renaming of dashed identifiers, e. g. custom properties.", + "multiple": false, + "path": "module.parser.css/auto.dashedIdents", + "type": "boolean", + }, + ], + "description": "Enable/disable renaming of dashed identifiers, e. g. custom properties.", + "multiple": false, + "simpleType": "boolean", + }, + "module-parser-css-auto-export-type": Object { + "configs": Array [ + Object { + "description": "Configure how CSS content is exported as default.", + "multiple": false, + "path": "module.parser.css/auto.exportType", + "type": "enum", + "values": Array [ + "link", + "text", + "css-style-sheet", + "style", + ], + }, + ], + "description": "Configure how CSS content is exported as default.", + "multiple": false, + "simpleType": "string", + }, + "module-parser-css-auto-function": Object { + "configs": Array [ + Object { + "description": "Enable/disable renaming of \`@function\` names.", + "multiple": false, + "path": "module.parser.css/auto.function", + "type": "boolean", + }, + ], + "description": "Enable/disable renaming of \`@function\` names.", + "multiple": false, + "simpleType": "boolean", + }, + "module-parser-css-auto-grid": Object { + "configs": Array [ + Object { + "description": "Enable/disable renaming of grid identifiers.", + "multiple": false, + "path": "module.parser.css/auto.grid", + "type": "boolean", + }, + ], + "description": "Enable/disable renaming of grid identifiers.", + "multiple": false, + "simpleType": "boolean", + }, + "module-parser-css-auto-import": Object { + "configs": Array [ + Object { + "description": "Enable/disable \`@import\` at-rules handling.", + "multiple": false, + "path": "module.parser.css/auto.import", + "type": "boolean", + }, + ], + "description": "Enable/disable \`@import\` at-rules handling.", + "multiple": false, + "simpleType": "boolean", + }, + "module-parser-css-auto-named-exports": Object { + "configs": Array [ + Object { + "description": "Use ES modules named export for css exports.", + "multiple": false, + "path": "module.parser.css/auto.namedExports", + "type": "boolean", + }, + ], + "description": "Use ES modules named export for css exports.", + "multiple": false, + "simpleType": "boolean", + }, + "module-parser-css-auto-pure": Object { + "configs": Array [ + Object { + "description": "Enable strict pure mode: every selector must contain at least one local class or id selector.", + "multiple": false, + "path": "module.parser.css/auto.pure", + "type": "boolean", + }, + ], + "description": "Enable strict pure mode: every selector must contain at least one local class or id selector.", + "multiple": false, + "simpleType": "boolean", + }, + "module-parser-css-auto-url": Object { + "configs": Array [ + Object { + "description": "Enable/disable \`url()\`/\`image-set()\`/\`src()\`/\`image()\` functions handling.", + "multiple": false, + "path": "module.parser.css/auto.url", + "type": "boolean", + }, + ], + "description": "Enable/disable \`url()\`/\`image-set()\`/\`src()\`/\`image()\` functions handling.", + "multiple": false, + "simpleType": "boolean", + }, + "module-parser-css-export-type": Object { + "configs": Array [ + Object { + "description": "Configure how CSS content is exported as default.", + "multiple": false, + "path": "module.parser.css.exportType", + "type": "enum", + "values": Array [ + "link", + "text", + "css-style-sheet", + "style", + ], + }, + ], + "description": "Configure how CSS content is exported as default.", + "multiple": false, + "simpleType": "string", + }, + "module-parser-css-global-animation": Object { + "configs": Array [ + Object { + "description": "Enable/disable renaming of \`@keyframes\`.", + "multiple": false, + "path": "module.parser.css/global.animation", + "type": "boolean", + }, + ], + "description": "Enable/disable renaming of \`@keyframes\`.", + "multiple": false, + "simpleType": "boolean", + }, + "module-parser-css-global-as": Object { + "configs": Array [ + Object { + "description": "Configure how the CSS source is parsed: as a full stylesheet (default) or as a block's contents (e.g. the content of an HTML \`style\` attribute).", + "multiple": false, + "path": "module.parser.css/global.as", + "type": "enum", + "values": Array [ + "stylesheet", + "block-contents", + ], + }, + ], + "description": "Configure how the CSS source is parsed: as a full stylesheet (default) or as a block's contents (e.g. the content of an HTML \`style\` attribute).", + "multiple": false, + "simpleType": "string", + }, + "module-parser-css-global-container": Object { + "configs": Array [ + Object { + "description": "Enable/disable renaming of \`@container\` names.", + "multiple": false, + "path": "module.parser.css/global.container", + "type": "boolean", + }, + ], + "description": "Enable/disable renaming of \`@container\` names.", + "multiple": false, + "simpleType": "boolean", + }, + "module-parser-css-global-custom-idents": Object { + "configs": Array [ + Object { + "description": "Enable/disable renaming of custom identifiers.", + "multiple": false, + "path": "module.parser.css/global.customIdents", + "type": "boolean", + }, + ], + "description": "Enable/disable renaming of custom identifiers.", + "multiple": false, + "simpleType": "boolean", + }, + "module-parser-css-global-dashed-idents": Object { + "configs": Array [ + Object { + "description": "Enable/disable renaming of dashed identifiers, e. g. custom properties.", + "multiple": false, + "path": "module.parser.css/global.dashedIdents", + "type": "boolean", + }, + ], + "description": "Enable/disable renaming of dashed identifiers, e. g. custom properties.", + "multiple": false, + "simpleType": "boolean", + }, + "module-parser-css-global-export-type": Object { + "configs": Array [ + Object { + "description": "Configure how CSS content is exported as default.", + "multiple": false, + "path": "module.parser.css/global.exportType", + "type": "enum", + "values": Array [ + "link", + "text", + "css-style-sheet", + "style", + ], + }, + ], + "description": "Configure how CSS content is exported as default.", + "multiple": false, + "simpleType": "string", + }, + "module-parser-css-global-function": Object { + "configs": Array [ + Object { + "description": "Enable/disable renaming of \`@function\` names.", + "multiple": false, + "path": "module.parser.css/global.function", + "type": "boolean", + }, + ], + "description": "Enable/disable renaming of \`@function\` names.", + "multiple": false, + "simpleType": "boolean", + }, + "module-parser-css-global-grid": Object { + "configs": Array [ + Object { + "description": "Enable/disable renaming of grid identifiers.", + "multiple": false, + "path": "module.parser.css/global.grid", + "type": "boolean", + }, + ], + "description": "Enable/disable renaming of grid identifiers.", + "multiple": false, + "simpleType": "boolean", + }, + "module-parser-css-global-import": Object { + "configs": Array [ + Object { + "description": "Enable/disable \`@import\` at-rules handling.", + "multiple": false, + "path": "module.parser.css/global.import", + "type": "boolean", + }, + ], + "description": "Enable/disable \`@import\` at-rules handling.", + "multiple": false, + "simpleType": "boolean", + }, + "module-parser-css-global-named-exports": Object { + "configs": Array [ + Object { + "description": "Use ES modules named export for css exports.", + "multiple": false, + "path": "module.parser.css/global.namedExports", + "type": "boolean", + }, + ], + "description": "Use ES modules named export for css exports.", + "multiple": false, + "simpleType": "boolean", + }, + "module-parser-css-global-url": Object { + "configs": Array [ + Object { + "description": "Enable/disable \`url()\`/\`image-set()\`/\`src()\`/\`image()\` functions handling.", + "multiple": false, + "path": "module.parser.css/global.url", + "type": "boolean", + }, + ], + "description": "Enable/disable \`url()\`/\`image-set()\`/\`src()\`/\`image()\` functions handling.", + "multiple": false, + "simpleType": "boolean", + }, + "module-parser-css-import": Object { + "configs": Array [ + Object { + "description": "Enable/disable \`@import\` at-rules handling.", + "multiple": false, + "path": "module.parser.css.import", + "type": "boolean", + }, + ], + "description": "Enable/disable \`@import\` at-rules handling.", + "multiple": false, + "simpleType": "boolean", + }, + "module-parser-css-module-animation": Object { + "configs": Array [ + Object { + "description": "Enable/disable renaming of \`@keyframes\`.", + "multiple": false, + "path": "module.parser.css/module.animation", + "type": "boolean", + }, + ], + "description": "Enable/disable renaming of \`@keyframes\`.", + "multiple": false, + "simpleType": "boolean", + }, + "module-parser-css-module-as": Object { + "configs": Array [ + Object { + "description": "Configure how the CSS source is parsed: as a full stylesheet (default) or as a block's contents (e.g. the content of an HTML \`style\` attribute).", + "multiple": false, + "path": "module.parser.css/module.as", + "type": "enum", + "values": Array [ + "stylesheet", + "block-contents", + ], + }, + ], + "description": "Configure how the CSS source is parsed: as a full stylesheet (default) or as a block's contents (e.g. the content of an HTML \`style\` attribute).", + "multiple": false, + "simpleType": "string", + }, + "module-parser-css-module-container": Object { + "configs": Array [ + Object { + "description": "Enable/disable renaming of \`@container\` names.", + "multiple": false, + "path": "module.parser.css/module.container", + "type": "boolean", + }, + ], + "description": "Enable/disable renaming of \`@container\` names.", + "multiple": false, + "simpleType": "boolean", + }, + "module-parser-css-module-custom-idents": Object { + "configs": Array [ + Object { + "description": "Enable/disable renaming of custom identifiers.", + "multiple": false, + "path": "module.parser.css/module.customIdents", + "type": "boolean", + }, + ], + "description": "Enable/disable renaming of custom identifiers.", + "multiple": false, + "simpleType": "boolean", + }, + "module-parser-css-module-dashed-idents": Object { + "configs": Array [ + Object { + "description": "Enable/disable renaming of dashed identifiers, e. g. custom properties.", + "multiple": false, + "path": "module.parser.css/module.dashedIdents", + "type": "boolean", + }, + ], + "description": "Enable/disable renaming of dashed identifiers, e. g. custom properties.", + "multiple": false, + "simpleType": "boolean", + }, + "module-parser-css-module-export-type": Object { + "configs": Array [ + Object { + "description": "Configure how CSS content is exported as default.", + "multiple": false, + "path": "module.parser.css/module.exportType", + "type": "enum", + "values": Array [ + "link", + "text", + "css-style-sheet", + "style", + ], + }, + ], + "description": "Configure how CSS content is exported as default.", + "multiple": false, + "simpleType": "string", + }, + "module-parser-css-module-function": Object { + "configs": Array [ + Object { + "description": "Enable/disable renaming of \`@function\` names.", + "multiple": false, + "path": "module.parser.css/module.function", + "type": "boolean", + }, + ], + "description": "Enable/disable renaming of \`@function\` names.", + "multiple": false, + "simpleType": "boolean", + }, + "module-parser-css-module-grid": Object { + "configs": Array [ + Object { + "description": "Enable/disable renaming of grid identifiers.", + "multiple": false, + "path": "module.parser.css/module.grid", + "type": "boolean", + }, + ], + "description": "Enable/disable renaming of grid identifiers.", + "multiple": false, + "simpleType": "boolean", + }, + "module-parser-css-module-import": Object { + "configs": Array [ + Object { + "description": "Enable/disable \`@import\` at-rules handling.", + "multiple": false, + "path": "module.parser.css/module.import", + "type": "boolean", + }, + ], + "description": "Enable/disable \`@import\` at-rules handling.", + "multiple": false, + "simpleType": "boolean", + }, + "module-parser-css-module-named-exports": Object { + "configs": Array [ + Object { + "description": "Use ES modules named export for css exports.", + "multiple": false, + "path": "module.parser.css/module.namedExports", + "type": "boolean", + }, + ], + "description": "Use ES modules named export for css exports.", + "multiple": false, + "simpleType": "boolean", + }, + "module-parser-css-module-pure": Object { + "configs": Array [ + Object { + "description": "Enable strict pure mode: every selector must contain at least one local class or id selector.", + "multiple": false, + "path": "module.parser.css/module.pure", + "type": "boolean", + }, + ], + "description": "Enable strict pure mode: every selector must contain at least one local class or id selector.", + "multiple": false, + "simpleType": "boolean", + }, + "module-parser-css-module-url": Object { + "configs": Array [ + Object { + "description": "Enable/disable \`url()\`/\`image-set()\`/\`src()\`/\`image()\` functions handling.", + "multiple": false, + "path": "module.parser.css/module.url", + "type": "boolean", + }, + ], + "description": "Enable/disable \`url()\`/\`image-set()\`/\`src()\`/\`image()\` functions handling.", + "multiple": false, + "simpleType": "boolean", + }, + "module-parser-css-named-exports": Object { + "configs": Array [ + Object { + "description": "Use ES modules named export for css exports.", + "multiple": false, + "path": "module.parser.css.namedExports", + "type": "boolean", + }, + ], + "description": "Use ES modules named export for css exports.", + "multiple": false, + "simpleType": "boolean", + }, + "module-parser-css-url": Object { + "configs": Array [ + Object { + "description": "Enable/disable \`url()\`/\`image-set()\`/\`src()\`/\`image()\` functions handling.", + "multiple": false, + "path": "module.parser.css.url", + "type": "boolean", + }, + ], + "description": "Enable/disable \`url()\`/\`image-set()\`/\`src()\`/\`image()\` functions handling.", + "multiple": false, + "simpleType": "boolean", + }, + "module-parser-html-sources": Object { + "configs": Array [ + Object { + "description": "A source entry: either the string \`\\"...\\"\` to inline the built-in default sources, or an object describing a \`tag\`/\`attribute\` pair to extract and how to bundle it.", + "multiple": true, + "path": "module.parser.html.sources[]", + "type": "enum", + "values": Array [ + "...", + ], + }, + Object { + "description": "Configure extraction of URL-like attribute values (e.g. \`\`, \`\`, \` - - - - - - - -
-
    -
  • -

    styling

    -
      -
    • -

      style!css loader

      -
    • -
    • -

      style!less loader

      -
    • -
    • -

      file loader:

      -
    • -
    -
-
- - - diff --git a/test/browsertest/web_modules/subcontent/index.js b/test/browsertest/web_modules/subcontent/index.js deleted file mode 100644 index c4efbc880dc..00000000000 --- a/test/browsertest/web_modules/subcontent/index.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = "replaced"; \ No newline at end of file diff --git a/test/browsertest/webpack.config.js b/test/browsertest/webpack.config.js deleted file mode 100644 index ee9f01b36c8..00000000000 --- a/test/browsertest/webpack.config.js +++ /dev/null @@ -1,16 +0,0 @@ -module.exports = { - resolve: { - modules: ["web_modules", "node_modules"], - extensions: [".json", ".web.js", ".js"] - }, - resolveLoader: { - extensions: [ - ".json", - ".webpack-loader.js", - ".web-loader.js", - ".loader.js", - ".js" - ], - mainFields: ["webpackLoader", "loader", "main"] - } -}; diff --git a/test/buildHtmlAst.unittest.js b/test/buildHtmlAst.unittest.js new file mode 100644 index 00000000000..8352f088d5b --- /dev/null +++ b/test/buildHtmlAst.unittest.js @@ -0,0 +1,847 @@ +"use strict"; + +// cspell:ignore selectedcontent + +const { + NS_HTML, + NS_MATHML, + NS_SVG, + NodeType, + buildHtmlAst +} = require("../lib/html/syntax"); + +/** + * @param {import("../lib/html/syntax").HtmlNode[]} children children + * @param {string} tagName tag name + * @returns {import("../lib/html/syntax").HtmlElement} the element + */ +const child = (children, tagName) => + /** @type {import("../lib/html/syntax").HtmlElement} */ ( + children.find((c) => c.type === NodeType.Element && c.tagName === tagName) + ); + +// The tree builder always produces a full document (html > head, body); these +// helpers reach the interesting subtrees. +/** + * @param {string} src source + * @returns {import("../lib/html/syntax").HtmlElement} html element + */ +const html = (src) => child(buildHtmlAst(src).children, "html"); +/** + * @param {string} src source + * @returns {import("../lib/html/syntax").HtmlElement[]} body children + */ +const body = (src) => + /** @type {import("../lib/html/syntax").HtmlElement[]} */ ( + child(html(src).children, "body").children + ); +/** + * @param {string} src source + * @returns {import("../lib/html/syntax").HtmlElement[]} head children + */ +const head = (src) => + /** @type {import("../lib/html/syntax").HtmlElement[]} */ ( + child(html(src).children, "head").children + ); + +/** + * @param {string} src source + * @param {string} tagName tag name + * @returns {import("../lib/html/syntax").HtmlElement} first matching element anywhere + */ +const find = (src, tagName) => { + /** @type {import("../lib/html/syntax").HtmlElement | undefined} */ + let found; + /** @param {import("../lib/html/syntax").HtmlNode} node node to search */ + const walk = (node) => { + if (found || node.type !== NodeType.Element) return; + if (node.tagName === tagName) { + found = node; + return; + } + for (const c of node.children) walk(c); + }; + for (const c of buildHtmlAst(src).children) walk(c); + return /** @type {import("../lib/html/syntax").HtmlElement} */ (found); +}; + +describe("buildHtmlAst", () => { + it("should produce an empty document with html/head/body scaffolding", () => { + const ast = buildHtmlAst(""); + expect(ast.type).toBe(NodeType.Document); + const root = child(ast.children, "html"); + expect(root.tagName).toBe("html"); + expect(child(root.children, "head").tagName).toBe("head"); + expect(child(root.children, "body").tagName).toBe("body"); + }); + + it("should parse a simple element into the body", () => { + const nodes = body("
"); + expect(nodes).toHaveLength(1); + expect(nodes[0].type).toBe(NodeType.Element); + expect(nodes[0].tagName).toBe("div"); + expect(nodes[0].children).toEqual([]); + }); + + it("should parse nested elements", () => { + const div = body("
hello
")[0]; + const span = /** @type {import("../lib/html/syntax").HtmlElement} */ ( + div.children[0] + ); + expect(span.tagName).toBe("span"); + expect(span.children[0].type).toBe(NodeType.Text); + expect( + /** @type {import("../lib/html/syntax").HtmlText} */ (span.children[0]) + .data + ).toBe("hello"); + }); + + it("should parse void elements", () => { + const nodes = body('
'); + expect(nodes).toHaveLength(2); + expect(nodes[0].tagName).toBe("img"); + expect(nodes[0].selfClosing).toBe(true); + expect(nodes[1].tagName).toBe("br"); + expect(nodes[1].selfClosing).toBe(true); + }); + + it("should keep attribute values raw with source offsets", () => { + const a = body('click')[0]; + expect(a.attributes).toHaveLength(2); + expect(a.attributes[0].name).toBe("href"); + expect(a.attributes[0].value).toBe("test.html"); + // Raw (undecoded) value preserved; consumers re-resolve from it. + const raw = body('x')[0]; + expect(raw.attributes[0].value).toBe("a&b"); + // Offsets line up with the source. + const src = 'x'; + const link = body(src)[0]; + const attr = link.attributes[0]; + expect(src.slice(attr.valueStart, attr.valueEnd)).toBe("test.html"); + }); + + it("should parse comments", () => { + const ast = buildHtmlAst(""); + expect(ast.children[0].type).toBe(NodeType.Comment); + expect( + /** @type {import("../lib/html/syntax").HtmlComment} */ (ast.children[0]) + .data + ).toBe(" hello "); + }); + + it("should parse doctype", () => { + const ast = buildHtmlAst(""); + expect(ast.children[0].type).toBe(NodeType.Doctype); + expect( + /** @type {import("../lib/html/syntax").HtmlDoctype} */ (ast.children[0]) + .name + ).toBe("html"); + }); + + it("should handle self-closing tags", () => { + const nodes = body(""); + expect(nodes[0].tagName).toBe("input"); + expect(nodes[0].selfClosing).toBe(true); + }); + + it("should auto-close

when a block element opens", () => { + const nodes = body("

one

two
"); + expect(nodes).toHaveLength(2); + expect(nodes[0].tagName).toBe("p"); + expect( + /** @type {import("../lib/html/syntax").HtmlText} */ ( + nodes[0].children[0] + ).data + ).toBe("one"); + expect(nodes[1].tagName).toBe("div"); + }); + + it("should auto-close same-name elements like
  • ", () => { + const ul = body("
    • one
    • two
    ")[0]; + expect(ul.children).toHaveLength(2); + expect( + /** @type {import("../lib/html/syntax").HtmlText} */ ( + /** @type {import("../lib/html/syntax").HtmlElement} */ (ul.children[0]) + .children[0] + ).data + ).toBe("one"); + expect( + /** @type {import("../lib/html/syntax").HtmlText} */ ( + /** @type {import("../lib/html/syntax").HtmlElement} */ (ul.children[1]) + .children[0] + ).data + ).toBe("two"); + }); + + it("should merge adjacent text nodes", () => { + // Foster-parenting the table's text next to the leading text exercises + // the adjacent-text-node merge. + const nodes = body("TextMisplaced
    "); + expect(nodes[0].type).toBe(NodeType.Text); + expect( + /** @type {import("../lib/html/syntax").HtmlText} */ ( + /** @type {unknown} */ (nodes[0]) + ).data + ).toBe("TextMisplaced"); + expect(child(nodes, "table").tagName).toBe("table"); + }); + + it("should detect SVG namespace and adjust foreign tag names", () => { + const svg = body("")[0]; + expect(svg.namespace).toBe(NS_SVG); + // SVG tag-name case is corrected per the foreign adjustment table. + expect( + /** @type {import("../lib/html/syntax").HtmlElement} */ (svg.children[0]) + .tagName + ).toBe("linearGradient"); + expect( + /** @type {import("../lib/html/syntax").HtmlElement} */ (svg.children[0]) + .namespace + ).toBe(NS_SVG); + }); + + it("should not resolve prototype-named SVG tags and attributes through the adjustment tables", () => { + const svg = body('')[0]; + const el = /** @type {import("../lib/html/syntax").HtmlElement} */ ( + svg.children[0] + ); + expect(el.tagName).toBe("constructor"); + expect(el.attributes[0].name).toBe("tostring"); + }); + + it("should detect MathML namespace", () => { + const math = body("x")[0]; + expect(math.namespace).toBe(NS_MATHML); + expect( + /** @type {import("../lib/html/syntax").HtmlElement} */ (math.children[0]) + .namespace + ).toBe(NS_MATHML); + }); + + it("should route head and body content to the right place", () => { + const src = + 'T

    Hi

    '; + expect(child(head(src), "meta").tagName).toBe("meta"); + expect(child(head(src), "title").tagName).toBe("title"); + expect(child(body(src), "h1").tagName).toBe("h1"); + }); + + it("should export namespace constants", () => { + expect(NS_HTML).toBe(0); + expect(NS_MATHML).toBe(1); + expect(NS_SVG).toBe(2); + }); + + it("should handle valueless attributes", () => { + const input = body("")[0]; + expect(input.attributes[0].name).toBe("disabled"); + expect(input.attributes[0].value).toBe(""); + }); + + it("should handle all attribute quote styles", () => { + const input = body("")[0]; + expect(input.attributes.map((attr) => attr.value)).toEqual([ + "1", + "2", + "3", + "" + ]); + }); + + it("should construct the table structure with implied tbody/tr", () => { + const table = body("
    ab
    ")[0]; + const tbody = /** @type {import("../lib/html/syntax").HtmlElement} */ ( + table.children[0] + ); + expect(tbody.tagName).toBe("tbody"); + const tr = /** @type {import("../lib/html/syntax").HtmlElement} */ ( + tbody.children[0] + ); + expect( + tr.children.map( + (c) => + /** @type {import("../lib/html/syntax").HtmlElement} */ (c).tagName + ) + ).toEqual(["td", "td"]); + }); + + it("should treat SVG foreignObject/desc as HTML integration points", () => { + const svg = body( + "
    html
    " + )[0]; + const fo = /** @type {import("../lib/html/syntax").HtmlElement} */ ( + svg.children[0] + ); + expect(fo.namespace).toBe(NS_SVG); + expect( + /** @type {import("../lib/html/syntax").HtmlElement} */ (fo.children[0]) + .namespace + ).toBe(NS_HTML); + const desc = /** @type {import("../lib/html/syntax").HtmlElement} */ ( + body("
    x
    ")[0].children[0] + ); + expect( + /** @type {import("../lib/html/syntax").HtmlElement} */ (desc.children[0]) + .namespace + ).toBe(NS_HTML); + }); + + it("should keep CDATA text in foreign content", () => { + const svg = body("foo")[0]; + expect(svg.children[0].type).toBe(NodeType.Text); + expect( + /** @type {import("../lib/html/syntax").HtmlText} */ (svg.children[0]) + .data + ).toBe("foo"); + }); + + it("should treat bogus comments as comments", () => { + // A leading bogus comment is inserted into the document before . + const ast = buildHtmlAst(""); + expect(ast.children[0].type).toBe(NodeType.Comment); + expect( + /** @type {import("../lib/html/syntax").HtmlComment} */ (ast.children[0]) + .data + ).toBe("?bogus comment"); + }); + + it("should parse raw-text elements without decoding entities", () => { + const script = find("", "script"); + expect(script.children[0].type).toBe(NodeType.Text); + expect( + /** @type {import("../lib/html/syntax").HtmlText} */ (script.children[0]) + .data + ).toBe("var a = 1 < 2 & 3;"); + }); + + it("should set tagEnd, nameEnd and start used by the consumer", () => { + const src = ""; + const script = find(src, "script"); + expect(script.start).toBe(0); + expect(script.tagEnd).toBe(8); // after " + + + +
    Foo
    + + +
    BAR
    + + + + + + + + + \\"Flowers\\" + + + + + + + +]]> + +link text + +Call me + +--> +--> + + + + + +
    +
    + +<div id = "character"> +© 2007 +or +© 2007 + +
    + +\\"Red + + + +\\"Elva +\\"Elva + +\\"Elva + + + + Test + +test +test +test + + + + + +

    Text

    +

    Text

    +

    Text

    + + + + + + + + + +\\"Elva + +foo bar + +Text +Text + + + +
    + +Visit our HTML tutorial +Visit our HTML tutorial + +
    text
    +
    text
    + + + + + +
    + +
    + + + + + +" +`; diff --git a/test/configCases/html/basic/__snapshots__/ConfigTest.snap b/test/configCases/html/basic/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..bbe823b1501 --- /dev/null +++ b/test/configCases/html/basic/__snapshots__/ConfigTest.snap @@ -0,0 +1,146 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html basic exported tests should compile and export html as string 1`] = ` +" + +Test + +

    My First Heading

    +

    My first paragraph.

    +

    An Unordered HTML List

    + +
      +
    • Coffee
    • +
    • Tea
    • +
    • Milk
    • +
    + +

    An Ordered HTML List

    + +
      +
    1. Coffee
    2. +
    3. Tea
    4. +
    5. Milk
    6. +
    + + + + + +
    Foo
    + + +
    BAR
    + + + + + + + + + \\"Flowers\\" + + + + + + + +]]> + +link text + +Call me + +--> +--> + + + + + +
    +
    + +<div id = "character"> +© 2007 +or +© 2007 + +
    + +\\"Red + + + +\\"Elva +\\"Elva + +\\"Elva + + + + Test + +test +test +test + + + + + +

    Text

    +

    Text

    +

    Text

    + + + + + + + + + +\\"Elva + +foo bar + +Text +Text + + + +
    + +Visit our HTML tutorial +Visit our HTML tutorial + +
    text
    +
    text
    + + + + + +
    + +
    + + + + + +" +`; diff --git a/test/configCases/html/basic/image.png b/test/configCases/html/basic/image.png new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/configCases/html/basic/index.js b/test/configCases/html/basic/index.js new file mode 100644 index 00000000000..584723cb29a --- /dev/null +++ b/test/configCases/html/basic/index.js @@ -0,0 +1,6 @@ +import page from "./page.html"; + +it("should compile and export html as string", () => { + expect(typeof page).toBe("string"); + expect(page).toMatchSnapshot(); +}); diff --git a/test/configCases/html/basic/noscript.png b/test/configCases/html/basic/noscript.png new file mode 100644 index 00000000000..0f2de3749df Binary files /dev/null and b/test/configCases/html/basic/noscript.png differ diff --git a/test/configCases/html/basic/page.html b/test/configCases/html/basic/page.html new file mode 100644 index 00000000000..30d10fbdc85 --- /dev/null +++ b/test/configCases/html/basic/page.html @@ -0,0 +1,145 @@ + + +Test + +

    My First Heading

    +

    My first paragraph.

    +

    An Unordered HTML List

    + +
      +
    • Coffee
    • +
    • Tea
    • +
    • Milk
    • +
    + +

    An Ordered HTML List

    + +
      +
    1. Coffee
    2. +
    3. Tea
    4. +
    5. Milk
    6. +
    + + + + + +
    Foo
    + + +
    BAR
    + + + + + + + + + Flowers + + + + + + + +]]> + + + +Call me + +--> +--> + + + + + +
    +
    + +<div id = "character"> +© 2007 +or +© 2007 + +
    + +Red dot + + + +Elva dressed as a fairy +Elva dressed as a fairy + +Elva dressed as a fairy + + + + Test + +test +test +test + + + + + +

    Text

    +

    Text

    +

    Text

    + + + + + + + + + +Elva dressed as a fairy + +foo bar + +Text +Text + + + +
    + +Visit our HTML tutorial +Visit our HTML tutorial + +
    text
    +
    text
    + + + + + +
    + +
    + + + + + diff --git a/test/configCases/html/basic/webpack.config.js b/test/configCases/html/basic/webpack.config.js new file mode 100644 index 00000000000..bc4cbc8fe56 --- /dev/null +++ b/test/configCases/html/basic/webpack.config.js @@ -0,0 +1,9 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: "web", + experiments: { + html: true + } +}; diff --git a/test/configCases/html/broken-html-syntax/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/broken-html-syntax/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..f3404e96b73 --- /dev/null +++ b/test/configCases/html/broken-html-syntax/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html broken-html-syntax exported tests should compile and export html as string 1`] = ` +"Text < img src=\\"image.png\\" > +Text < +Text > + +boohay +<<<<>foo +>>< +Text < +Text > + +boohay +<<<<>foo +>>< { + expect(page).toMatchSnapshot(); +}); diff --git a/test/configCases/html/broken-html-syntax/page.html b/test/configCases/html/broken-html-syntax/page.html new file mode 100644 index 00000000000..330004b63df --- /dev/null +++ b/test/configCases/html/broken-html-syntax/page.html @@ -0,0 +1,8 @@ +Text < img src="image.png" > +Text < +Text > + +boohay +<<<<>foo +>>< + + + + + + Document + + +
    Test
    + + +" +`; diff --git a/test/configCases/html/concatenation/__snapshots__/ConfigTest.snap b/test/configCases/html/concatenation/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..c23eab41439 --- /dev/null +++ b/test/configCases/html/concatenation/__snapshots__/ConfigTest.snap @@ -0,0 +1,18 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html concatenation exported tests should concatenate HTML module 1`] = ` +" + + + + + + Document + + +
    Test
    + + +" +`; diff --git a/test/configCases/html/concatenation/index.js b/test/configCases/html/concatenation/index.js new file mode 100644 index 00000000000..1669f408dbf --- /dev/null +++ b/test/configCases/html/concatenation/index.js @@ -0,0 +1,6 @@ +import page from "./page.html"; + +it("should concatenate HTML module", () => { + expect(Object.keys(__webpack_modules__).length).toBe(1); + expect(page).toMatchSnapshot(); +}); diff --git a/test/configCases/html/concatenation/page.html b/test/configCases/html/concatenation/page.html new file mode 100644 index 00000000000..16d3650a519 --- /dev/null +++ b/test/configCases/html/concatenation/page.html @@ -0,0 +1,13 @@ + + + + + + + Document + + +
    Test
    + + diff --git a/test/configCases/html/concatenation/webpack.config.js b/test/configCases/html/concatenation/webpack.config.js new file mode 100644 index 00000000000..bc4cbc8fe56 --- /dev/null +++ b/test/configCases/html/concatenation/webpack.config.js @@ -0,0 +1,9 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: "web", + experiments: { + html: true + } +}; diff --git a/test/configCases/html/css-imported-from-js/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/css-imported-from-js/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..feb93dad9a9 --- /dev/null +++ b/test/configCases/html/css-imported-from-js/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html css-imported-from-js exported tests should emit a for the entry chunk's CSS file, copying CSP/fetch attributes from the originating + +
    Box
    + +" +`; diff --git a/test/configCases/html/css-imported-from-js/__snapshots__/ConfigTest.snap b/test/configCases/html/css-imported-from-js/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..081b4799ad8 --- /dev/null +++ b/test/configCases/html/css-imported-from-js/__snapshots__/ConfigTest.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html css-imported-from-js exported tests should emit a for the entry chunk's CSS file, copying CSP/fetch attributes from the originating + +
    Box
    + +" +`; diff --git a/test/configCases/html/css-imported-from-js/entry.js b/test/configCases/html/css-imported-from-js/entry.js new file mode 100644 index 00000000000..4fe51c72d64 --- /dev/null +++ b/test/configCases/html/css-imported-from-js/entry.js @@ -0,0 +1 @@ +import "./style.css"; diff --git a/test/configCases/html/css-imported-from-js/page.html b/test/configCases/html/css-imported-from-js/page.html new file mode 100644 index 00000000000..a745f77d8b7 --- /dev/null +++ b/test/configCases/html/css-imported-from-js/page.html @@ -0,0 +1,8 @@ + + + +HTML entry with JS that imports CSS + + +
    Box
    + diff --git a/test/configCases/html/css-imported-from-js/style.css b/test/configCases/html/css-imported-from-js/style.css new file mode 100644 index 00000000000..49d13132599 --- /dev/null +++ b/test/configCases/html/css-imported-from-js/style.css @@ -0,0 +1 @@ +.hero { color: green; } diff --git a/test/configCases/html/css-imported-from-js/test.config.js b/test/configCases/html/css-imported-from-js/test.config.js new file mode 100644 index 00000000000..1f8d2c48be8 --- /dev/null +++ b/test/configCases/html/css-imported-from-js/test.config.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + findBundle() { + return ["./test.js"]; + } +}; diff --git a/test/configCases/html/css-imported-from-js/test.js b/test/configCases/html/css-imported-from-js/test.js new file mode 100644 index 00000000000..dcd78a50a28 --- /dev/null +++ b/test/configCases/html/css-imported-from-js/test.js @@ -0,0 +1,57 @@ +const fs = require("fs"); +const path = require("path"); + +const readFile = (name) => + fs.readFileSync(path.resolve(__dirname, name), "utf-8"); + +it("should emit a for the entry chunk's CSS file, copying CSP/fetch attributes from the originating + +
    Box
    + +" +`; diff --git a/test/configCases/html/css-mixed-link-and-js-import/__snapshots__/ConfigTest.snap b/test/configCases/html/css-mixed-link-and-js-import/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..ea3e1721fd1 --- /dev/null +++ b/test/configCases/html/css-mixed-link-and-js-import/__snapshots__/ConfigTest.snap @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html css-mixed-link-and-js-import exported tests should mix a entry with CSS imported from a + +
    Box
    + +" +`; diff --git a/test/configCases/html/css-mixed-link-and-js-import/entry.js b/test/configCases/html/css-mixed-link-and-js-import/entry.js new file mode 100644 index 00000000000..41c9514594b --- /dev/null +++ b/test/configCases/html/css-mixed-link-and-js-import/entry.js @@ -0,0 +1,2 @@ +import "./imported.css"; +import "./imports-shared.css"; diff --git a/test/configCases/html/css-mixed-link-and-js-import/imported.css b/test/configCases/html/css-mixed-link-and-js-import/imported.css new file mode 100644 index 00000000000..85d38eaa1da --- /dev/null +++ b/test/configCases/html/css-mixed-link-and-js-import/imported.css @@ -0,0 +1,2 @@ +.hero { color: green; } +.imported { content: "imported"; } diff --git a/test/configCases/html/css-mixed-link-and-js-import/imports-shared.css b/test/configCases/html/css-mixed-link-and-js-import/imports-shared.css new file mode 100644 index 00000000000..eda780063af --- /dev/null +++ b/test/configCases/html/css-mixed-link-and-js-import/imports-shared.css @@ -0,0 +1,2 @@ +@import "./shared.css"; +.imports-shared { content: "imports-shared"; } diff --git a/test/configCases/html/css-mixed-link-and-js-import/linked.css b/test/configCases/html/css-mixed-link-and-js-import/linked.css new file mode 100644 index 00000000000..7f10ce8716a --- /dev/null +++ b/test/configCases/html/css-mixed-link-and-js-import/linked.css @@ -0,0 +1,2 @@ +.hero { color: red; } +.linked { content: "linked"; } diff --git a/test/configCases/html/css-mixed-link-and-js-import/page.html b/test/configCases/html/css-mixed-link-and-js-import/page.html new file mode 100644 index 00000000000..c71177ef1c1 --- /dev/null +++ b/test/configCases/html/css-mixed-link-and-js-import/page.html @@ -0,0 +1,9 @@ + + + +Mixed: link stylesheet + JS-imported CSS + + + +
    Box
    + diff --git a/test/configCases/html/css-mixed-link-and-js-import/shared.css b/test/configCases/html/css-mixed-link-and-js-import/shared.css new file mode 100644 index 00000000000..51189b33a60 --- /dev/null +++ b/test/configCases/html/css-mixed-link-and-js-import/shared.css @@ -0,0 +1 @@ +.shared { content: "shared"; } diff --git a/test/configCases/html/css-mixed-link-and-js-import/test.config.js b/test/configCases/html/css-mixed-link-and-js-import/test.config.js new file mode 100644 index 00000000000..1f8d2c48be8 --- /dev/null +++ b/test/configCases/html/css-mixed-link-and-js-import/test.config.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + findBundle() { + return ["./test.js"]; + } +}; diff --git a/test/configCases/html/css-mixed-link-and-js-import/test.js b/test/configCases/html/css-mixed-link-and-js-import/test.js new file mode 100644 index 00000000000..f4de7a5cc94 --- /dev/null +++ b/test/configCases/html/css-mixed-link-and-js-import/test.js @@ -0,0 +1,74 @@ +const fs = require("fs"); +const path = require("path"); + +const readFile = (name) => + fs.readFileSync(path.resolve(__dirname, name), "utf-8"); + +it("should mix a entry with CSS imported from a + + + diff --git a/test/configCases/html/css-multi-entry-order/test.config.js b/test/configCases/html/css-multi-entry-order/test.config.js new file mode 100644 index 00000000000..1f8d2c48be8 --- /dev/null +++ b/test/configCases/html/css-multi-entry-order/test.config.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + findBundle() { + return ["./test.js"]; + } +}; diff --git a/test/configCases/html/css-multi-entry-order/test.js b/test/configCases/html/css-multi-entry-order/test.js new file mode 100644 index 00000000000..3d1038d6338 --- /dev/null +++ b/test/configCases/html/css-multi-entry-order/test.js @@ -0,0 +1,23 @@ +const fs = require("fs"); +const path = require("path"); + +const readFile = (name) => + fs.readFileSync(path.resolve(__dirname, name), "utf-8"); + +it("emits every stylesheet before any + +
    Box
    + +" +`; diff --git a/test/configCases/html/css-runtime-and-split-chunks/__snapshots__/ConfigTest.snap b/test/configCases/html/css-runtime-and-split-chunks/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..3b4714797fb --- /dev/null +++ b/test/configCases/html/css-runtime-and-split-chunks/__snapshots__/ConfigTest.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html css-runtime-and-split-chunks exported tests should preserve runtime -> vendor -> entry script order while still emitting CSS s before the scripts 1`] = ` +" + + +HTML entry with runtimeChunk + splitChunks + JS-imported CSS + + +
    Box
    + +" +`; diff --git a/test/configCases/html/css-runtime-and-split-chunks/app-style.css b/test/configCases/html/css-runtime-and-split-chunks/app-style.css new file mode 100644 index 00000000000..49d13132599 --- /dev/null +++ b/test/configCases/html/css-runtime-and-split-chunks/app-style.css @@ -0,0 +1 @@ +.hero { color: green; } diff --git a/test/configCases/html/css-runtime-and-split-chunks/entry.js b/test/configCases/html/css-runtime-and-split-chunks/entry.js new file mode 100644 index 00000000000..e8de0db3641 --- /dev/null +++ b/test/configCases/html/css-runtime-and-split-chunks/entry.js @@ -0,0 +1,3 @@ +import "./vendor"; +import "./vendor-style.css"; +import "./app-style.css"; diff --git a/test/configCases/html/css-runtime-and-split-chunks/page.html b/test/configCases/html/css-runtime-and-split-chunks/page.html new file mode 100644 index 00000000000..4f50ac19cc3 --- /dev/null +++ b/test/configCases/html/css-runtime-and-split-chunks/page.html @@ -0,0 +1,8 @@ + + + +HTML entry with runtimeChunk + splitChunks + JS-imported CSS + + +
    Box
    + diff --git a/test/configCases/html/css-runtime-and-split-chunks/test.config.js b/test/configCases/html/css-runtime-and-split-chunks/test.config.js new file mode 100644 index 00000000000..1f8d2c48be8 --- /dev/null +++ b/test/configCases/html/css-runtime-and-split-chunks/test.config.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + findBundle() { + return ["./test.js"]; + } +}; diff --git a/test/configCases/html/css-runtime-and-split-chunks/test.js b/test/configCases/html/css-runtime-and-split-chunks/test.js new file mode 100644 index 00000000000..dde6cec8815 --- /dev/null +++ b/test/configCases/html/css-runtime-and-split-chunks/test.js @@ -0,0 +1,51 @@ +const fs = require("fs"); +const path = require("path"); + +const readFile = (name) => + fs.readFileSync(path.resolve(__dirname, name), "utf-8"); + +it("should preserve runtime -> vendor -> entry script order while still emitting CSS s before the scripts", () => { + const extracted = readFile("page.html"); + expect(extracted).toMatchSnapshot(); + + const scriptSrcRe = / + +
    Box
    + +" +`; diff --git a/test/configCases/html/css-split-chunks-order/__snapshots__/ConfigTest.snap b/test/configCases/html/css-split-chunks-order/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..01e7e347fdc --- /dev/null +++ b/test/configCases/html/css-split-chunks-order/__snapshots__/ConfigTest.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html css-split-chunks-order exported tests should reference split CSS chunks with and the JS chunk with + +
    Box
    + +" +`; diff --git a/test/configCases/html/css-split-chunks-order/entry.js b/test/configCases/html/css-split-chunks-order/entry.js new file mode 100644 index 00000000000..02493ff10c0 --- /dev/null +++ b/test/configCases/html/css-split-chunks-order/entry.js @@ -0,0 +1,2 @@ +import "./style1.css"; +import "./style2.css"; diff --git a/test/configCases/html/css-split-chunks-order/page.html b/test/configCases/html/css-split-chunks-order/page.html new file mode 100644 index 00000000000..513844e08ef --- /dev/null +++ b/test/configCases/html/css-split-chunks-order/page.html @@ -0,0 +1,8 @@ + + + +HTML entry with split CSS chunks + + +
    Box
    + diff --git a/test/configCases/html/css-split-chunks-order/style1.css b/test/configCases/html/css-split-chunks-order/style1.css new file mode 100644 index 00000000000..21455b167f3 --- /dev/null +++ b/test/configCases/html/css-split-chunks-order/style1.css @@ -0,0 +1 @@ +.hero { color: red; } diff --git a/test/configCases/html/css-split-chunks-order/style2.css b/test/configCases/html/css-split-chunks-order/style2.css new file mode 100644 index 00000000000..49d13132599 --- /dev/null +++ b/test/configCases/html/css-split-chunks-order/style2.css @@ -0,0 +1 @@ +.hero { color: green; } diff --git a/test/configCases/html/css-split-chunks-order/test.config.js b/test/configCases/html/css-split-chunks-order/test.config.js new file mode 100644 index 00000000000..1f8d2c48be8 --- /dev/null +++ b/test/configCases/html/css-split-chunks-order/test.config.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + findBundle() { + return ["./test.js"]; + } +}; diff --git a/test/configCases/html/css-split-chunks-order/test.js b/test/configCases/html/css-split-chunks-order/test.js new file mode 100644 index 00000000000..cab4fa2356c --- /dev/null +++ b/test/configCases/html/css-split-chunks-order/test.js @@ -0,0 +1,40 @@ +const fs = require("fs"); +const path = require("path"); + +const readFile = (name) => + fs.readFileSync(path.resolve(__dirname, name), "utf-8"); + +it("should reference split CSS chunks with and the JS chunk with + + +

    Hello

    +
    Box
    + + +" +`; diff --git a/test/configCases/html/entry-script-and-stylesheet/__snapshots__/ConfigTest.snap b/test/configCases/html/entry-script-and-stylesheet/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..b5b8322dd20 --- /dev/null +++ b/test/configCases/html/entry-script-and-stylesheet/__snapshots__/ConfigTest.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html entry-script-and-stylesheet exported tests should emit page.html referencing both the bundled JS and CSS chunks 1`] = ` +" + + +HTML entry with script and stylesheet + + + + +

    Hello

    +
    Box
    + + +" +`; diff --git a/test/configCases/html/entry-script-and-stylesheet/app.js b/test/configCases/html/entry-script-and-stylesheet/app.js new file mode 100644 index 00000000000..a51631e198a --- /dev/null +++ b/test/configCases/html/entry-script-and-stylesheet/app.js @@ -0,0 +1 @@ +module.exports = "entry-script-and-stylesheet"; diff --git a/test/configCases/html/entry-script-and-stylesheet/page.html b/test/configCases/html/entry-script-and-stylesheet/page.html new file mode 100644 index 00000000000..a958c107b27 --- /dev/null +++ b/test/configCases/html/entry-script-and-stylesheet/page.html @@ -0,0 +1,12 @@ + + + +HTML entry with script and stylesheet + + + + +

    Hello

    +
    Box
    + + diff --git a/test/configCases/html/entry-script-and-stylesheet/styles.css b/test/configCases/html/entry-script-and-stylesheet/styles.css new file mode 100644 index 00000000000..21fd752bc88 --- /dev/null +++ b/test/configCases/html/entry-script-and-stylesheet/styles.css @@ -0,0 +1,3 @@ +.box { + color: green; +} diff --git a/test/configCases/html/entry-script-and-stylesheet/test.config.js b/test/configCases/html/entry-script-and-stylesheet/test.config.js new file mode 100644 index 00000000000..1f8d2c48be8 --- /dev/null +++ b/test/configCases/html/entry-script-and-stylesheet/test.config.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + findBundle() { + return ["./test.js"]; + } +}; diff --git a/test/configCases/html/entry-script-and-stylesheet/test.js b/test/configCases/html/entry-script-and-stylesheet/test.js new file mode 100644 index 00000000000..48946d57e8b --- /dev/null +++ b/test/configCases/html/entry-script-and-stylesheet/test.js @@ -0,0 +1,25 @@ +const fs = require("fs"); +const path = require("path"); + +const readFile = (name) => + fs.readFileSync(path.resolve(__dirname, name), "utf-8"); + +it("should emit page.html referencing both the bundled JS and CSS chunks", () => { + const html = readFile("page.html"); + expect(html).toMatchSnapshot(); + // Original source references were rewritten away. + expect(html).not.toContain('src="./app.js"'); + expect(html).not.toContain('href="./styles.css"'); + // ` + + +

    Hello

    + + + + +" +`; diff --git a/test/configCases/html/extract-runtime-chunk/__snapshots__/ConfigTest.snap b/test/configCases/html/extract-runtime-chunk/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..f8caceb8ae4 --- /dev/null +++ b/test/configCases/html/extract-runtime-chunk/__snapshots__/ConfigTest.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html extract-runtime-chunk exported tests should load the shared runtime chunk exactly once even when multiple + + +

    Hello

    + + + + +" +`; diff --git a/test/configCases/html/extract-runtime-chunk/entry.js b/test/configCases/html/extract-runtime-chunk/entry.js new file mode 100644 index 00000000000..6555fae7ca0 --- /dev/null +++ b/test/configCases/html/extract-runtime-chunk/entry.js @@ -0,0 +1 @@ +module.exports = "entry"; diff --git a/test/configCases/html/extract-runtime-chunk/index.js b/test/configCases/html/extract-runtime-chunk/index.js new file mode 100644 index 00000000000..5bd36d82a29 --- /dev/null +++ b/test/configCases/html/extract-runtime-chunk/index.js @@ -0,0 +1,54 @@ +const fs = require("fs"); +const path = require("path"); + +require("./page.html"); + +const readFile = (name) => + fs.readFileSync(path.resolve(__dirname, name), "utf-8"); + +it("should load the shared runtime chunk exactly once even when multiple + + +

    Hello

    + + + + diff --git a/test/configCases/html/extract-runtime-chunk/test.config.js b/test/configCases/html/extract-runtime-chunk/test.config.js new file mode 100644 index 00000000000..0fa717e15cd --- /dev/null +++ b/test/configCases/html/extract-runtime-chunk/test.config.js @@ -0,0 +1,12 @@ +"use strict"; + +const fs = require("fs"); + +// `output.filename` is `[name].js` so the test entry bundle lives at +// `main.js`, not `bundle0.js`. Tell the harness where to find it. +module.exports = { + findBundle(_i, options) { + const files = fs.readdirSync(options.output.path); + return files.includes("main.js") ? ["./main.js"] : undefined; + } +}; diff --git a/test/configCases/html/extract-runtime-chunk/webpack.config.js b/test/configCases/html/extract-runtime-chunk/webpack.config.js new file mode 100644 index 00000000000..4c50fb6071e --- /dev/null +++ b/test/configCases/html/extract-runtime-chunk/webpack.config.js @@ -0,0 +1,34 @@ +"use strict"; + +// Combines `extract: true` with `optimization.runtimeChunk` targeting only +// the synthetic entries that HtmlModulesPlugin creates for ` + + +

    Hello

    + + +" +`; diff --git a/test/configCases/html/extract-split-chunks/__snapshots__/ConfigTest.snap b/test/configCases/html/extract-split-chunks/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..bd217c0df0c --- /dev/null +++ b/test/configCases/html/extract-split-chunks/__snapshots__/ConfigTest.snap @@ -0,0 +1,15 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html extract-split-chunks exported tests should reference both the split-out vendor chunk and the entry chunk 1`] = ` +" + + +Extracted HTML with split chunks + + + +

    Hello

    + + +" +`; diff --git a/test/configCases/html/extract-split-chunks/entry.js b/test/configCases/html/extract-split-chunks/entry.js new file mode 100644 index 00000000000..7f26b820c93 --- /dev/null +++ b/test/configCases/html/extract-split-chunks/entry.js @@ -0,0 +1,3 @@ +var vendor = require("./vendor"); + +module.exports = "entry:" + vendor; diff --git a/test/configCases/html/extract-split-chunks/index.js b/test/configCases/html/extract-split-chunks/index.js new file mode 100644 index 00000000000..b7daa627f6c --- /dev/null +++ b/test/configCases/html/extract-split-chunks/index.js @@ -0,0 +1,25 @@ +const fs = require("fs"); +const path = require("path"); + +require("./page.html"); + +const readFile = (name) => + fs.readFileSync(path.resolve(__dirname, name), "utf-8"); + +it("should reference both the split-out vendor chunk and the entry chunk", () => { + const extracted = readFile("page.html"); + expect(extracted).toMatchSnapshot(); + // Use exec()-in-a-loop instead of String.prototype.matchAll for + // compatibility with legacy Node.js. + const scriptSrcRe = / + + +

    Hello

    + + diff --git a/test/configCases/html/extract-split-chunks/test.config.js b/test/configCases/html/extract-split-chunks/test.config.js new file mode 100644 index 00000000000..bc56a8d0a0d --- /dev/null +++ b/test/configCases/html/extract-split-chunks/test.config.js @@ -0,0 +1,16 @@ +"use strict"; + +const fs = require("fs"); + +// `output.filename` is `[name].js` so the test entry bundle lives at +// `main.js`, not `bundle0.js`. Tell the harness where to find it; we also +// load the vendor chunk first so its module factories are registered before +// `main.js` requires them at runtime. +module.exports = { + findBundle(_i, options) { + const files = fs.readdirSync(options.output.path); + if (!files.includes("main.js")) return undefined; + const vendor = files.find((f) => f === "vendor.js"); + return vendor ? [`./${vendor}`, "./main.js"] : ["./main.js"]; + } +}; diff --git a/test/configCases/html/extract-split-chunks/vendor.js b/test/configCases/html/extract-split-chunks/vendor.js new file mode 100644 index 00000000000..df530057b43 --- /dev/null +++ b/test/configCases/html/extract-split-chunks/vendor.js @@ -0,0 +1 @@ +module.exports = "vendor-module"; diff --git a/test/configCases/html/extract-split-chunks/webpack.config.js b/test/configCases/html/extract-split-chunks/webpack.config.js new file mode 100644 index 00000000000..723dcde54f3 --- /dev/null +++ b/test/configCases/html/extract-split-chunks/webpack.config.js @@ -0,0 +1,37 @@ +"use strict"; + +// Force the entry's heavy dependency into a separate chunk via +// `optimization.splitChunks`. The extracted HTML must include a script tag +// for the split-out vendor chunk in addition to the entry chunk so the +// browser loads both. + +/** @type {import("../../../../").Configuration} */ +module.exports = { + output: { + filename: "[name].js", + chunkFilename: "[name].chunk.js" + }, + optimization: { + chunkIds: "named", + splitChunks: { + cacheGroups: { + vendor: { + test: /vendor\.js$/, + name: "vendor", + chunks: "all", + enforce: true + } + } + } + }, + module: { + generator: { + html: { + extract: true + } + } + }, + experiments: { + html: true + } +}; diff --git a/test/configCases/html/extract/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/extract/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..1bcd735f5bf --- /dev/null +++ b/test/configCases/html/extract/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html extract exported tests should emit page.html with rewritten URLs alongside the JS bundle 1`] = ` +" + + +Extracted HTML + + + + +

    Hello

    +\\"image\\" + + +" +`; diff --git a/test/configCases/html/extract/__snapshots__/ConfigTest.snap b/test/configCases/html/extract/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..ae9b8dc8c25 --- /dev/null +++ b/test/configCases/html/extract/__snapshots__/ConfigTest.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html extract exported tests should emit page.html with rewritten URLs alongside the JS bundle 1`] = ` +" + + +Extracted HTML + + + + +

    Hello

    +\\"image\\" + + +" +`; diff --git a/test/configCases/html/extract/entry.js b/test/configCases/html/extract/entry.js new file mode 100644 index 00000000000..6555fae7ca0 --- /dev/null +++ b/test/configCases/html/extract/entry.js @@ -0,0 +1 @@ +module.exports = "entry"; diff --git a/test/configCases/html/extract/icon.png b/test/configCases/html/extract/icon.png new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/configCases/html/extract/image.png b/test/configCases/html/extract/image.png new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/configCases/html/extract/index.js b/test/configCases/html/extract/index.js new file mode 100644 index 00000000000..687d70e1a53 --- /dev/null +++ b/test/configCases/html/extract/index.js @@ -0,0 +1,31 @@ +const fs = require("fs"); +const path = require("path"); + +const page = require("./page.html"); + +const readFile = (name) => + fs.readFileSync(path.resolve(__dirname, name), "utf-8"); + +// `output.htmlFilename` defaults to `output.filename` with `.js` swapped for +// `.html`, mirroring the CSS pipeline. `[name]` in the template resolves to +// the HTML source's basename (e.g. `page` for `./page.html`) so multiple +// HTML modules imported from the same chunk don't collide on output. +it("should emit page.html with rewritten URLs alongside the JS bundle", () => { + const extracted = readFile("page.html"); + expect(extracted).toMatchSnapshot(); + // URLs were rewritten — no original relative paths remain. + expect(extracted).not.toContain('src="./entry.js"'); + expect(extracted).not.toContain('href="./icon.png"'); + expect(extracted).not.toContain('src="./image.png"'); + // ` + + +

    Hello

    +image + + diff --git a/test/configCases/html/extract/test.config.js b/test/configCases/html/extract/test.config.js new file mode 100644 index 00000000000..0fa717e15cd --- /dev/null +++ b/test/configCases/html/extract/test.config.js @@ -0,0 +1,12 @@ +"use strict"; + +const fs = require("fs"); + +// `output.filename` is `[name].js` so the test entry bundle lives at +// `main.js`, not `bundle0.js`. Tell the harness where to find it. +module.exports = { + findBundle(_i, options) { + const files = fs.readdirSync(options.output.path); + return files.includes("main.js") ? ["./main.js"] : undefined; + } +}; diff --git a/test/configCases/html/extract/webpack.config.js b/test/configCases/html/extract/webpack.config.js new file mode 100644 index 00000000000..b8637beeea0 --- /dev/null +++ b/test/configCases/html/extract/webpack.config.js @@ -0,0 +1,22 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + output: { + filename: "[name].js", + chunkFilename: "[name].chunk.js" + }, + optimization: { + chunkIds: "named" + }, + module: { + generator: { + html: { + extract: true + } + } + }, + experiments: { + html: true + } +}; diff --git a/test/configCases/html/full-lexer-integration/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/full-lexer-integration/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..1e14eb6f4b4 --- /dev/null +++ b/test/configCases/html/full-lexer-integration/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,86 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html full-lexer-integration exported tests should compile a kitchen-sink HTML document through all lexer states 1`] = ` +" + + + + +Kitchen & Sink — Full Lexer Test + + + + + + + + +
    Double quoted
    +
    Single quoted
    +
    Unquoted attributes
    + +\\"test +
    +
    + + + +Entity in URL +Single quoted URL +Unquoted URL + +

    Named entities: & < > "

    +

    Decimal numeric: ABC

    +

    Hex numeric: ABC

    +

    Upper hex: DE

    +

    Bare ampersand: Tom & Jerry

    +

    Unknown entity: &unknown; stays raw

    +

    Missing semicolon: & without semicolon

    +

    Empty numeric: &#; and &#x;

    +

    Large codepoint: ϧ

    + + + + + + + + + + +
    + Mixed attribute styles +
    + +ignored cdata]]> + +

    Self-closing voids:


    + +
    +
    +
    + Deep nesting +
    +
    +
    + +

    Text

    +

    Text

    +

    Text

    + + + +" +`; diff --git a/test/configCases/html/full-lexer-integration/__snapshots__/ConfigTest.snap b/test/configCases/html/full-lexer-integration/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..390841e4e0a --- /dev/null +++ b/test/configCases/html/full-lexer-integration/__snapshots__/ConfigTest.snap @@ -0,0 +1,86 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html full-lexer-integration exported tests should compile a kitchen-sink HTML document through all lexer states 1`] = ` +" + + + + +Kitchen & Sink — Full Lexer Test + + + + + + + + +
    Double quoted
    +
    Single quoted
    +
    Unquoted attributes
    + +\\"test +
    +
    + + + +Entity in URL +Single quoted URL +Unquoted URL + +

    Named entities: & < > "

    +

    Decimal numeric: ABC

    +

    Hex numeric: ABC

    +

    Upper hex: DE

    +

    Bare ampersand: Tom & Jerry

    +

    Unknown entity: &unknown; stays raw

    +

    Missing semicolon: & without semicolon

    +

    Empty numeric: &#; and &#x;

    +

    Large codepoint: ϧ

    + + + + + + + + + + +
    + Mixed attribute styles +
    + +ignored cdata]]> + +

    Self-closing voids:


    + +
    +
    +
    + Deep nesting +
    +
    +
    + +

    Text

    +

    Text

    +

    Text

    + + + +" +`; diff --git a/test/configCases/html/full-lexer-integration/image.png b/test/configCases/html/full-lexer-integration/image.png new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/configCases/html/full-lexer-integration/index.js b/test/configCases/html/full-lexer-integration/index.js new file mode 100644 index 00000000000..a12032fca17 --- /dev/null +++ b/test/configCases/html/full-lexer-integration/index.js @@ -0,0 +1,6 @@ +import page from "./page.html"; + +it("should compile a kitchen-sink HTML document through all lexer states", () => { + expect(typeof page).toBe("string"); + expect(page).toMatchSnapshot(); +}); diff --git a/test/configCases/html/full-lexer-integration/page.html b/test/configCases/html/full-lexer-integration/page.html new file mode 100644 index 00000000000..bc7ea50056b --- /dev/null +++ b/test/configCases/html/full-lexer-integration/page.html @@ -0,0 +1,81 @@ + + + + + +Kitchen & Sink — Full Lexer Test + + + + + + + + +
    Double quoted
    +
    Single quoted
    +
    Unquoted attributes
    + +test image +
    +
    + + + +Entity in URL +Single quoted URL +Unquoted URL + +

    Named entities: & < > "

    +

    Decimal numeric: ABC

    +

    Hex numeric: ABC

    +

    Upper hex: DE

    +

    Bare ampersand: Tom & Jerry

    +

    Unknown entity: &unknown; stays raw

    +

    Missing semicolon: & without semicolon

    +

    Empty numeric: &#; and &#x;

    +

    Large codepoint: ϧ

    + + + + + + + + + + +
    + Mixed attribute styles +
    + +ignored cdata]]> + +

    Self-closing voids:


    + +
    +
    +
    + Deep nesting +
    +
    +
    + +

    Text

    +

    Text

    +

    Text

    + + + diff --git a/test/configCases/html/full-lexer-integration/simple.html b/test/configCases/html/full-lexer-integration/simple.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/configCases/html/full-lexer-integration/webpack.config.js b/test/configCases/html/full-lexer-integration/webpack.config.js new file mode 100644 index 00000000000..bc4cbc8fe56 --- /dev/null +++ b/test/configCases/html/full-lexer-integration/webpack.config.js @@ -0,0 +1,9 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: "web", + experiments: { + html: true + } +}; diff --git a/test/configCases/html/html-entry-point-css/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/html-entry-point-css/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..f7abf32798b --- /dev/null +++ b/test/configCases/html/html-entry-point-css/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,16 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html html-entry-point-css exported tests should emit page.html with a pointing to the bundled CSS chunk 1`] = ` +" + + +HTML entry point with CSS + + + +

    Hello

    +
    Box
    + + +" +`; diff --git a/test/configCases/html/html-entry-point-css/__snapshots__/ConfigTest.snap b/test/configCases/html/html-entry-point-css/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..cd9ecc806c3 --- /dev/null +++ b/test/configCases/html/html-entry-point-css/__snapshots__/ConfigTest.snap @@ -0,0 +1,16 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html html-entry-point-css exported tests should emit page.html with a pointing to the bundled CSS chunk 1`] = ` +" + + +HTML entry point with CSS + + + +

    Hello

    +
    Box
    + + +" +`; diff --git a/test/configCases/html/html-entry-point-css/image.png b/test/configCases/html/html-entry-point-css/image.png new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/configCases/html/html-entry-point-css/page.html b/test/configCases/html/html-entry-point-css/page.html new file mode 100644 index 00000000000..4918c198d3e --- /dev/null +++ b/test/configCases/html/html-entry-point-css/page.html @@ -0,0 +1,11 @@ + + + +HTML entry point with CSS + + + +

    Hello

    +
    Box
    + + diff --git a/test/configCases/html/html-entry-point-css/style.css b/test/configCases/html/html-entry-point-css/style.css new file mode 100644 index 00000000000..70997c66367 --- /dev/null +++ b/test/configCases/html/html-entry-point-css/style.css @@ -0,0 +1,4 @@ +.hero { + background: url("./image.png") no-repeat; + color: red; +} diff --git a/test/configCases/html/html-entry-point-css/test.config.js b/test/configCases/html/html-entry-point-css/test.config.js new file mode 100644 index 00000000000..1f8d2c48be8 --- /dev/null +++ b/test/configCases/html/html-entry-point-css/test.config.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + findBundle() { + return ["./test.js"]; + } +}; diff --git a/test/configCases/html/html-entry-point-css/test.js b/test/configCases/html/html-entry-point-css/test.js new file mode 100644 index 00000000000..2f6614127bd --- /dev/null +++ b/test/configCases/html/html-entry-point-css/test.js @@ -0,0 +1,25 @@ +const fs = require("fs"); +const path = require("path"); + +const readFile = (name) => + fs.readFileSync(path.resolve(__dirname, name), "utf-8"); + +it("should emit page.html with a pointing to the bundled CSS chunk", () => { + const extracted = readFile("page.html"); + expect(extracted).toMatchSnapshot(); + // Original `./style.css` was rewritten away. + expect(extracted).not.toContain('href="./style.css"'); + // The `` points at the emitted CSS chunk. + const linkMatch = extracted.match( + /`. With `experiments.css` enabled, the +// CSS goes through webpack's CSS pipeline (so `url()` references are +// resolved into asset modules), and the extracted HTML should reference +// the emitted `.css` chunk rather than the original `./style.css`. The +// `url()` asset URL in the emitted CSS should also be a hashed filename. + +const fs = require("fs"); +const path = require("path"); +const webpack = require("../../../../"); + +/** @type {import("../../../../").Configuration} */ +module.exports = { + // `target: "web"` makes the default CSS generator emit `.css` files + // (with `target: "async-node"`, `exportsOnly` defaults to `true` and + // CSS chunks aren't emitted). + target: "web", + entry: { + page: "./page.html" + }, + output: { + filename: "[name].js", + chunkFilename: "[name].chunk.js" + }, + optimization: { + chunkIds: "named" + }, + experiments: { + html: true, + css: true + }, + plugins: [ + { + apply(compiler) { + compiler.hooks.compilation.tap("Test", (compilation) => { + compilation.hooks.processAssets.tap( + { + name: "copy-test", + stage: + compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL + }, + () => { + const data = fs.readFileSync(path.resolve(__dirname, "test.js")); + compilation.emitAsset( + "test.js", + new webpack.sources.RawSource(data) + ); + } + ); + }); + } + } + ] +}; diff --git a/test/configCases/html/html-entry-point/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/html-entry-point/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..17a185be9f3 --- /dev/null +++ b/test/configCases/html/html-entry-point/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,15 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html html-entry-point exported tests should emit page.html for an HTML entry without explicit extract: true 1`] = ` +" + + +HTML entry point + + + +\\"image\\" + + +" +`; diff --git a/test/configCases/html/html-entry-point/__snapshots__/ConfigTest.snap b/test/configCases/html/html-entry-point/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..81017407bfa --- /dev/null +++ b/test/configCases/html/html-entry-point/__snapshots__/ConfigTest.snap @@ -0,0 +1,15 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html html-entry-point exported tests should emit page.html for an HTML entry without explicit extract: true 1`] = ` +" + + +HTML entry point + + + +\\"image\\" + + +" +`; diff --git a/test/configCases/html/html-entry-point/image.png b/test/configCases/html/html-entry-point/image.png new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/configCases/html/html-entry-point/page.html b/test/configCases/html/html-entry-point/page.html new file mode 100644 index 00000000000..59be29bd5ca --- /dev/null +++ b/test/configCases/html/html-entry-point/page.html @@ -0,0 +1,10 @@ + + + +HTML entry point + + + +image + + diff --git a/test/configCases/html/html-entry-point/script.js b/test/configCases/html/html-entry-point/script.js new file mode 100644 index 00000000000..465294efedb --- /dev/null +++ b/test/configCases/html/html-entry-point/script.js @@ -0,0 +1 @@ +module.exports = "html-entry-script"; diff --git a/test/configCases/html/html-entry-point/test.config.js b/test/configCases/html/html-entry-point/test.config.js new file mode 100644 index 00000000000..b3988929bc0 --- /dev/null +++ b/test/configCases/html/html-entry-point/test.config.js @@ -0,0 +1,9 @@ +"use strict"; + +// The HTML entry has no top-level `it(...)` body — assertions live in the +// emitted `test.js` asset, which the harness loads directly. +module.exports = { + findBundle() { + return ["./test.js"]; + } +}; diff --git a/test/configCases/html/html-entry-point/test.js b/test/configCases/html/html-entry-point/test.js new file mode 100644 index 00000000000..b6e47a6d085 --- /dev/null +++ b/test/configCases/html/html-entry-point/test.js @@ -0,0 +1,14 @@ +const fs = require("fs"); +const path = require("path"); + +const readFile = (name) => + fs.readFileSync(path.resolve(__dirname, name), "utf-8"); + +it("should emit page.html for an HTML entry without explicit extract: true", () => { + const extracted = readFile("page.html"); + expect(extracted).toMatchSnapshot(); + expect(extracted).not.toContain('src="./script.js"'); + expect(extracted).not.toContain('src="./image.png"'); + expect(extracted).toMatch(/ + + + + + + +

    Hi

    + + +" +`; diff --git a/test/configCases/html/inline-script-classic/__snapshots__/ConfigTest.snap b/test/configCases/html/inline-script-classic/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..a60ab586826 --- /dev/null +++ b/test/configCases/html/inline-script-classic/__snapshots__/ConfigTest.snap @@ -0,0 +1,179 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html inline-script-classic exported tests should emit IIFE-wrapped chunks for inline + + + + + + +

    Hi

    + + +" +`; diff --git a/test/configCases/html/inline-script-classic/index.js b/test/configCases/html/inline-script-classic/index.js new file mode 100644 index 00000000000..78c5405a015 --- /dev/null +++ b/test/configCases/html/inline-script-classic/index.js @@ -0,0 +1,111 @@ +const fs = require("fs"); +const path = require("path"); + +const page = require("./page.html"); + +const readChunk = (name) => + fs.readFileSync(path.resolve(__dirname, name), "utf-8"); + +// `require("./page.html")` always returns the HTML body as a string, but +// static analysis can't see through the HTML module type, so normalize +// once at the top so the rest of the file accesses a concrete string. +const pageContent = typeof page === "string" ? page : ""; + +// `String.prototype.matchAll` requires Node 12+, but this test stays on +// Node 10-compatible CJS so we collect all matches via a `regex.exec` +// loop with the `g` flag. +const collectMatches = (str, regex) => { + const out = []; + let m; + while ((m = regex.exec(str)) !== null) out.push(m); + return out; +}; + +it("should rewrite inline + + + + + + +

    Hi

    + + diff --git a/test/configCases/html/inline-script-classic/webpack.config.js b/test/configCases/html/inline-script-classic/webpack.config.js new file mode 100644 index 00000000000..772bb8f208e --- /dev/null +++ b/test/configCases/html/inline-script-classic/webpack.config.js @@ -0,0 +1,28 @@ +"use strict"; + +// No `experiments.outputModule` and no `output.module` — emitted chunks are +// classic IIFE-wrapped scripts. In this mode the parser must NOT auto- +// upgrade classic inline ` + + + + + + + +

    Hi

    + + + +" +`; diff --git a/test/configCases/html/inline-script/__snapshots__/ConfigTest.snap b/test/configCases/html/inline-script/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..1a51f9d64a0 --- /dev/null +++ b/test/configCases/html/inline-script/__snapshots__/ConfigTest.snap @@ -0,0 +1,39 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html inline-script exported tests should bundle inline + + + + + + + +

    Hi

    + + + +" +`; diff --git a/test/configCases/html/inline-script/index.js b/test/configCases/html/inline-script/index.js new file mode 100644 index 00000000000..5e05031bdff --- /dev/null +++ b/test/configCases/html/inline-script/index.js @@ -0,0 +1,106 @@ +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; + +import page from "./page.html"; + +const here = path.dirname(fileURLToPath(import.meta.url)); + +const readChunk = (name) => fs.readFileSync(path.resolve(here, name), "utf-8"); + +// `import page from "./page.html"` always returns the HTML body as a +// string, but static analysis can't see through the HTML module type, so +// it flags property access on `page` as possibly-undefined. Normalize +// once at the top so the rest of the file accesses a concrete string. +const pageContent = typeof page === "string" ? page : ""; + +// Document-order list of every inline-script chunk url emitted into the page. +const scriptChunkUrls = [ + ...pageContent.matchAll(/]*\bsrc="(__html_[^"]+)"/g) +].map((m) => m[1]); + +it("should bundle inline + + + + + + + +

    Hi

    + + + diff --git a/test/configCases/html/inline-script/test.filter.js b/test/configCases/html/inline-script/test.filter.js new file mode 100644 index 00000000000..df2522abe50 --- /dev/null +++ b/test/configCases/html/inline-script/test.filter.js @@ -0,0 +1,9 @@ +"use strict"; + +// Tests use `import.meta.url` to locate emitted chunks at runtime, which +// requires the bundle to run as a real ES module. Jest's ESM support +// (via `--experimental-vm-modules`) needs Node 12+. +module.exports = function filter() { + const major = Number(process.versions.node.split(".")[0]); + return major >= 12; +}; diff --git a/test/configCases/html/inline-script/webpack.config.js b/test/configCases/html/inline-script/webpack.config.js new file mode 100644 index 00000000000..73f5fad1ec0 --- /dev/null +++ b/test/configCases/html/inline-script/webpack.config.js @@ -0,0 +1,30 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: ["web", "es2022"], + node: { + __dirname: false, + __filename: false + }, + externalsPresets: { + node: true + }, + module: { + parser: { + javascript: { + importMeta: false + } + } + }, + output: { + chunkFilename: "[name].chunk.js" + }, + optimization: { + chunkIds: "named" + }, + experiments: { + html: true, + outputModule: true + } +}; diff --git a/test/configCases/html/invisible-space/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/invisible-space/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..900b8594208 --- /dev/null +++ b/test/configCases/html/invisible-space/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,6 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html invisible-space exported tests should compile and export html as string 1`] = ` +"
    • \\\\u2028 - 
 Text 

    • \\\\u2029 - 
 Text 

    +" +`; diff --git a/test/configCases/html/invisible-space/__snapshots__/ConfigTest.snap b/test/configCases/html/invisible-space/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..3d8465e3c72 --- /dev/null +++ b/test/configCases/html/invisible-space/__snapshots__/ConfigTest.snap @@ -0,0 +1,6 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html invisible-space exported tests should compile and export html as string 1`] = ` +"
    • \\\\u2028 - 
 Text 

    • \\\\u2029 - 
 Text 

    +" +`; diff --git a/test/configCases/html/invisible-space/index.js b/test/configCases/html/invisible-space/index.js new file mode 100644 index 00000000000..e52e8786661 --- /dev/null +++ b/test/configCases/html/invisible-space/index.js @@ -0,0 +1,5 @@ +import page from "./page.html"; + +it("should compile and export html as string", () => { + expect(page).toMatchSnapshot(); +}); diff --git a/test/configCases/html/invisible-space/page.html b/test/configCases/html/invisible-space/page.html new file mode 100644 index 00000000000..eb5f2ab99b9 --- /dev/null +++ b/test/configCases/html/invisible-space/page.html @@ -0,0 +1 @@ +
    • \u2028 - 
 Text 

    • \u2029 - 
 Text 

    diff --git a/test/configCases/html/invisible-space/webpack.config.js b/test/configCases/html/invisible-space/webpack.config.js new file mode 100644 index 00000000000..bc4cbc8fe56 --- /dev/null +++ b/test/configCases/html/invisible-space/webpack.config.js @@ -0,0 +1,9 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: "web", + experiments: { + html: true + } +}; diff --git a/test/configCases/html/js-css-html/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/js-css-html/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..a038f2751d8 --- /dev/null +++ b/test/configCases/html/js-css-html/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,33 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html js-css-html exported tests should work 1`] = `"https://test.cases/path/handled-image.png"`; + +exports[`ConfigCacheTestCases html js-css-html exported tests should work 2`] = ` +" + + + + + + Document + + +\\"test\\" + + +" +`; + +exports[`ConfigCacheTestCases html js-css-html exported tests should work 3`] = ` +Array [ + "/*!***********************!*\\\\ + !*** css ./style.css ***! + \\\\***********************/ +.class { + background: url(handled-image.png); +} + +", +] +`; diff --git a/test/configCases/html/js-css-html/__snapshots__/ConfigTest.snap b/test/configCases/html/js-css-html/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..4ae3d0dee25 --- /dev/null +++ b/test/configCases/html/js-css-html/__snapshots__/ConfigTest.snap @@ -0,0 +1,33 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html js-css-html exported tests should work 1`] = `"https://test.cases/path/handled-image.png"`; + +exports[`ConfigTestCases html js-css-html exported tests should work 2`] = ` +" + + + + + + Document + + +\\"test\\" + + +" +`; + +exports[`ConfigTestCases html js-css-html exported tests should work 3`] = ` +Array [ + "/*!***********************!*\\\\ + !*** css ./style.css ***! + \\\\***********************/ +.class { + background: url(handled-image.png); +} + +", +] +`; diff --git a/test/configCases/html/js-css-html/image.png b/test/configCases/html/js-css-html/image.png new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/configCases/html/js-css-html/index.js b/test/configCases/html/js-css-html/index.js new file mode 100644 index 00000000000..5b8b4e2b7a5 --- /dev/null +++ b/test/configCases/html/js-css-html/index.js @@ -0,0 +1,19 @@ +import html from "./page.html"; +import "./style.css"; + +it("should work", () => { + const myUrl = new URL("./image.png", import.meta.url); + + expect(myUrl).toMatchSnapshot(); + expect(html).toMatchSnapshot(); + + const links = document.getElementsByTagName("link"); + const css = []; + + // Skip first because import it by default + for (const link of links.slice(1)) { + css.push(link.sheet.css); + } + + expect(css).toMatchSnapshot(); +}); diff --git a/test/configCases/html/js-css-html/page.html b/test/configCases/html/js-css-html/page.html new file mode 100644 index 00000000000..4ce6d0c4949 --- /dev/null +++ b/test/configCases/html/js-css-html/page.html @@ -0,0 +1,13 @@ + + + + + + + Document + + +test + + diff --git a/test/configCases/html/js-css-html/style.css b/test/configCases/html/js-css-html/style.css new file mode 100644 index 00000000000..3c6140f6e35 --- /dev/null +++ b/test/configCases/html/js-css-html/style.css @@ -0,0 +1,3 @@ +.class { + background: url("./image.png"); +} diff --git a/test/configCases/html/js-css-html/test.config.js b/test/configCases/html/js-css-html/test.config.js new file mode 100644 index 00000000000..dbfd4316888 --- /dev/null +++ b/test/configCases/html/js-css-html/test.config.js @@ -0,0 +1,10 @@ +"use strict"; + +module.exports = { + moduleScope(scope) { + const link = scope.window.document.createElement("link"); + link.rel = "stylesheet"; + link.href = `bundle${scope.__STATS_I__}.css`; + scope.window.document.head.appendChild(link); + } +}; diff --git a/test/configCases/html/js-css-html/test.filter.js b/test/configCases/html/js-css-html/test.filter.js new file mode 100644 index 00000000000..f5f225500d8 --- /dev/null +++ b/test/configCases/html/js-css-html/test.filter.js @@ -0,0 +1,10 @@ +"use strict"; + +// Test uses `new URL("./image.png", import.meta.url)` to locate the +// emitted asset at runtime, which requires the bundle to run as a real +// ES module. Jest's ESM support (via `--experimental-vm-modules`) needs +// Node 12+. +module.exports = function filter() { + const major = Number(process.versions.node.split(".")[0]); + return major >= 12; +}; diff --git a/test/configCases/html/js-css-html/webpack.config.js b/test/configCases/html/js-css-html/webpack.config.js new file mode 100644 index 00000000000..b57b96278fc --- /dev/null +++ b/test/configCases/html/js-css-html/webpack.config.js @@ -0,0 +1,13 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: "web", + output: { + assetModuleFilename: "handled-[name][ext]" + }, + experiments: { + html: true, + css: true + } +}; diff --git a/test/configCases/html/legacy-and-obsolete-sources/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/legacy-and-obsolete-sources/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..4c987ad73ea --- /dev/null +++ b/test/configCases/html/legacy-and-obsolete-sources/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html legacy-and-obsolete-sources exported tests should resolve legacy and obsolete source attributes 1`] = ` +" + + +Legacy and obsolete sources + + + + + + + + + + + + + + + + +" +`; diff --git a/test/configCases/html/legacy-and-obsolete-sources/__snapshots__/ConfigTest.snap b/test/configCases/html/legacy-and-obsolete-sources/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..599834a1d56 --- /dev/null +++ b/test/configCases/html/legacy-and-obsolete-sources/__snapshots__/ConfigTest.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html legacy-and-obsolete-sources exported tests should resolve legacy and obsolete source attributes 1`] = ` +" + + +Legacy and obsolete sources + + + + + + + + + + + + + + + + +" +`; diff --git a/test/configCases/html/legacy-and-obsolete-sources/index.js b/test/configCases/html/legacy-and-obsolete-sources/index.js new file mode 100644 index 00000000000..88968a812e5 --- /dev/null +++ b/test/configCases/html/legacy-and-obsolete-sources/index.js @@ -0,0 +1,20 @@ +import page from "./page.html"; + +it("should resolve legacy and obsolete source attributes", () => { + expect(page).not.toContain("./ref.png"); + // Legacy preview-image hints + expect(page).toMatch(//); + expect(page).toMatch(//); + // Obsolete / + expect(page).toMatch(/classid="[0-9a-f]+\.png"/); + expect(page).toMatch( + // + ); + expect(page).toMatch(/code="[0-9a-f]+\.png"/); + expect(page).toMatch(/object="[0-9a-f]+\.png"/); + // MathML + expect(page).toMatch(//); + // A without valuetype="ref" is an opaque string, left untouched + expect(page).toContain('value="./skip.png"'); + expect(page).toMatchSnapshot(); +}); diff --git a/test/configCases/html/legacy-and-obsolete-sources/page.html b/test/configCases/html/legacy-and-obsolete-sources/page.html new file mode 100644 index 00000000000..eac7fcf1a4d --- /dev/null +++ b/test/configCases/html/legacy-and-obsolete-sources/page.html @@ -0,0 +1,20 @@ + + + +Legacy and obsolete sources + + + + + + + + + + + + + + + + diff --git a/test/configCases/html/legacy-and-obsolete-sources/ref.png b/test/configCases/html/legacy-and-obsolete-sources/ref.png new file mode 100644 index 00000000000..91a99b94e23 Binary files /dev/null and b/test/configCases/html/legacy-and-obsolete-sources/ref.png differ diff --git a/test/configCases/html/legacy-and-obsolete-sources/webpack.config.js b/test/configCases/html/legacy-and-obsolete-sources/webpack.config.js new file mode 100644 index 00000000000..2d275504b64 --- /dev/null +++ b/test/configCases/html/legacy-and-obsolete-sources/webpack.config.js @@ -0,0 +1,10 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + devtool: false, + target: "web", + experiments: { + html: true + } +}; diff --git a/test/configCases/html/legacy-background/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/legacy-background/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..42a1d06aa82 --- /dev/null +++ b/test/configCases/html/legacy-background/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html legacy-background exported tests should resolve the deprecated \`background\` attribute on body/table/td/th 1`] = ` +" + +Legacy background + + + + + + + +
    headcell
    + + + +" +`; diff --git a/test/configCases/html/legacy-background/__snapshots__/ConfigTest.snap b/test/configCases/html/legacy-background/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..dae79503d87 --- /dev/null +++ b/test/configCases/html/legacy-background/__snapshots__/ConfigTest.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html legacy-background exported tests should resolve the deprecated \`background\` attribute on body/table/td/th 1`] = ` +" + +Legacy background + + + + + + + +
    headcell
    + + + +" +`; diff --git a/test/configCases/html/legacy-background/bg.png b/test/configCases/html/legacy-background/bg.png new file mode 100644 index 00000000000..91a99b94e23 Binary files /dev/null and b/test/configCases/html/legacy-background/bg.png differ diff --git a/test/configCases/html/legacy-background/index.js b/test/configCases/html/legacy-background/index.js new file mode 100644 index 00000000000..e6359fc7683 --- /dev/null +++ b/test/configCases/html/legacy-background/index.js @@ -0,0 +1,10 @@ +import page from "./page.html"; + +it("should resolve the deprecated `background` attribute on body/table/td/th", () => { + expect(page).not.toContain("./bg.png"); + expect(page).toMatch(//); + expect(page).toMatch(//); + expect(page).toMatch(/
    /); + expect(page).toMatch(//); + expect(page).toMatchSnapshot(); +}); diff --git a/test/configCases/html/legacy-background/page.html b/test/configCases/html/legacy-background/page.html new file mode 100644 index 00000000000..c51bea73105 --- /dev/null +++ b/test/configCases/html/legacy-background/page.html @@ -0,0 +1,14 @@ + + +Legacy background + + + + + + + +
    headcell
    + + + diff --git a/test/configCases/html/legacy-background/webpack.config.js b/test/configCases/html/legacy-background/webpack.config.js new file mode 100644 index 00000000000..2d275504b64 --- /dev/null +++ b/test/configCases/html/legacy-background/webpack.config.js @@ -0,0 +1,10 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + devtool: false, + target: "web", + experiments: { + html: true + } +}; diff --git a/test/configCases/html/link/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/link/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..35ce018131b --- /dev/null +++ b/test/configCases/html/link/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,89 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html link exported tests should handle link tag 1`] = ` +" + + +Link Tag Test + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +" +`; + +exports[`ConfigCacheTestCases html link exported tests should handle link tag 2`] = ` +Array [ + "/*!***********************!*\\\\ + !*** css ./style.css ***! + \\\\***********************/ +.foo { + background: url(handled-image.png); + color: red; +} + +/*!***************************!*\\\\ + !*** css ./alt-style.css ***! + \\\\***************************/ +.bar { + background: url(handled-icon.png); + color: blue; +} + +", +] +`; diff --git a/test/configCases/html/link/__snapshots__/ConfigTest.snap b/test/configCases/html/link/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..8c9086c83f9 --- /dev/null +++ b/test/configCases/html/link/__snapshots__/ConfigTest.snap @@ -0,0 +1,89 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html link exported tests should handle link tag 1`] = ` +" + + +Link Tag Test + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +" +`; + +exports[`ConfigTestCases html link exported tests should handle link tag 2`] = ` +Array [ + "/*!***********************!*\\\\ + !*** css ./style.css ***! + \\\\***********************/ +.foo { + background: url(handled-image.png); + color: red; +} + +/*!***************************!*\\\\ + !*** css ./alt-style.css ***! + \\\\***************************/ +.bar { + background: url(handled-icon.png); + color: blue; +} + +", +] +`; diff --git a/test/configCases/html/link/alt-style.css b/test/configCases/html/link/alt-style.css new file mode 100644 index 00000000000..7d3168d1af2 --- /dev/null +++ b/test/configCases/html/link/alt-style.css @@ -0,0 +1,4 @@ +.bar { + background: url(./icon.png); + color: blue; +} diff --git a/test/configCases/html/link/font.woff2 b/test/configCases/html/link/font.woff2 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/configCases/html/link/icon.png b/test/configCases/html/link/icon.png new file mode 100644 index 00000000000..b74b839e2b8 Binary files /dev/null and b/test/configCases/html/link/icon.png differ diff --git a/test/configCases/html/link/image.png b/test/configCases/html/link/image.png new file mode 100644 index 00000000000..b74b839e2b8 Binary files /dev/null and b/test/configCases/html/link/image.png differ diff --git a/test/configCases/html/link/index.js b/test/configCases/html/link/index.js new file mode 100644 index 00000000000..c2accf60b4c --- /dev/null +++ b/test/configCases/html/link/index.js @@ -0,0 +1,20 @@ +import page from "./page.html"; +import "./style.css"; +import "./alt-style.css"; + +it("should handle link tag", () => { + expect(page).toMatchSnapshot(); + + // webpackIgnore leaves the stylesheet link untouched. + expect(page).toContain(''); + + const links = document.getElementsByTagName("link"); + const css = []; + + // Skip first because import it by default + for (const link of links.slice(1)) { + css.push(link.sheet.css); + } + + expect(css).toMatchSnapshot(); +}); diff --git a/test/configCases/html/link/manifest.webmanifest b/test/configCases/html/link/manifest.webmanifest new file mode 100644 index 00000000000..9e26dfeeb6e --- /dev/null +++ b/test/configCases/html/link/manifest.webmanifest @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/configCases/html/link/module.mjs b/test/configCases/html/link/module.mjs new file mode 100644 index 00000000000..46d3ca8c61f --- /dev/null +++ b/test/configCases/html/link/module.mjs @@ -0,0 +1 @@ +export const value = 42; diff --git a/test/configCases/html/link/page.html b/test/configCases/html/link/page.html new file mode 100644 index 00000000000..177c13c6e61 --- /dev/null +++ b/test/configCases/html/link/page.html @@ -0,0 +1,62 @@ + + + +Link Tag Test + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/configCases/html/link/prefetch.png b/test/configCases/html/link/prefetch.png new file mode 100644 index 00000000000..b74b839e2b8 Binary files /dev/null and b/test/configCases/html/link/prefetch.png differ diff --git a/test/configCases/html/link/preload.png b/test/configCases/html/link/preload.png new file mode 100644 index 00000000000..b74b839e2b8 Binary files /dev/null and b/test/configCases/html/link/preload.png differ diff --git a/test/configCases/html/link/style.css b/test/configCases/html/link/style.css new file mode 100644 index 00000000000..c0f9bec22dd --- /dev/null +++ b/test/configCases/html/link/style.css @@ -0,0 +1,4 @@ +.foo { + background: url(./image.png); + color: red; +} diff --git a/test/configCases/html/link/test.config.js b/test/configCases/html/link/test.config.js new file mode 100644 index 00000000000..dbfd4316888 --- /dev/null +++ b/test/configCases/html/link/test.config.js @@ -0,0 +1,10 @@ +"use strict"; + +module.exports = { + moduleScope(scope) { + const link = scope.window.document.createElement("link"); + link.rel = "stylesheet"; + link.href = `bundle${scope.__STATS_I__}.css`; + scope.window.document.head.appendChild(link); + } +}; diff --git a/test/configCases/html/link/webpack.config.js b/test/configCases/html/link/webpack.config.js new file mode 100644 index 00000000000..28c452814aa --- /dev/null +++ b/test/configCases/html/link/webpack.config.js @@ -0,0 +1,14 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + devtool: false, + target: "web", + output: { + assetModuleFilename: "handled-[name][ext]" + }, + experiments: { + html: true, + css: true + } +}; diff --git a/test/configCases/html/modulepreload-esm/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/modulepreload-esm/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..684aedc97e5 --- /dev/null +++ b/test/configCases/html/modulepreload-esm/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,131 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html modulepreload-esm exported tests should emit module-format chunks (no IIFE wrapper) when output.module is enabled 1`] = ` +"/*!********************!*\\\\ + !*** ./preload.js ***! + \\\\********************/ +const value = \\"preload module\\"; +globalThis.__preloadLoaded = true; +/* unused harmony default export */ var __WEBPACK_DEFAULT_EXPORT__ = ((/* unused pure expression or super */ null && (value))); + +" +`; + +exports[`ConfigCacheTestCases html modulepreload-esm exported tests should emit module-format chunks (no IIFE wrapper) when output.module is enabled 2`] = ` +"/******/ var __webpack_modules__ = ({ + +/***/ 492 +/*!********************!*\\\\ + !*** ./preload.js ***! + \\\\********************/ +(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + +const value = \\"preload module\\"; +globalThis.__preloadLoaded = true; +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (value); + +/* harmony export */ __webpack_require__.d(__webpack_exports__, [ +/* harmony export */ \\"A\\", 0, /* export default binding */ __WEBPACK_DEFAULT_EXPORT__ +/* harmony export */ ]); + + +/***/ } + +/******/ }); +/************************************************************************/ +/******/ // 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 +/******/ if (!(moduleId in __webpack_modules__)) { +/******/ delete __webpack_module_cache__[moduleId]; +/******/ const e = new Error(\\"Cannot find module '\\" + moduleId + \\"'\\"); +/******/ e.code = 'MODULE_NOT_FOUND'; +/******/ throw e; +/******/ } +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/************************************************************************/ +/******/ /* 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)) +/******/ })(); +/******/ +/************************************************************************/ +let __webpack_exports__ = {}; +/*!******************!*\\\\ + !*** ./entry.js ***! + \\\\******************/ +/* harmony import */ var _preload_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./preload.js */ 492); + + +globalThis.__entryResult = \`entry: \${_preload_js__WEBPACK_IMPORTED_MODULE_0__/* [\\"default\\"] */ .A}\`; + +" +`; + +exports[`ConfigCacheTestCases html modulepreload-esm exported tests should rewrite and + + + + +" +`; diff --git a/test/configCases/html/modulepreload-esm/__snapshots__/ConfigTest.snap b/test/configCases/html/modulepreload-esm/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..344bdb8a91c --- /dev/null +++ b/test/configCases/html/modulepreload-esm/__snapshots__/ConfigTest.snap @@ -0,0 +1,131 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html modulepreload-esm exported tests should emit module-format chunks (no IIFE wrapper) when output.module is enabled 1`] = ` +"/*!********************!*\\\\ + !*** ./preload.js ***! + \\\\********************/ +const value = \\"preload module\\"; +globalThis.__preloadLoaded = true; +/* unused harmony default export */ var __WEBPACK_DEFAULT_EXPORT__ = ((/* unused pure expression or super */ null && (value))); + +" +`; + +exports[`ConfigTestCases html modulepreload-esm exported tests should emit module-format chunks (no IIFE wrapper) when output.module is enabled 2`] = ` +"/******/ var __webpack_modules__ = ({ + +/***/ 492 +/*!********************!*\\\\ + !*** ./preload.js ***! + \\\\********************/ +(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + +const value = \\"preload module\\"; +globalThis.__preloadLoaded = true; +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (value); + +/* harmony export */ __webpack_require__.d(__webpack_exports__, [ +/* harmony export */ \\"A\\", 0, /* export default binding */ __WEBPACK_DEFAULT_EXPORT__ +/* harmony export */ ]); + + +/***/ } + +/******/ }); +/************************************************************************/ +/******/ // 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 +/******/ if (!(moduleId in __webpack_modules__)) { +/******/ delete __webpack_module_cache__[moduleId]; +/******/ const e = new Error(\\"Cannot find module '\\" + moduleId + \\"'\\"); +/******/ e.code = 'MODULE_NOT_FOUND'; +/******/ throw e; +/******/ } +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/************************************************************************/ +/******/ /* 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)) +/******/ })(); +/******/ +/************************************************************************/ +let __webpack_exports__ = {}; +/*!******************!*\\\\ + !*** ./entry.js ***! + \\\\******************/ +/* harmony import */ var _preload_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./preload.js */ 492); + + +globalThis.__entryResult = \`entry: \${_preload_js__WEBPACK_IMPORTED_MODULE_0__/* [\\"default\\"] */ .A}\`; + +" +`; + +exports[`ConfigTestCases html modulepreload-esm exported tests should rewrite and + + + + +" +`; diff --git a/test/configCases/html/modulepreload-esm/entry.js b/test/configCases/html/modulepreload-esm/entry.js new file mode 100644 index 00000000000..bf063ad406f --- /dev/null +++ b/test/configCases/html/modulepreload-esm/entry.js @@ -0,0 +1,3 @@ +import preload from "./preload.js"; + +globalThis.__entryResult = `entry: ${preload}`; diff --git a/test/configCases/html/modulepreload-esm/index.js b/test/configCases/html/modulepreload-esm/index.js new file mode 100644 index 00000000000..23b0020fa11 --- /dev/null +++ b/test/configCases/html/modulepreload-esm/index.js @@ -0,0 +1,71 @@ +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; + +import page from "./page.html"; + +const here = path.dirname(fileURLToPath(import.meta.url)); + +const readChunk = (name) => fs.readFileSync(path.resolve(here, name), "utf-8"); + +const chunkFor = (regex) => { + const m = page.match(regex); + if (!m) throw new Error(`No chunk match for ${regex}`); + return m[1]; +}; + +it("should rewrite and + + + + diff --git a/test/configCases/html/modulepreload-esm/preload-other.js b/test/configCases/html/modulepreload-esm/preload-other.js new file mode 100644 index 00000000000..8f32e3cbab8 --- /dev/null +++ b/test/configCases/html/modulepreload-esm/preload-other.js @@ -0,0 +1,2 @@ +globalThis.__preloadOtherLoaded = true; +export default "preload-other module"; diff --git a/test/configCases/html/modulepreload-esm/preload.js b/test/configCases/html/modulepreload-esm/preload.js new file mode 100644 index 00000000000..ccc9bde06a5 --- /dev/null +++ b/test/configCases/html/modulepreload-esm/preload.js @@ -0,0 +1,3 @@ +const value = "preload module"; +globalThis.__preloadLoaded = true; +export default value; diff --git a/test/configCases/html/modulepreload-esm/test.config.js b/test/configCases/html/modulepreload-esm/test.config.js new file mode 100644 index 00000000000..07b84041615 --- /dev/null +++ b/test/configCases/html/modulepreload-esm/test.config.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + findBundle() { + return ["./main.mjs"]; + } +}; diff --git a/test/configCases/html/modulepreload-esm/test.filter.js b/test/configCases/html/modulepreload-esm/test.filter.js new file mode 100644 index 00000000000..f0f143b8e02 --- /dev/null +++ b/test/configCases/html/modulepreload-esm/test.filter.js @@ -0,0 +1,9 @@ +"use strict"; + +// Tests use `import.meta.url` to locate emitted chunks at runtime and +// `globalThis` in fixtures, both of which require Node 12+. +module.exports = function filter() { + const major = Number(process.versions.node.split(".")[0]); + // the es2022 snapshot uses `Object.hasOwn`, available since Node 16.9 + return major >= 12 && typeof Object.hasOwn === "function"; +}; diff --git a/test/configCases/html/modulepreload-esm/webpack.config.js b/test/configCases/html/modulepreload-esm/webpack.config.js new file mode 100644 index 00000000000..dad1657187d --- /dev/null +++ b/test/configCases/html/modulepreload-esm/webpack.config.js @@ -0,0 +1,32 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: ["web", "es2022"], + node: { + __dirname: false, + __filename: false + }, + externalsPresets: { + node: true + }, + module: { + parser: { + javascript: { + importMeta: false + } + } + }, + output: { + filename: "[name].mjs", + chunkFilename: "[name].chunk.mjs", + module: true + }, + optimization: { + chunkIds: "named" + }, + experiments: { + html: true, + outputModule: true + } +}; diff --git a/test/configCases/html/output-html-crossorigin-template/src/a.js b/test/configCases/html/output-html-crossorigin-template/src/a.js new file mode 100644 index 00000000000..7b2a3460115 --- /dev/null +++ b/test/configCases/html/output-html-crossorigin-template/src/a.js @@ -0,0 +1 @@ +console.log("a"); diff --git a/test/configCases/html/output-html-crossorigin-template/src/b.js b/test/configCases/html/output-html-crossorigin-template/src/b.js new file mode 100644 index 00000000000..6d012e7f1f1 --- /dev/null +++ b/test/configCases/html/output-html-crossorigin-template/src/b.js @@ -0,0 +1 @@ +console.log("b"); diff --git a/test/configCases/html/output-html-crossorigin-template/src/page.html b/test/configCases/html/output-html-crossorigin-template/src/page.html new file mode 100644 index 00000000000..774d4497c48 --- /dev/null +++ b/test/configCases/html/output-html-crossorigin-template/src/page.html @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/test/configCases/html/output-html-crossorigin-template/test.config.js b/test/configCases/html/output-html-crossorigin-template/test.config.js new file mode 100644 index 00000000000..1f8d2c48be8 --- /dev/null +++ b/test/configCases/html/output-html-crossorigin-template/test.config.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + findBundle() { + return ["./test.js"]; + } +}; diff --git a/test/configCases/html/output-html-crossorigin-template/test.js b/test/configCases/html/output-html-crossorigin-template/test.js new file mode 100644 index 00000000000..129e119a87e --- /dev/null +++ b/test/configCases/html/output-html-crossorigin-template/test.js @@ -0,0 +1,17 @@ +const fs = require("fs"); +const path = require("path"); + +const read = (file) => fs.readFileSync(path.resolve(__dirname, file), "utf-8"); +const count = (str, sub) => str.split(sub).length - 1; + +it("adds output.crossOriginLoading to a template tag without crossorigin", () => { + const html = read("page.html"); + // the `a.js` tag had no crossorigin -> exactly one tag gets the default + expect(count(html, 'crossorigin="anonymous"')).toBe(1); +}); + +it("preserves an author-set crossorigin value on a template tag", () => { + const html = read("page.html"); + // the `b.js` tag's value is kept, not duplicated or overridden + expect(count(html, 'crossorigin="use-credentials"')).toBe(1); +}); diff --git a/test/configCases/html/output-html-crossorigin-template/webpack.config.js b/test/configCases/html/output-html-crossorigin-template/webpack.config.js new file mode 100644 index 00000000000..b11a11f0580 --- /dev/null +++ b/test/configCases/html/output-html-crossorigin-template/webpack.config.js @@ -0,0 +1,39 @@ +"use strict"; + +const fs = require("fs"); +const path = require("path"); +const webpack = require("../../../../"); + +/** @type {import("../../../../").WebpackPluginInstance} */ +const copyTest = { + apply(compiler) { + compiler.hooks.compilation.tap("Test", (compilation) => { + compilation.hooks.processAssets.tap( + { + name: "copy-test", + stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL + }, + () => { + compilation.emitAsset( + "test.js", + new webpack.sources.RawSource( + fs.readFileSync(path.resolve(__dirname, "test.js")) + ) + ); + } + ); + }); + } +}; + +// Real `.html` template entry: `output.crossOriginLoading` is added to tags +// that have no `crossorigin`, but an author-set value is preserved. +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: "web", + entry: { page: "./src/page.html" }, + output: { filename: "[name].js", crossOriginLoading: "anonymous" }, + optimization: { minimize: false }, + experiments: { html: true }, + plugins: [copyTest] +}; diff --git a/test/configCases/html/output-html-crossorigin/src/main.js b/test/configCases/html/output-html-crossorigin/src/main.js new file mode 100644 index 00000000000..78092c48fee --- /dev/null +++ b/test/configCases/html/output-html-crossorigin/src/main.js @@ -0,0 +1,3 @@ +import "./style.css"; + +console.log("crossorigin"); diff --git a/test/configCases/html/output-html-crossorigin/src/style.css b/test/configCases/html/output-html-crossorigin/src/style.css new file mode 100644 index 00000000000..461cdec8fb1 --- /dev/null +++ b/test/configCases/html/output-html-crossorigin/src/style.css @@ -0,0 +1,3 @@ +.a { + color: red; +} diff --git a/test/configCases/html/output-html-crossorigin/test.config.js b/test/configCases/html/output-html-crossorigin/test.config.js new file mode 100644 index 00000000000..1f8d2c48be8 --- /dev/null +++ b/test/configCases/html/output-html-crossorigin/test.config.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + findBundle() { + return ["./test.js"]; + } +}; diff --git a/test/configCases/html/output-html-crossorigin/test.js b/test/configCases/html/output-html-crossorigin/test.js new file mode 100644 index 00000000000..73e2085b037 --- /dev/null +++ b/test/configCases/html/output-html-crossorigin/test.js @@ -0,0 +1,13 @@ +const fs = require("fs"); +const path = require("path"); + +const read = (file) => fs.readFileSync(path.resolve(__dirname, file), "utf-8"); +const count = (str, sub) => str.split(sub).length - 1; + +it("mirrors output.crossOriginLoading onto the injected script and stylesheet", () => { + const html = read("main.html"); + expect(html).toMatch(/src="[^"]+\.js"/); + expect(html).toMatch(/href="[^"]+\.css"/); + // both the script and the extracted stylesheet carry the attribute + expect(count(html, 'crossorigin="anonymous"')).toBe(2); +}); diff --git a/test/configCases/html/output-html-crossorigin/webpack.config.js b/test/configCases/html/output-html-crossorigin/webpack.config.js new file mode 100644 index 00000000000..67e0e647ee3 --- /dev/null +++ b/test/configCases/html/output-html-crossorigin/webpack.config.js @@ -0,0 +1,43 @@ +"use strict"; + +const fs = require("fs"); +const path = require("path"); +const webpack = require("../../../../"); + +/** @type {import("../../../../").WebpackPluginInstance} */ +const copyTest = { + apply(compiler) { + compiler.hooks.compilation.tap("Test", (compilation) => { + compilation.hooks.processAssets.tap( + { + name: "copy-test", + stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL + }, + () => { + compilation.emitAsset( + "test.js", + new webpack.sources.RawSource( + fs.readFileSync(path.resolve(__dirname, "test.js")) + ) + ); + } + ); + }); + } +}; + +// `output.crossOriginLoading` must be mirrored onto the injected JS script and +// the CSS `` extracted from the JS import. +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: "web", + entry: { main: "./src/main.js" }, + output: { + filename: "[name].js", + html: true, + crossOriginLoading: "anonymous" + }, + optimization: { minimize: false }, + experiments: { html: true, css: true }, + plugins: [copyTest] +}; diff --git a/test/configCases/html/output-html-css/src/styles.css b/test/configCases/html/output-html-css/src/styles.css new file mode 100644 index 00000000000..e3fc0241a1a --- /dev/null +++ b/test/configCases/html/output-html-css/src/styles.css @@ -0,0 +1,3 @@ +.x { + color: red; +} diff --git a/test/configCases/html/output-html-css/test.config.js b/test/configCases/html/output-html-css/test.config.js new file mode 100644 index 00000000000..1f8d2c48be8 --- /dev/null +++ b/test/configCases/html/output-html-css/test.config.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + findBundle() { + return ["./test.js"]; + } +}; diff --git a/test/configCases/html/output-html-css/test.js b/test/configCases/html/output-html-css/test.js new file mode 100644 index 00000000000..125201b3100 --- /dev/null +++ b/test/configCases/html/output-html-css/test.js @@ -0,0 +1,8 @@ +const fs = require("fs"); +const path = require("path"); + +it("wraps a CSS entry with only a stylesheet link, no script", () => { + const html = fs.readFileSync(path.resolve(__dirname, "styles.html"), "utf-8"); + expect(html).toMatch(//); + expect(html).not.toMatch(/__html_[^"]*\.js/); +}); diff --git a/test/configCases/html/output-html-css/webpack.config.js b/test/configCases/html/output-html-css/webpack.config.js new file mode 100644 index 00000000000..ec45d166a24 --- /dev/null +++ b/test/configCases/html/output-html-css/webpack.config.js @@ -0,0 +1,36 @@ +"use strict"; + +const fs = require("fs"); +const path = require("path"); +const webpack = require("../../../../"); + +/** @type {import("../../../../").WebpackPluginInstance} */ +const copyTest = { + apply(compiler) { + compiler.hooks.compilation.tap("Test", (compilation) => { + compilation.hooks.processAssets.tap( + { + name: "copy-test", + stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL + }, + () => { + compilation.emitAsset( + "test.js", + new webpack.sources.RawSource( + fs.readFileSync(path.resolve(__dirname, "test.js")) + ) + ); + } + ); + }); + } +}; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: "web", + entry: { styles: "./src/styles.css" }, + output: { filename: "[name].js", html: true }, + experiments: { html: true, css: true }, + plugins: [copyTest] +}; diff --git a/test/configCases/html/output-html-depend-on-css/src/app.js b/test/configCases/html/output-html-depend-on-css/src/app.js new file mode 100644 index 00000000000..d24e2916dfa --- /dev/null +++ b/test/configCases/html/output-html-depend-on-css/src/app.js @@ -0,0 +1,3 @@ +import { v } from "./shared.js"; + +(self.order = self.order || []).push(`APP:${v}`); diff --git a/test/configCases/html/output-html-depend-on-css/src/shared.js b/test/configCases/html/output-html-depend-on-css/src/shared.js new file mode 100644 index 00000000000..d7d2f72175a --- /dev/null +++ b/test/configCases/html/output-html-depend-on-css/src/shared.js @@ -0,0 +1,5 @@ +import "./theme.css"; + +(self.order = self.order || []).push("SHARED"); + +export const v = "v"; diff --git a/test/configCases/html/output-html-depend-on-css/src/theme.css b/test/configCases/html/output-html-depend-on-css/src/theme.css new file mode 100644 index 00000000000..ab95c8d8cad --- /dev/null +++ b/test/configCases/html/output-html-depend-on-css/src/theme.css @@ -0,0 +1,3 @@ +.theme { + color: red; +} diff --git a/test/configCases/html/output-html-depend-on-css/test.config.js b/test/configCases/html/output-html-depend-on-css/test.config.js new file mode 100644 index 00000000000..1f8d2c48be8 --- /dev/null +++ b/test/configCases/html/output-html-depend-on-css/test.config.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + findBundle() { + return ["./test.js"]; + } +}; diff --git a/test/configCases/html/output-html-depend-on-css/test.js b/test/configCases/html/output-html-depend-on-css/test.js new file mode 100644 index 00000000000..9699f6cd340 --- /dev/null +++ b/test/configCases/html/output-html-depend-on-css/test.js @@ -0,0 +1,18 @@ +const fs = require("fs"); +const path = require("path"); + +const read = (file) => fs.readFileSync(path.resolve(__dirname, file), "utf-8"); + +it("injects the dependOn target's stylesheet into the dependant page", () => { + const html = read("app.html"); + expect(html).toMatch(//); +}); + +it("loads the stylesheet before the script that needs it", () => { + const html = read("app.html"); + const linkIndex = html.indexOf('rel="stylesheet"'); + const scriptIndex = html.indexOf(" { + compilation.hooks.processAssets.tap( + { + name: "copy-test", + stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL + }, + () => { + compilation.emitAsset( + "test.js", + new webpack.sources.RawSource( + fs.readFileSync(path.resolve(__dirname, "test.js")) + ) + ); + } + ); + }); + } +}; + +// The dependOn target (`shared`) pulls in CSS — the dependant page must inject +// that stylesheet too, before its own script. +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: "web", + entry: { + shared: "./src/shared.js", + app: { import: "./src/app.js", dependOn: "shared" } + }, + output: { filename: "[name].js", html: true }, + optimization: { minimize: false }, + experiments: { html: true, css: true }, + plugins: [copyTest] +}; diff --git a/test/configCases/html/output-html-depend-on-transitive/src/app.js b/test/configCases/html/output-html-depend-on-transitive/src/app.js new file mode 100644 index 00000000000..b62f06b2287 --- /dev/null +++ b/test/configCases/html/output-html-depend-on-transitive/src/app.js @@ -0,0 +1,4 @@ +import { mid1 } from "./mid1.js"; +import { mid2 } from "./mid2.js"; + +(self.order = self.order || []).push(`APP:${mid1}${mid2}`); diff --git a/test/configCases/html/output-html-depend-on-transitive/src/base.js b/test/configCases/html/output-html-depend-on-transitive/src/base.js new file mode 100644 index 00000000000..ac6fcee1596 --- /dev/null +++ b/test/configCases/html/output-html-depend-on-transitive/src/base.js @@ -0,0 +1,3 @@ +(self.order = self.order || []).push("BASE"); + +export const base = "B"; diff --git a/test/configCases/html/output-html-depend-on-transitive/src/mid1.js b/test/configCases/html/output-html-depend-on-transitive/src/mid1.js new file mode 100644 index 00000000000..f23a95cd919 --- /dev/null +++ b/test/configCases/html/output-html-depend-on-transitive/src/mid1.js @@ -0,0 +1,5 @@ +import { base } from "./base.js"; + +(self.order = self.order || []).push(`MID1:${base}`); + +export const mid1 = "M1"; diff --git a/test/configCases/html/output-html-depend-on-transitive/src/mid2.js b/test/configCases/html/output-html-depend-on-transitive/src/mid2.js new file mode 100644 index 00000000000..4ef88741c77 --- /dev/null +++ b/test/configCases/html/output-html-depend-on-transitive/src/mid2.js @@ -0,0 +1,5 @@ +import { base } from "./base.js"; + +(self.order = self.order || []).push(`MID2:${base}`); + +export const mid2 = "M2"; diff --git a/test/configCases/html/output-html-depend-on-transitive/test.config.js b/test/configCases/html/output-html-depend-on-transitive/test.config.js new file mode 100644 index 00000000000..1f8d2c48be8 --- /dev/null +++ b/test/configCases/html/output-html-depend-on-transitive/test.config.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + findBundle() { + return ["./test.js"]; + } +}; diff --git a/test/configCases/html/output-html-depend-on-transitive/test.js b/test/configCases/html/output-html-depend-on-transitive/test.js new file mode 100644 index 00000000000..34c93132efc --- /dev/null +++ b/test/configCases/html/output-html-depend-on-transitive/test.js @@ -0,0 +1,37 @@ +const fs = require("fs"); +const path = require("path"); +const vm = require("vm"); + +const read = (file) => fs.readFileSync(path.resolve(__dirname, file), "utf-8"); +// Match the `src` attribute rather than the whole tag — the page only puts +// `src` on ``) + .join(""); + return `data:text/html,

    From plugin

    ${tags}`; + } + ); + } + }, + { + apply(compiler) { + compiler.hooks.compilation.tap("Test", (compilation) => { + compilation.hooks.processAssets.tap( + { + name: "copy-test", + stage: + compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL + }, + () => { + const data = fs.readFileSync(path.resolve(__dirname, "test.js")); + compilation.emitAsset( + "test.js", + new webpack.sources.RawSource(data) + ); + } + ); + }); + } + } + ] +}; diff --git a/test/configCases/html/output-html-per-entry/src/a.js b/test/configCases/html/output-html-per-entry/src/a.js new file mode 100644 index 00000000000..adaeba36533 --- /dev/null +++ b/test/configCases/html/output-html-per-entry/src/a.js @@ -0,0 +1 @@ +console.log('a') diff --git a/test/configCases/html/output-html-per-entry/src/b.js b/test/configCases/html/output-html-per-entry/src/b.js new file mode 100644 index 00000000000..6a919339d27 --- /dev/null +++ b/test/configCases/html/output-html-per-entry/src/b.js @@ -0,0 +1 @@ +console.log('b') diff --git a/test/configCases/html/output-html-per-entry/test.config.js b/test/configCases/html/output-html-per-entry/test.config.js new file mode 100644 index 00000000000..1f8d2c48be8 --- /dev/null +++ b/test/configCases/html/output-html-per-entry/test.config.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + findBundle() { + return ["./test.js"]; + } +}; diff --git a/test/configCases/html/output-html-per-entry/test.js b/test/configCases/html/output-html-per-entry/test.js new file mode 100644 index 00000000000..453485a0f68 --- /dev/null +++ b/test/configCases/html/output-html-per-entry/test.js @@ -0,0 +1,7 @@ +const fs = require("fs"); +const path = require("path"); + +it("respects per-entry html:false override of output.html:true", () => { + expect(fs.existsSync(path.resolve(__dirname, "a.html"))).toBe(true); + expect(fs.existsSync(path.resolve(__dirname, "b.html"))).toBe(false); +}); diff --git a/test/configCases/html/output-html-per-entry/webpack.config.js b/test/configCases/html/output-html-per-entry/webpack.config.js new file mode 100644 index 00000000000..7b16cc8a4b1 --- /dev/null +++ b/test/configCases/html/output-html-per-entry/webpack.config.js @@ -0,0 +1,38 @@ +"use strict"; + +const fs = require("fs"); +const path = require("path"); +const webpack = require("../../../../"); + +/** @type {import("../../../../").WebpackPluginInstance} */ +const copyTest = { + apply(compiler) { + compiler.hooks.compilation.tap("Test", (compilation) => { + compilation.hooks.processAssets.tap( + { + name: "copy-test", + stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL + }, + () => { + compilation.emitAsset( + "test.js", + new webpack.sources.RawSource( + fs.readFileSync(path.resolve(__dirname, "test.js")) + ) + ); + } + ); + }); + } +}; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + entry: { + a: "./src/a.js", + b: { import: "./src/b.js", html: false } + }, + output: { filename: "[name].js", html: true }, + experiments: { html: true }, + plugins: [copyTest] +}; diff --git a/test/configCases/html/output-html-script-loading/src/main.js b/test/configCases/html/output-html-script-loading/src/main.js new file mode 100644 index 00000000000..aef22247d75 --- /dev/null +++ b/test/configCases/html/output-html-script-loading/src/main.js @@ -0,0 +1 @@ +export default 1; diff --git a/test/configCases/html/output-html-script-loading/test.config.js b/test/configCases/html/output-html-script-loading/test.config.js new file mode 100644 index 00000000000..1f8d2c48be8 --- /dev/null +++ b/test/configCases/html/output-html-script-loading/test.config.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + findBundle() { + return ["./test.js"]; + } +}; diff --git a/test/configCases/html/output-html-script-loading/test.js b/test/configCases/html/output-html-script-loading/test.js new file mode 100644 index 00000000000..10d303fa8a3 --- /dev/null +++ b/test/configCases/html/output-html-script-loading/test.js @@ -0,0 +1,30 @@ +const fs = require("fs"); +const path = require("path"); + +const read = (name) => + fs.readFileSync(path.resolve(__dirname, name), "utf-8"); + +it("scriptLoading: defer emits a deferred script", () => { + expect(read("defer.html")).toMatch(/` bug. + expect((page.match(/]*><\/script>/); +}); + +it("should emit a real `type=module` ` would produce invalid, +// non-executing markup like ``. + +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: "web", + output: { + filename: "[name].js", + chunkFilename: "[name].chunk.js" + }, + optimization: { + chunkIds: "named", + runtimeChunk: { + name: (entrypoint) => + entrypoint.name.startsWith("__html_") + ? `${entrypoint.name}-runtime` + : undefined + } + }, + module: { + parser: { + html: { + sources: [ + "...", + { tag: "my-script", attribute: "src", type: "script" }, + { tag: "my-module", attribute: "src", type: "script-module" } + ] + } + } + }, + experiments: { + html: true + } +}; diff --git a/test/configCases/html/parser-sources-custom/hero.png b/test/configCases/html/parser-sources-custom/hero.png new file mode 100644 index 00000000000..0dd1608e45a --- /dev/null +++ b/test/configCases/html/parser-sources-custom/hero.png @@ -0,0 +1,2 @@ +‰PNG + diff --git a/test/configCases/html/parser-sources-custom/index.js b/test/configCases/html/parser-sources-custom/index.js new file mode 100644 index 00000000000..0d846ac5b53 --- /dev/null +++ b/test/configCases/html/parser-sources-custom/index.js @@ -0,0 +1,30 @@ +import page from "./page.html"; + +it("should rewrite custom data-src URLs the same way as default sources", () => { + // Default `` still rewritten. + expect(page).not.toContain('src="./hero.png"'); + expect(page).toMatch(/src="hero\.png"/); + // Custom `data-src` rewritten via the user-supplied source entry. + expect(page).not.toContain('data-src="./lazy.png"'); + expect(page).toMatch(/data-src="lazy\.png"/); +}); + +it("should parse custom srcset entries as srcset (not src)", () => { + expect(page).not.toContain('data-srcset="./small.png 1x, ./large.png 2x"'); + // Both candidates rewritten to bare filenames; descriptors preserved. + expect(page).toMatch(/data-srcset="small\.png 1x,\s*large\.png 2x"/); +}); + +it("should match a tagless entry against any element", () => { + expect(page).not.toContain('data-href="./linked.png"'); + expect(page).toMatch(/
    /); +}); + +it("should not promote custom sources into compilation entries", () => { + // Even if a user adds `{ tag: 'script', attribute: 'src', type: 'src' }` + // without `'...'`, it should be a plain URL rewrite — never a chunk + // entry. That's verified here by the absence of `__html_*` chunk + // names in the rewritten HTML; the custom-source `data-src` / + // `data-srcset` / `data-href` URLs are asset URLs, not script chunks. + expect(page).not.toMatch(/__html_[a-f0-9]+_\d+/); +}); diff --git a/test/configCases/html/parser-sources-custom/large.png b/test/configCases/html/parser-sources-custom/large.png new file mode 100644 index 00000000000..0dd1608e45a --- /dev/null +++ b/test/configCases/html/parser-sources-custom/large.png @@ -0,0 +1,2 @@ +‰PNG + diff --git a/test/configCases/html/parser-sources-custom/lazy.png b/test/configCases/html/parser-sources-custom/lazy.png new file mode 100644 index 00000000000..0dd1608e45a --- /dev/null +++ b/test/configCases/html/parser-sources-custom/lazy.png @@ -0,0 +1,2 @@ +‰PNG + diff --git a/test/configCases/html/parser-sources-custom/linked.png b/test/configCases/html/parser-sources-custom/linked.png new file mode 100644 index 00000000000..0dd1608e45a --- /dev/null +++ b/test/configCases/html/parser-sources-custom/linked.png @@ -0,0 +1,2 @@ +‰PNG + diff --git a/test/configCases/html/parser-sources-custom/page.html b/test/configCases/html/parser-sources-custom/page.html new file mode 100644 index 00000000000..714d348061b --- /dev/null +++ b/test/configCases/html/parser-sources-custom/page.html @@ -0,0 +1,11 @@ + + + +Custom sources + + +hero +responsive +
    content
    + + diff --git a/test/configCases/html/parser-sources-custom/small.png b/test/configCases/html/parser-sources-custom/small.png new file mode 100644 index 00000000000..0dd1608e45a --- /dev/null +++ b/test/configCases/html/parser-sources-custom/small.png @@ -0,0 +1,2 @@ +‰PNG + diff --git a/test/configCases/html/parser-sources-custom/test.config.js b/test/configCases/html/parser-sources-custom/test.config.js new file mode 100644 index 00000000000..0fa717e15cd --- /dev/null +++ b/test/configCases/html/parser-sources-custom/test.config.js @@ -0,0 +1,12 @@ +"use strict"; + +const fs = require("fs"); + +// `output.filename` is `[name].js` so the test entry bundle lives at +// `main.js`, not `bundle0.js`. Tell the harness where to find it. +module.exports = { + findBundle(_i, options) { + const files = fs.readdirSync(options.output.path); + return files.includes("main.js") ? ["./main.js"] : undefined; + } +}; diff --git a/test/configCases/html/parser-sources-custom/webpack.config.js b/test/configCases/html/parser-sources-custom/webpack.config.js new file mode 100644 index 00000000000..c4f95656a00 --- /dev/null +++ b/test/configCases/html/parser-sources-custom/webpack.config.js @@ -0,0 +1,26 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: "web", + output: { + filename: "[name].js", + assetModuleFilename: "[name][ext]" + }, + module: { + parser: { + html: { + sources: [ + "...", + { tag: "img", attribute: "data-src", type: "src" }, + { tag: "img", attribute: "data-srcset", type: "srcset" }, + // Omit `tag` to match any element. + { attribute: "data-href", type: "src" } + ] + } + } + }, + experiments: { + html: true + } +}; diff --git a/test/configCases/html/parser-sources-disabled/index.js b/test/configCases/html/parser-sources-disabled/index.js new file mode 100644 index 00000000000..3f8aa70131b --- /dev/null +++ b/test/configCases/html/parser-sources-disabled/index.js @@ -0,0 +1,29 @@ +import page from "./page.html"; + +it("should leave URL attributes untouched when module.parser.html.sources is false", () => { + // None of the referenced files (./entry.js, ./styles.css, ./icon.png, + // ./image.png) exist in this directory — disabling `sources` means + // the parser must not turn them into webpack dependencies, otherwise + // the compilation would fail with module-not-found errors. + expect(page).toContain('href="./icon.png"'); + expect(page).toContain('href="./styles.css"'); + expect(page).toContain('src="./entry.js"'); + expect(page).toContain('src="./image.png"'); +}); + +it("should not let Object.prototype-named tags bypass sources:false", () => { + // `` must stay untouched — the + // lookup tables are null-prototype, so `constructor` doesn't resolve + // to an inherited value and never becomes a chunk entry (the file + // doesn't exist; a bogus entry would fail the build). + expect(page).toContain('name="./proto-bypass.js"'); + expect(page).not.toMatch(/__html_[a-f0-9]+_\d+/); +}); + +it("should still bundle inline + + + +image + + + + diff --git a/test/configCases/html/parser-sources-disabled/webpack.config.js b/test/configCases/html/parser-sources-disabled/webpack.config.js new file mode 100644 index 00000000000..7f70fa9a577 --- /dev/null +++ b/test/configCases/html/parser-sources-disabled/webpack.config.js @@ -0,0 +1,16 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: "web", + module: { + parser: { + html: { + sources: false + } + } + }, + experiments: { + html: true + } +}; diff --git a/test/configCases/html/parser-sources-filter/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/parser-sources-filter/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..516bc53f375 --- /dev/null +++ b/test/configCases/html/parser-sources-filter/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,27 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html parser-sources-filter exported tests should apply sources[].filter to conditionally extract URLs 1`] = ` +" + +sources filter + + + +\\"hero\\" + + +\\"hero-small\\" + + +\\"hero-no-attr\\" + + + + + + + + + +" +`; diff --git a/test/configCases/html/parser-sources-filter/__snapshots__/ConfigTest.snap b/test/configCases/html/parser-sources-filter/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..d3eb5a09086 --- /dev/null +++ b/test/configCases/html/parser-sources-filter/__snapshots__/ConfigTest.snap @@ -0,0 +1,27 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html parser-sources-filter exported tests should apply sources[].filter to conditionally extract URLs 1`] = ` +" + +sources filter + + + +\\"hero\\" + + +\\"hero-small\\" + + +\\"hero-no-attr\\" + + + + + + + + + +" +`; diff --git a/test/configCases/html/parser-sources-filter/image.png b/test/configCases/html/parser-sources-filter/image.png new file mode 100644 index 00000000000..91a99b94e23 Binary files /dev/null and b/test/configCases/html/parser-sources-filter/image.png differ diff --git a/test/configCases/html/parser-sources-filter/index.js b/test/configCases/html/parser-sources-filter/index.js new file mode 100644 index 00000000000..59f57dcb922 --- /dev/null +++ b/test/configCases/html/parser-sources-filter/index.js @@ -0,0 +1,5 @@ +import page from "./page.html"; + +it("should apply sources[].filter to conditionally extract URLs", () => { + expect(page).toMatchSnapshot(); +}); diff --git a/test/configCases/html/parser-sources-filter/page.html b/test/configCases/html/parser-sources-filter/page.html new file mode 100644 index 00000000000..d71766fb4ae --- /dev/null +++ b/test/configCases/html/parser-sources-filter/page.html @@ -0,0 +1,22 @@ + + +sources filter + + + +hero + + +hero-small + + +hero-no-attr + + + + + + + + + diff --git a/test/configCases/html/parser-sources-filter/webpack.config.js b/test/configCases/html/parser-sources-filter/webpack.config.js new file mode 100644 index 00000000000..3938fee968a --- /dev/null +++ b/test/configCases/html/parser-sources-filter/webpack.config.js @@ -0,0 +1,32 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + devtool: false, + target: "web", + output: { + assetModuleFilename: "[name][ext]" + }, + module: { + parser: { + html: { + sources: [ + { + tag: "img", + attribute: "src", + type: "src", + filter: (attrs) => attrs.get("data-size") === "large" + }, + { + attribute: "href", + type: "src", + filter: (attrs) => attrs.get("rel") === "icon" + } + ] + } + } + }, + experiments: { + html: true + } +}; diff --git a/test/configCases/html/parser-sources-list-only/index.js b/test/configCases/html/parser-sources-list-only/index.js new file mode 100644 index 00000000000..33529099f15 --- /dev/null +++ b/test/configCases/html/parser-sources-list-only/index.js @@ -0,0 +1,15 @@ +import page from "./page.html"; + +it("should treat an array without `'...'` as opt-out of defaults", () => { + // `./missing.css`, `./missing.js`, `./missing.png` don't exist; the + // build only succeeds because their attributes aren't in the user's + // sources list and are left untouched. + expect(page).toContain('href="./missing.css"'); + expect(page).toContain('src="./missing.js"'); + expect(page).toContain('src="./missing.png"'); +}); + +it("should still rewrite the user's custom data-src", () => { + expect(page).not.toContain('data-src="./lazy.png"'); + expect(page).toMatch(/data-src="[^"]+\.png"/); +}); diff --git a/test/configCases/html/parser-sources-list-only/lazy.png b/test/configCases/html/parser-sources-list-only/lazy.png new file mode 100644 index 00000000000..0dd1608e45a --- /dev/null +++ b/test/configCases/html/parser-sources-list-only/lazy.png @@ -0,0 +1,2 @@ +‰PNG + diff --git a/test/configCases/html/parser-sources-list-only/page.html b/test/configCases/html/parser-sources-list-only/page.html new file mode 100644 index 00000000000..6de6edd2c76 --- /dev/null +++ b/test/configCases/html/parser-sources-list-only/page.html @@ -0,0 +1,10 @@ + + + + + + + +image + + diff --git a/test/configCases/html/parser-sources-list-only/webpack.config.js b/test/configCases/html/parser-sources-list-only/webpack.config.js new file mode 100644 index 00000000000..72de73a49b2 --- /dev/null +++ b/test/configCases/html/parser-sources-list-only/webpack.config.js @@ -0,0 +1,22 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: "web", + module: { + parser: { + html: { + // An array without `"..."` opts out of the default source list. + // Only `` is extracted as a webpack dependency; + // ``, ``, ` + + + + + + + + +" +`; diff --git a/test/configCases/html/script-src-classic/__snapshots__/ConfigTest.snap b/test/configCases/html/script-src-classic/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..e67c162fe6b --- /dev/null +++ b/test/configCases/html/script-src-classic/__snapshots__/ConfigTest.snap @@ -0,0 +1,185 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html script-src-classic exported tests should emit IIFE-wrapped chunks for + + + + + + + + +" +`; diff --git a/test/configCases/html/script-src-classic/classic-typed.js b/test/configCases/html/script-src-classic/classic-typed.js new file mode 100644 index 00000000000..8d9b9b48bd7 --- /dev/null +++ b/test/configCases/html/script-src-classic/classic-typed.js @@ -0,0 +1 @@ +module.exports = "classic typed entry"; diff --git a/test/configCases/html/script-src-classic/entry.js b/test/configCases/html/script-src-classic/entry.js new file mode 100644 index 00000000000..d8fffe7e01d --- /dev/null +++ b/test/configCases/html/script-src-classic/entry.js @@ -0,0 +1 @@ +module.exports = "first entry"; diff --git a/test/configCases/html/script-src-classic/index.js b/test/configCases/html/script-src-classic/index.js new file mode 100644 index 00000000000..f0a60d76764 --- /dev/null +++ b/test/configCases/html/script-src-classic/index.js @@ -0,0 +1,69 @@ +const fs = require("fs"); +const path = require("path"); + +const page = require("./page.html"); + +const readChunk = (name) => fs.readFileSync(path.resolve(__dirname, name), "utf-8"); + +// `String.prototype.matchAll` requires Node 12+; this test runs on Node +// 10 too, so collect all matches via a `regex.exec` loop instead. +const collectMatches = (str, regex) => { + const out = []; + let m; + while ((m = regex.exec(str)) !== null) out.push(m); + return out; +}; + +it("should rewrite script src attributes without changing the type attribute when output.module is off", () => { + expect(typeof page).toBe("string"); + expect(page).toMatchSnapshot(); + // Original source paths were rewritten. + expect(page).not.toContain('src="./entry.js"'); + expect(page).not.toContain('src="./other.js"'); + expect(page).not.toContain('src="./classic-typed.js"'); + expect(page).not.toContain('src="./module-entry.js"'); + // Classic + + + + + + + + diff --git a/test/configCases/html/script-src-classic/webpack.config.js b/test/configCases/html/script-src-classic/webpack.config.js new file mode 100644 index 00000000000..a60bb974151 --- /dev/null +++ b/test/configCases/html/script-src-classic/webpack.config.js @@ -0,0 +1,26 @@ +"use strict"; + +// No `experiments.outputModule` and no `output.module` — emitted chunks are +// classic IIFE-wrapped scripts. The parser must NOT auto-upgrade +// ` + + + + + + + +" +`; + +exports[`ConfigCacheTestCases html script-src-mixed exported tests should chain multiple + + + + + + + +" +`; + +exports[`ConfigTestCases html script-src-mixed exported tests should chain multiple + + + + + + + diff --git a/test/configCases/html/script-src-mixed/test.filter.js b/test/configCases/html/script-src-mixed/test.filter.js new file mode 100644 index 00000000000..f0f143b8e02 --- /dev/null +++ b/test/configCases/html/script-src-mixed/test.filter.js @@ -0,0 +1,9 @@ +"use strict"; + +// Tests use `import.meta.url` to locate emitted chunks at runtime and +// `globalThis` in fixtures, both of which require Node 12+. +module.exports = function filter() { + const major = Number(process.versions.node.split(".")[0]); + // the es2022 snapshot uses `Object.hasOwn`, available since Node 16.9 + return major >= 12 && typeof Object.hasOwn === "function"; +}; diff --git a/test/configCases/html/script-src-mixed/webpack.config.js b/test/configCases/html/script-src-mixed/webpack.config.js new file mode 100644 index 00000000000..73f5fad1ec0 --- /dev/null +++ b/test/configCases/html/script-src-mixed/webpack.config.js @@ -0,0 +1,30 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: ["web", "es2022"], + node: { + __dirname: false, + __filename: false + }, + externalsPresets: { + node: true + }, + module: { + parser: { + javascript: { + importMeta: false + } + } + }, + output: { + chunkFilename: "[name].chunk.js" + }, + optimization: { + chunkIds: "named" + }, + experiments: { + html: true, + outputModule: true + } +}; diff --git a/test/configCases/html/script-src-sibling-attrs/entry.js b/test/configCases/html/script-src-sibling-attrs/entry.js new file mode 100644 index 00000000000..4fe51c72d64 --- /dev/null +++ b/test/configCases/html/script-src-sibling-attrs/entry.js @@ -0,0 +1 @@ +import "./style.css"; diff --git a/test/configCases/html/script-src-sibling-attrs/page.html b/test/configCases/html/script-src-sibling-attrs/page.html new file mode 100644 index 00000000000..699918b10e7 --- /dev/null +++ b/test/configCases/html/script-src-sibling-attrs/page.html @@ -0,0 +1,8 @@ + + + +script src sibling attrs + + +
    Box
    + diff --git a/test/configCases/html/script-src-sibling-attrs/style.css b/test/configCases/html/script-src-sibling-attrs/style.css new file mode 100644 index 00000000000..49d13132599 --- /dev/null +++ b/test/configCases/html/script-src-sibling-attrs/style.css @@ -0,0 +1 @@ +.hero { color: green; } diff --git a/test/configCases/html/script-src-sibling-attrs/test.config.js b/test/configCases/html/script-src-sibling-attrs/test.config.js new file mode 100644 index 00000000000..1f8d2c48be8 --- /dev/null +++ b/test/configCases/html/script-src-sibling-attrs/test.config.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + findBundle() { + return ["./test.js"]; + } +}; diff --git a/test/configCases/html/script-src-sibling-attrs/test.js b/test/configCases/html/script-src-sibling-attrs/test.js new file mode 100644 index 00000000000..c159f46307c --- /dev/null +++ b/test/configCases/html/script-src-sibling-attrs/test.js @@ -0,0 +1,29 @@ +const fs = require("fs"); +const path = require("path"); + +const readFile = (name) => + fs.readFileSync(path.resolve(__dirname, name), "utf-8"); + +it("should copy quoted, bare and unquoted CSP/fetch attributes byte-exact onto the synthesized sibling ", () => { + const extracted = readFile("page.html"); + + const linkMatch = extracted.match(/]*>/); + expect(linkMatch).not.toBeNull(); + const linkTag = linkMatch[0]; + expect(linkTag).toMatch(/href="[^"]+\.css"/); + + // Each copyable attribute is carried back in its original source form — + // quoted, bare (valueless), and unquoted — in the fixed nonce/crossorigin/ + // referrerpolicy order, regardless of source order. + expect(linkTag).toContain( + ' nonce="tok-1" crossorigin referrerpolicy=origin' + ); + // `defer` is not a copyable attribute, so it must not leak onto the link. + expect(linkTag).not.toContain("defer"); + + // The link precedes the script so the stylesheet download starts first. + const scriptIdx = extracted.indexOf("` HTML entry whose JS imports CSS, carrying the three +// copyable CSP/fetch attributes in all three source forms — quoted +// (`nonce="…"`), bare (`crossorigin`), and unquoted (`referrerpolicy=…`). +// The synthesized sibling `` must carry each one back byte-exact, +// exercising every branch of the parser's `attrSourceSpan`. + +const fs = require("fs"); +const path = require("path"); +const webpack = require("../../../../"); + +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: "web", + entry: { + page: "./page.html" + }, + output: { + filename: "[name].js", + chunkFilename: "[name].chunk.js" + }, + optimization: { chunkIds: "named" }, + experiments: { html: true, css: true }, + plugins: [ + { + apply(compiler) { + compiler.hooks.compilation.tap("Test", (compilation) => { + compilation.hooks.processAssets.tap( + { + name: "copy-test", + stage: + compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL + }, + () => { + const data = fs.readFileSync(path.resolve(__dirname, "test.js")); + compilation.emitAsset( + "test.js", + new webpack.sources.RawSource(data) + ); + } + ); + }); + } + } + ] +}; diff --git a/test/configCases/html/script-src/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/script-src/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..df874b97b34 --- /dev/null +++ b/test/configCases/html/script-src/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,152 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html script-src exported tests should bundle + + +

    Hello

    + + + + + + + + + + +" +`; diff --git a/test/configCases/html/script-src/__snapshots__/ConfigTest.snap b/test/configCases/html/script-src/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..2d9c036c086 --- /dev/null +++ b/test/configCases/html/script-src/__snapshots__/ConfigTest.snap @@ -0,0 +1,152 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html script-src exported tests should bundle + + +

    Hello

    + + + + + + + + + + +" +`; diff --git a/test/configCases/html/script-src/classic-typed.js b/test/configCases/html/script-src/classic-typed.js new file mode 100644 index 00000000000..8d9b9b48bd7 --- /dev/null +++ b/test/configCases/html/script-src/classic-typed.js @@ -0,0 +1 @@ +module.exports = "classic typed entry"; diff --git a/test/configCases/html/script-src/entry.js b/test/configCases/html/script-src/entry.js new file mode 100644 index 00000000000..d8fffe7e01d --- /dev/null +++ b/test/configCases/html/script-src/entry.js @@ -0,0 +1 @@ +module.exports = "first entry"; diff --git a/test/configCases/html/script-src/icon.png b/test/configCases/html/script-src/icon.png new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/configCases/html/script-src/importmap.json b/test/configCases/html/script-src/importmap.json new file mode 100644 index 00000000000..f6ca8454c56 --- /dev/null +++ b/test/configCases/html/script-src/importmap.json @@ -0,0 +1,3 @@ +{ + "imports": {} +} diff --git a/test/configCases/html/script-src/index.js b/test/configCases/html/script-src/index.js new file mode 100644 index 00000000000..e2636061144 --- /dev/null +++ b/test/configCases/html/script-src/index.js @@ -0,0 +1,96 @@ +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; + +import page from "./page.html"; + +const here = path.dirname(fileURLToPath(import.meta.url)); + +const readChunk = (name) => fs.readFileSync(path.resolve(here, name), "utf-8"); + +// Document-order list of every script-src chunk url emitted into the page. +// Classic + + +

    Hello

    + + + + + + + + + + diff --git a/test/configCases/html/script-src/style.css b/test/configCases/html/script-src/style.css new file mode 100644 index 00000000000..991912d894b --- /dev/null +++ b/test/configCases/html/script-src/style.css @@ -0,0 +1 @@ +body { color: red; } diff --git a/test/configCases/html/script-src/test.filter.js b/test/configCases/html/script-src/test.filter.js new file mode 100644 index 00000000000..57fde62104e --- /dev/null +++ b/test/configCases/html/script-src/test.filter.js @@ -0,0 +1,10 @@ +"use strict"; + +// Tests use `import.meta.url` to locate emitted chunks at runtime, which +// requires the bundle to run as a real ES module. Jest's ESM support +// (via `--experimental-vm-modules`) needs Node 12+. +module.exports = function filter() { + const major = Number(process.versions.node.split(".")[0]); + // the es2022 snapshot uses `Object.hasOwn`, available since Node 16.9 + return major >= 12 && typeof Object.hasOwn === "function"; +}; diff --git a/test/configCases/html/script-src/webpack.config.js b/test/configCases/html/script-src/webpack.config.js new file mode 100644 index 00000000000..73f5fad1ec0 --- /dev/null +++ b/test/configCases/html/script-src/webpack.config.js @@ -0,0 +1,30 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: ["web", "es2022"], + node: { + __dirname: false, + __filename: false + }, + externalsPresets: { + node: true + }, + module: { + parser: { + javascript: { + importMeta: false + } + } + }, + output: { + chunkFilename: "[name].chunk.js" + }, + optimization: { + chunkIds: "named" + }, + experiments: { + html: true, + outputModule: true + } +}; diff --git a/test/configCases/html/skip-behavior/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/skip-behavior/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..f250b594bdb --- /dev/null +++ b/test/configCases/html/skip-behavior/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html skip-behavior exported tests should silently skip whitespace-only source attribute values 1`] = ` +" + +skip behavior + + + +\\"ws-only\\" +\\"ws-only-srcset\\" + + +\\"ok\\" + + + +" +`; diff --git a/test/configCases/html/skip-behavior/__snapshots__/ConfigTest.snap b/test/configCases/html/skip-behavior/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..e7c3c98a724 --- /dev/null +++ b/test/configCases/html/skip-behavior/__snapshots__/ConfigTest.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html skip-behavior exported tests should silently skip whitespace-only source attribute values 1`] = ` +" + +skip behavior + + + +\\"ws-only\\" +\\"ws-only-srcset\\" + + +\\"ok\\" + + + +" +`; diff --git a/test/configCases/html/skip-behavior/image.png b/test/configCases/html/skip-behavior/image.png new file mode 100644 index 00000000000..b74b839e2b8 Binary files /dev/null and b/test/configCases/html/skip-behavior/image.png differ diff --git a/test/configCases/html/skip-behavior/index.js b/test/configCases/html/skip-behavior/index.js new file mode 100644 index 00000000000..a4f228e0dd1 --- /dev/null +++ b/test/configCases/html/skip-behavior/index.js @@ -0,0 +1,5 @@ +import page from "./page.html"; + +it("should silently skip whitespace-only source attribute values", () => { + expect(page).toMatchSnapshot(); +}); diff --git a/test/configCases/html/skip-behavior/page.html b/test/configCases/html/skip-behavior/page.html new file mode 100644 index 00000000000..f30b6e09417 --- /dev/null +++ b/test/configCases/html/skip-behavior/page.html @@ -0,0 +1,14 @@ + + +skip behavior + + + +ws-only +ws-only-srcset + + +ok + + + diff --git a/test/configCases/html/skip-behavior/webpack.config.js b/test/configCases/html/skip-behavior/webpack.config.js new file mode 100644 index 00000000000..2d275504b64 --- /dev/null +++ b/test/configCases/html/skip-behavior/webpack.config.js @@ -0,0 +1,10 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + devtool: false, + target: "web", + experiments: { + html: true + } +}; diff --git a/test/configCases/html/sources/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/sources/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..f715074da89 --- /dev/null +++ b/test/configCases/html/sources/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,240 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html sources exported tests should handle source 1`] = ` +" + +Test + + +\\"img\\" +\\"img\\" +\\"img\\" + + + + + +\\"Elva +\\"Elva +\\"Elva +\\"Elva +\\"Elva +\\"Elva +\\"Elva +\\"Elva +\\"Elva + +
    + +
    + + + + + +
    text
    +
    text
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Text + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +\\"Red + + +vincetanan@gmail.com +vince@gmail.com + +

    Text

    +

    Text

    +

    Text

    + +\\"Smiley +\\"Smiley +\\"Elva +\\"Elva + +\\"multi + +\\"Red + +\\"\\" + +\\"test\\" +\\"test\\" +\\"test\\" + +\\"test\\" + + + +" +`; diff --git a/test/configCases/html/sources/__snapshots__/ConfigTest.snap b/test/configCases/html/sources/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..518f92e41e4 --- /dev/null +++ b/test/configCases/html/sources/__snapshots__/ConfigTest.snap @@ -0,0 +1,240 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html sources exported tests should handle source 1`] = ` +" + +Test + + +\\"img\\" +\\"img\\" +\\"img\\" + + + + + +\\"Elva +\\"Elva +\\"Elva +\\"Elva +\\"Elva +\\"Elva +\\"Elva +\\"Elva +\\"Elva + +
    + +
    + + + + + +
    text
    +
    text
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Text + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +\\"Red + + +vincetanan@gmail.com +vince@gmail.com + +

    Text

    +

    Text

    +

    Text

    + +\\"Smiley +\\"Smiley +\\"Elva +\\"Elva + +\\"multi + +\\"Red + +\\"\\" + +\\"test\\" +\\"test\\" +\\"test\\" + +\\"test\\" + + + +" +`; diff --git a/test/configCases/html/sources/browserconfig.xml b/test/configCases/html/sources/browserconfig.xml new file mode 100644 index 00000000000..2a8c8169f0b --- /dev/null +++ b/test/configCases/html/sources/browserconfig.xml @@ -0,0 +1,19 @@ + + + + + + + + + #000000 + + + + + + 30 + 1 + + + diff --git a/test/configCases/html/sources/example.ogg b/test/configCases/html/sources/example.ogg new file mode 100644 index 00000000000..a39a4e5a6a2 Binary files /dev/null and b/test/configCases/html/sources/example.ogg differ diff --git a/test/configCases/html/sources/example.vtt b/test/configCases/html/sources/example.vtt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/configCases/html/sources/favicon.ico b/test/configCases/html/sources/favicon.ico new file mode 100644 index 00000000000..b74b839e2b8 Binary files /dev/null and b/test/configCases/html/sources/favicon.ico differ diff --git a/test/configCases/html/sources/file.pdf b/test/configCases/html/sources/file.pdf new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/configCases/html/sources/icons.svg b/test/configCases/html/sources/icons.svg new file mode 100644 index 00000000000..bd21f5337b8 --- /dev/null +++ b/test/configCases/html/sources/icons.svg @@ -0,0 +1 @@ + diff --git a/test/configCases/html/sources/image.png b/test/configCases/html/sources/image.png new file mode 100644 index 00000000000..b74b839e2b8 Binary files /dev/null and b/test/configCases/html/sources/image.png differ diff --git a/test/configCases/html/sources/index.js b/test/configCases/html/sources/index.js new file mode 100644 index 00000000000..ec228bc4f98 --- /dev/null +++ b/test/configCases/html/sources/index.js @@ -0,0 +1,5 @@ +import page from "./page.html"; + +it("should handle source", () => { + expect(page).toMatchSnapshot(); +}); diff --git a/test/configCases/html/sources/music.mp3 b/test/configCases/html/sources/music.mp3 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/configCases/html/sources/page.html b/test/configCases/html/sources/page.html new file mode 100644 index 00000000000..80fc3f36147 --- /dev/null +++ b/test/configCases/html/sources/page.html @@ -0,0 +1,242 @@ + + +Test + + +img +img +img + + + + + +Elva dressed as a fairy +Elva dressed as a fairy +Elva dressed as a fairy +Elva dressed as a fairy +Elva dressed as a fairy +Elva dressed as a fairy +Elva dressed as a fairy +Elva dressed as a fairy +Elva dressed as a fairy + +
    + +
    + + + + + +
    text
    +
    text
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Text + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Red dot + + +vincetanan@gmail.com +vince@gmail.com + +

    Text

    +

    Text

    +

    Text

    + +Smiley face +Smiley face +Elva dressed as a fairy +Elva dressed as a fairy + +multi
+line
+alt + +Red dot + + + +test +test +test + +test + + + diff --git a/test/configCases/html/sources/pixel.png b/test/configCases/html/sources/pixel.png new file mode 100644 index 00000000000..0f2de3749df Binary files /dev/null and b/test/configCases/html/sources/pixel.png differ diff --git a/test/configCases/html/sources/site.webmanifest b/test/configCases/html/sources/site.webmanifest new file mode 100644 index 00000000000..9e26dfeeb6e --- /dev/null +++ b/test/configCases/html/sources/site.webmanifest @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/configCases/html/sources/sound.mp3 b/test/configCases/html/sources/sound.mp3 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/configCases/html/sources/video.mp4 b/test/configCases/html/sources/video.mp4 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/configCases/html/sources/webpack.config.js b/test/configCases/html/sources/webpack.config.js new file mode 100644 index 00000000000..f35cdef83fa --- /dev/null +++ b/test/configCases/html/sources/webpack.config.js @@ -0,0 +1,13 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + devtool: false, + target: "web", + optimization: { + concatenateModules: true + }, + experiments: { + html: true + } +}; diff --git a/test/configCases/html/sources/webpack.svg b/test/configCases/html/sources/webpack.svg new file mode 100644 index 00000000000..07e7602cc85 --- /dev/null +++ b/test/configCases/html/sources/webpack.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git "a/test/configCases/html/sources/\360\237\230\200abc.png" "b/test/configCases/html/sources/\360\237\230\200abc.png" new file mode 100644 index 00000000000..b74b839e2b8 Binary files /dev/null and "b/test/configCases/html/sources/\360\237\230\200abc.png" differ diff --git a/test/configCases/html/srcset/errors.js b/test/configCases/html/srcset/errors.js new file mode 100644 index 00000000000..759d6efa493 --- /dev/null +++ b/test/configCases/html/srcset/errors.js @@ -0,0 +1,217 @@ +"use strict"; + +// Generated against the parser behavior: character references in attribute +// values are decoded before srcset parsing (entity-encoded ASCII whitespace +// separates candidates, like in browsers), so parse-error messages and +// unresolvable URLs below contain the decoded characters, while rewrite +// spans still map back to the raw source text. +module.exports = [ + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'image\.png 480w 2x broken' at 'broken'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'image\.png broken' at 'broken'/, + /Bad value for attribute "srcset" on element "img": Must contain one or more image candidate strings/, + /Bad value for attribute "srcset" on element "img": Must contain one or more image candidate strings/, + /Bad value for attribute "srcset" on element "img": Must contain one or more image candidate strings/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'image\.png 0h, image\.png 800w' at '0h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in ',a foo' at 'foo'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'a \(,' at '\(,'/, + /Bad value for attribute "srcset" on element "img": Must contain one or more image candidate strings/, + /Bad value for attribute "srcset" on element "img": Must contain one or more image candidate strings/, + /Module not found: Error: Can't resolve '\u000B\u000Bdata:,a\u000B\u000B1x\u000B\u000B'/, + /Module not found: Error: Can't resolve '\u000E\u000Edata:,a\u000E\u000E1x\u000E\u000E'/, + /Module not found: Error: Can't resolve '\u000F\u000Fdata:,a\u000F\u000F1x\u000F\u000F'/, + /Module not found: Error: Can't resolve '\u0010\u0010data:,a\u0010\u00101x\u0010\u0010'/, + /Module not found: Error: Can't resolve '\u00A0data:,a'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \( , data:,b 1x, \), data:,c' at '\( , data:,b 1x, \)'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \(\(\( , data:,b 1x, \), data:,c' at '\(\(\( , data:,b 1x, \)'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \[ , data:,b 1x, \], data:,c' at '\['/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \{ , data:,b 1x, \}, data:,c' at '\{'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a, data:,b \(' at '\('/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a, data:,b \( {2}' at '\( {2}'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a, data:,b \(,' at '\(,'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a, data:,b \(x' at '\(x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a, data:,b \(\)' at '\(\)'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \(, data:,b' at '\(, data:,b'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \/\*, data:,b, data:,c \*\/' at '\/\*'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \/\/, data:,b' at '\/\/'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a foo' at 'foo'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a foo foo' at 'foo'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a foo 1x' at '1x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a foo 1x foo' at 'foo'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a foo 1w' at '1w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a foo 1w foo' at 'foo'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1x 1x' at '1x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1w' at '1w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1h 1h' at '1h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1x' at '1x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1x 1w' at '1w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1h 1x' at '1x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1h 1w 1x' at '1x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1x 1w 1h' at '1h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1h foo' at 'foo'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a foo 1h' at '1h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 0w' at '0w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a -1w' at '-1w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w -1w' at '-1w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\.0w' at '1\.0w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\.0w' at '1\.0w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1e0w' at '1e0w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1e0w' at '1e0w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1www' at '1www'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1www' at '1www'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \+1w' at '\+1w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1W' at '1W'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1W' at '1W'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a Infinityw' at 'Infinityw'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w Infinityw' at 'Infinityw'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a NaNw' at 'NaNw'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w NaNw' at 'NaNw'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 0x1w' at '0x1w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u0001w' at '1\u0001w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u00A0w' at '1\u00A0w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u1680w' at '1\u1680w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2000w' at '1\u2000w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2001w' at '1\u2001w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2002w' at '1\u2002w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2003w' at '1\u2003w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2004w' at '1\u2004w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2005w' at '1\u2005w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2006w' at '1\u2006w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2007w' at '1\u2007w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2008w' at '1\u2008w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2009w' at '1\u2009w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u200Aw' at '1\u200Aw'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u200Cw' at '1\u200Cw'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u200Dw' at '1\u200Dw'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u202Fw' at '1\u202Fw'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u205Fw' at '1\u205Fw'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u3000w' at '1\u3000w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\uFEFFw' at '1\uFEFFw'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u00011w' at '\u00011w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u00A01w' at '\u00A01w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u16801w' at '\u16801w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20001w' at '\u20001w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20011w' at '\u20011w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20021w' at '\u20021w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20031w' at '\u20031w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20041w' at '\u20041w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20051w' at '\u20051w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20061w' at '\u20061w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20071w' at '\u20071w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20081w' at '\u20081w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20091w' at '\u20091w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u200A1w' at '\u200A1w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u200C1w' at '\u200C1w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u200D1w' at '\u200D1w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u202F1w' at '\u202F1w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u205F1w' at '\u205F1w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u30001w' at '\u30001w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \uFEFF1w' at '\uFEFF1w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1x -0x' at '-0x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a -1x' at '-1x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1x -1x' at '-1x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a -x' at '-x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \.x' at '\.x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a -\.x' at '-\.x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\.x' at '1\.x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1x 1\.5e1x' at '1\.5e1x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1x 1e1\.5x' at '1e1\.5x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1x 1\.0x' at '1\.0x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \+1x' at '\+1x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1X' at '1X'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a Infinityx' at 'Infinityx'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a NaNx' at 'NaNx'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 0x1x' at '0x1x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 0X1x' at '0X1x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u0001x' at '1\u0001x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u00A0x' at '1\u00A0x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u1680x' at '1\u1680x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2000x' at '1\u2000x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2001x' at '1\u2001x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2002x' at '1\u2002x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2003x' at '1\u2003x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2004x' at '1\u2004x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2005x' at '1\u2005x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2006x' at '1\u2006x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2007x' at '1\u2007x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2008x' at '1\u2008x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2009x' at '1\u2009x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u200Ax' at '1\u200Ax'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u200Cx' at '1\u200Cx'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u200Dx' at '1\u200Dx'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u202Fx' at '1\u202Fx'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u205Fx' at '1\u205Fx'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u3000x' at '1\u3000x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\uFEFFx' at '1\uFEFFx'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u00011x' at '\u00011x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u00A01x' at '\u00A01x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u16801x' at '\u16801x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20001x' at '\u20001x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20011x' at '\u20011x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20021x' at '\u20021x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20031x' at '\u20031x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20041x' at '\u20041x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20051x' at '\u20051x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20061x' at '\u20061x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20071x' at '\u20071x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20081x' at '\u20081x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20091x' at '\u20091x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u200A1x' at '\u200A1x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u200C1x' at '\u200C1x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u200D1x' at '\u200D1x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u202F1x' at '\u202F1x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u205F1x' at '\u205F1x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u30001x' at '\u30001x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \uFEFF1x' at '\uFEFF1x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 0h' at '0h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w -1h' at '-1h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\.0h' at '1\.0h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1e0h' at '1e0h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1hhh' at '1hhh'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1H' at '1H'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w Infinityh' at 'Infinityh'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w NaNh' at 'NaNh'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 0x1h' at '0x1h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 0X1h' at '0X1h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u0001h' at '1\u0001h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u00A0h' at '1\u00A0h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u1680h' at '1\u1680h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u2000h' at '1\u2000h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u2001h' at '1\u2001h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u2002h' at '1\u2002h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u2003h' at '1\u2003h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u2004h' at '1\u2004h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u2005h' at '1\u2005h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u2006h' at '1\u2006h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u2007h' at '1\u2007h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u2008h' at '1\u2008h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u2009h' at '1\u2009h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u200Ah' at '1\u200Ah'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u200Ch' at '1\u200Ch'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u200Dh' at '1\u200Dh'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u202Fh' at '1\u202Fh'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u205Fh' at '1\u205Fh'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u3000h' at '1\u3000h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\uFEFFh' at '1\uFEFFh'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u00011h' at '\u00011h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u00A01h' at '\u00A01h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u16801h' at '\u16801h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u20001h' at '\u20001h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u20011h' at '\u20011h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u20021h' at '\u20021h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u20031h' at '\u20031h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u20041h' at '\u20041h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u20051h' at '\u20051h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u20061h' at '\u20061h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u20071h' at '\u20071h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u20081h' at '\u20081h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u20091h' at '\u20091h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u200A1h' at '\u200A1h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u200C1h' at '\u200C1h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u200D1h' at '\u200D1h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u202F1h' at '\u202F1h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u205F1h' at '\u205F1h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u30001h' at '\u30001h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \uFEFF1h' at '\uFEFF1h'/, + /Bad value for attribute "srcset" on element "img": Must contain one or more image candidate strings/, + /Bad value for attribute "srcset" on element "img": Must contain one or more image candidate strings/ +]; diff --git a/test/configCases/html/srcset/image.png b/test/configCases/html/srcset/image.png new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/configCases/html/srcset/index.js b/test/configCases/html/srcset/index.js new file mode 100644 index 00000000000..82f61fb7cb3 --- /dev/null +++ b/test/configCases/html/srcset/index.js @@ -0,0 +1,5 @@ +import page from "./page.html"; + +it("should work and handle different syntax", () => { + expect(page).toMatchSnapshot(); +}); diff --git a/test/configCases/html/srcset/page.html b/test/configCases/html/srcset/page.html new file mode 100644 index 00000000000..a646d3c8b19 --- /dev/null +++ b/test/configCases/html/srcset/page.html @@ -0,0 +1,293 @@ +Elva dressed as a fairy +Elva dressed as a fairy + + + + +Elva dressed as a fairy +Elva dressed as a fairy +Elva dressed as a fairy + +Elva dressed as a fairy + +Elva dressed as a fairy + + + +Elva dressed as a fairy +Elva dressed as a fairy + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Elva dressed as a fairy +Elva dressed as a fairy +Elva dressed as a fairy +Elva dressed as a fairy +Elva dressed as a fairy +Elva dressed as a fairy +Elva dressed as a fairy +Elva dressed as a fairy +Elva dressed as a fairy +Elva dressed as a fairy +Elva dressed as a fairy + + + + + + + + + + + +vincetanan@gmail.com +vince@gmail.com diff --git a/test/configCases/html/srcset/test.filter.js b/test/configCases/html/srcset/test.filter.js new file mode 100644 index 00000000000..96ddb2717f2 --- /dev/null +++ b/test/configCases/html/srcset/test.filter.js @@ -0,0 +1,3 @@ +"use strict"; + +module.exports = (config) => !config.cache; diff --git a/test/configCases/html/srcset/webpack.config.js b/test/configCases/html/srcset/webpack.config.js new file mode 100644 index 00000000000..bc4cbc8fe56 --- /dev/null +++ b/test/configCases/html/srcset/webpack.config.js @@ -0,0 +1,9 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: "web", + experiments: { + html: true + } +}; diff --git a/test/configCases/html/style-attribute/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/style-attribute/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..8112f3f95d6 --- /dev/null +++ b/test/configCases/html/style-attribute/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html style-attribute exported tests should resolve url() references inside \`style\` attributes 1`] = ` +" + + +Style Attribute Test + + +
    Single url
    +

    No url here — left untouched

    +image-set +
    Multiple urls
    +
    Entity in url
    +
    Entity round-trip
    +
    Entity-obfuscated url()
    +
    Empty style — left untouched
    + + +" +`; diff --git a/test/configCases/html/style-attribute/__snapshots__/ConfigTest.snap b/test/configCases/html/style-attribute/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..e7a99933edd --- /dev/null +++ b/test/configCases/html/style-attribute/__snapshots__/ConfigTest.snap @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html style-attribute exported tests should resolve url() references inside \`style\` attributes 1`] = ` +" + + +Style Attribute Test + + +
    Single url
    +

    No url here — left untouched

    +image-set +
    Multiple urls
    +
    Entity in url
    +
    Entity round-trip
    +
    Entity-obfuscated url()
    +
    Empty style — left untouched
    + + +" +`; diff --git a/test/configCases/html/style-attribute/index.js b/test/configCases/html/style-attribute/index.js new file mode 100644 index 00000000000..ac026c13109 --- /dev/null +++ b/test/configCases/html/style-attribute/index.js @@ -0,0 +1,35 @@ +import page from "./page.html"; + +it("should resolve url() references inside `style` attributes", () => { + expect(typeof page).toBe("string"); + expect(page).toMatchSnapshot(); + + // The CSS declarations themselves pass through unchanged. + expect(page).toContain("color: red"); + + // `url(./pixel.png)` (quoted and unquoted) is rewritten to an asset URL. + expect(page).not.toContain("url(./pixel.png)"); + expect(page).not.toContain("url('./pixel.png')"); + expect(page).toMatch(/url\(handled-pixel\.png\)/); + + // `image-set()` references are resolved too (the string form is + // rewritten to a `url()`). + expect(page).toMatch(/image-set\(url\(handled-pixel\.png\) 1x\)/); + + // A `style` attribute with no URL-bearing function is left untouched. + expect(page).toContain('style="color: blue;"'); +}); + +it("should decode character references and re-escape on write-back", () => { + // `url(./pixel.png)` decodes to `./pixel.png` and resolves. + expect(page).not.toContain("pixel.png"); + + // A decoded `"` (from `"`) is re-escaped when the processed CSS is + // written back into the attribute (quotes are escaped for any quoting + // context), keeping the HTML valid. + expect(page).toMatch(/style="--quote: '"';[^"]*"/); + + // The url() pre-filter runs on the decoded value, so `url(` is + // recognized and resolved. + expect(page).not.toContain("url(./pixel.png)"); +}); diff --git a/test/configCases/html/style-attribute/page.html b/test/configCases/html/style-attribute/page.html new file mode 100644 index 00000000000..ae478ed0d44 --- /dev/null +++ b/test/configCases/html/style-attribute/page.html @@ -0,0 +1,16 @@ + + + +Style Attribute Test + + +
    Single url
    +

    No url here — left untouched

    +image-set +
    Multiple urls
    +
    Entity in url
    +
    Entity round-trip
    +
    Entity-obfuscated url()
    +
    Empty style — left untouched
    + + diff --git a/test/configCases/html/style-attribute/pixel.png b/test/configCases/html/style-attribute/pixel.png new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/configCases/html/style-attribute/webpack.config.js b/test/configCases/html/style-attribute/webpack.config.js new file mode 100644 index 00000000000..fe3ab4ea595 --- /dev/null +++ b/test/configCases/html/style-attribute/webpack.config.js @@ -0,0 +1,15 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + devtool: false, + target: "web", + output: { + pathinfo: false, + assetModuleFilename: "handled-[name][ext]" + }, + experiments: { + html: true, + css: true + } +}; diff --git a/test/configCases/html/style-tag-context/index.js b/test/configCases/html/style-tag-context/index.js new file mode 100644 index 00000000000..64cfa6dfb9c --- /dev/null +++ b/test/configCases/html/style-tag-context/index.js @@ -0,0 +1,12 @@ +// The HTML file lives in `sub/`, while this entry (which sets the +// compilation context for webpack) lives one level up. Relative `url(...)` +// references inside an inline ` + + + diff --git a/test/configCases/html/style-tag-context/sub/pixel.png b/test/configCases/html/style-tag-context/sub/pixel.png new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/configCases/html/style-tag-context/webpack.config.js b/test/configCases/html/style-tag-context/webpack.config.js new file mode 100644 index 00000000000..28c452814aa --- /dev/null +++ b/test/configCases/html/style-tag-context/webpack.config.js @@ -0,0 +1,14 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + devtool: false, + target: "web", + output: { + assetModuleFilename: "handled-[name][ext]" + }, + experiments: { + html: true, + css: true + } +}; diff --git a/test/configCases/html/style-tag-no-css/index.js b/test/configCases/html/style-tag-no-css/index.js new file mode 100644 index 00000000000..c4b2a29ff8f --- /dev/null +++ b/test/configCases/html/style-tag-no-css/index.js @@ -0,0 +1,10 @@ +import page from "./page.html"; + +it("should leave inline ` so the rawtext isn't reparsed as HTML. + expect(page).toContain("body { color: red; }"); + expect(page).not.toContain("data:text/css"); +}); diff --git a/test/configCases/html/style-tag-no-css/page.html b/test/configCases/html/style-tag-no-css/page.html new file mode 100644 index 00000000000..c73638f96da --- /dev/null +++ b/test/configCases/html/style-tag-no-css/page.html @@ -0,0 +1,12 @@ + + + +No CSS Test + + + +

    Hi

    + + diff --git a/test/configCases/html/style-tag-no-css/webpack.config.js b/test/configCases/html/style-tag-no-css/webpack.config.js new file mode 100644 index 00000000000..2d275504b64 --- /dev/null +++ b/test/configCases/html/style-tag-no-css/webpack.config.js @@ -0,0 +1,10 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + devtool: false, + target: "web", + experiments: { + html: true + } +}; diff --git a/test/configCases/html/style-tag/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/style-tag/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..e3bbe0a92b7 --- /dev/null +++ b/test/configCases/html/style-tag/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,42 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html style-tag exported tests should process inline + + + + + + +

    Hi

    + + +" +`; diff --git a/test/configCases/html/style-tag/__snapshots__/ConfigTest.snap b/test/configCases/html/style-tag/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..331884d58c8 --- /dev/null +++ b/test/configCases/html/style-tag/__snapshots__/ConfigTest.snap @@ -0,0 +1,42 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html style-tag exported tests should process inline + + + + + + +

    Hi

    + + +" +`; diff --git a/test/configCases/html/style-tag/index.js b/test/configCases/html/style-tag/index.js new file mode 100644 index 00000000000..75f10065fa0 --- /dev/null +++ b/test/configCases/html/style-tag/index.js @@ -0,0 +1,38 @@ +import page from "./page.html"; + +it("should process inline + + + + + + +

    Hi

    + + diff --git a/test/configCases/html/style-tag/pixel.png b/test/configCases/html/style-tag/pixel.png new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/configCases/html/style-tag/webpack.config.js b/test/configCases/html/style-tag/webpack.config.js new file mode 100644 index 00000000000..28c452814aa --- /dev/null +++ b/test/configCases/html/style-tag/webpack.config.js @@ -0,0 +1,14 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + devtool: false, + target: "web", + output: { + assetModuleFilename: "handled-[name][ext]" + }, + experiments: { + html: true, + css: true + } +}; diff --git a/test/configCases/html/svg-paint-server-references/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/svg-paint-server-references/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..ae9e7cc4cfd --- /dev/null +++ b/test/configCases/html/svg-paint-server-references/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html svg-paint-server-references exported tests should resolve external SVG paint-server and color-profile references 1`] = ` +" + +SVG paint servers + + + + + + + + + + + + + + + + + +" +`; diff --git a/test/configCases/html/svg-paint-server-references/__snapshots__/ConfigTest.snap b/test/configCases/html/svg-paint-server-references/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..4cf56ef4192 --- /dev/null +++ b/test/configCases/html/svg-paint-server-references/__snapshots__/ConfigTest.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html svg-paint-server-references exported tests should resolve external SVG paint-server and color-profile references 1`] = ` +" + +SVG paint servers + + + + + + + + + + + + + + + + + +" +`; diff --git a/test/configCases/html/svg-paint-server-references/defs.svg b/test/configCases/html/svg-paint-server-references/defs.svg new file mode 100644 index 00000000000..33a5892dc59 --- /dev/null +++ b/test/configCases/html/svg-paint-server-references/defs.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/test/configCases/html/svg-paint-server-references/index.js b/test/configCases/html/svg-paint-server-references/index.js new file mode 100644 index 00000000000..3e0a751a5ff --- /dev/null +++ b/test/configCases/html/svg-paint-server-references/index.js @@ -0,0 +1,19 @@ +import page from "./page.html"; + +it("should resolve external SVG paint-server and color-profile references", () => { + expect(page).not.toContain("./defs.svg"); + expect(page).not.toContain("./sRGB.icc"); + // linearGradient / radialGradient / pattern / filter href + xlink:href + expect(page).toMatch(//); + expect(page).toMatch( + // + ); + expect(page).toMatch(//); + expect(page).toMatch(//); + // color-profile points at an external ICC profile file + expect(page).toMatch(//); + // Fragment-only references stay untouched + expect(page).toContain(''); + expect(page).toContain('fill="url(#g1)"'); + expect(page).toMatchSnapshot(); +}); diff --git a/test/configCases/html/svg-paint-server-references/page.html b/test/configCases/html/svg-paint-server-references/page.html new file mode 100644 index 00000000000..919bdcc98f6 --- /dev/null +++ b/test/configCases/html/svg-paint-server-references/page.html @@ -0,0 +1,20 @@ + + +SVG paint servers + + + + + + + + + + + + + + + + + diff --git a/test/configCases/html/svg-paint-server-references/sRGB.icc b/test/configCases/html/svg-paint-server-references/sRGB.icc new file mode 100644 index 00000000000..f55b2beefc6 --- /dev/null +++ b/test/configCases/html/svg-paint-server-references/sRGB.icc @@ -0,0 +1 @@ +ICCPROFILE-placeholder diff --git a/test/configCases/html/svg-paint-server-references/webpack.config.js b/test/configCases/html/svg-paint-server-references/webpack.config.js new file mode 100644 index 00000000000..2d275504b64 --- /dev/null +++ b/test/configCases/html/svg-paint-server-references/webpack.config.js @@ -0,0 +1,10 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + devtool: false, + target: "web", + experiments: { + html: true + } +}; diff --git a/test/configCases/html/svg-presentation-url/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/svg-presentation-url/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..983759dddbe --- /dev/null +++ b/test/configCases/html/svg-presentation-url/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html svg-presentation-url exported tests should resolve url() references in SVG presentation attributes 1`] = ` +" + +SVG presentation url() + + + + + + + + + masked + + + + +" +`; diff --git a/test/configCases/html/svg-presentation-url/__snapshots__/ConfigTest.snap b/test/configCases/html/svg-presentation-url/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..5239bdc18f4 --- /dev/null +++ b/test/configCases/html/svg-presentation-url/__snapshots__/ConfigTest.snap @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html svg-presentation-url exported tests should resolve url() references in SVG presentation attributes 1`] = ` +" + +SVG presentation url() + + + + + + + + + masked + + + + +" +`; diff --git a/test/configCases/html/svg-presentation-url/index.js b/test/configCases/html/svg-presentation-url/index.js new file mode 100644 index 00000000000..f385a98c03d --- /dev/null +++ b/test/configCases/html/svg-presentation-url/index.js @@ -0,0 +1,13 @@ +import page from "./page.html"; + +it("should resolve url() references in SVG presentation attributes", () => { + expect(page).not.toContain("./paint.svg"); + // Unquoted and quoted url() in fill/stroke/filter/mask are rewritten + expect(page).toMatch(/fill="url\([0-9a-f]+\.svg#grad\)"/); + expect(page).toMatch(/stroke="url\('[0-9a-f]+\.svg#grad'\)"/); + expect(page).toMatch(/filter="url\([0-9a-f]+\.svg#blur\)"/); + expect(page).toMatch(/mask="url\([0-9a-f]+\.svg#m\)"/); + // Internal FuncIRI references stay untouched + expect(page).toContain('clip-path="url(#clip)"'); + expect(page).toMatchSnapshot(); +}); diff --git a/test/configCases/html/svg-presentation-url/page.html b/test/configCases/html/svg-presentation-url/page.html new file mode 100644 index 00000000000..3bc2980655a --- /dev/null +++ b/test/configCases/html/svg-presentation-url/page.html @@ -0,0 +1,16 @@ + + +SVG presentation url() + + + + + + + + + masked + + + + diff --git a/test/configCases/html/svg-presentation-url/paint.svg b/test/configCases/html/svg-presentation-url/paint.svg new file mode 100644 index 00000000000..037db38953b --- /dev/null +++ b/test/configCases/html/svg-presentation-url/paint.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/test/configCases/html/svg-presentation-url/webpack.config.js b/test/configCases/html/svg-presentation-url/webpack.config.js new file mode 100644 index 00000000000..2d275504b64 --- /dev/null +++ b/test/configCases/html/svg-presentation-url/webpack.config.js @@ -0,0 +1,10 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + devtool: false, + target: "web", + experiments: { + html: true + } +}; diff --git a/test/configCases/html/svg-references/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/svg-references/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..c1c844e4649 --- /dev/null +++ b/test/configCases/html/svg-references/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html svg-references exported tests should resolve external textPath and mpath references 1`] = ` +" + +SVG references + + + + external + local + + + + + + +" +`; diff --git a/test/configCases/html/svg-references/__snapshots__/ConfigTest.snap b/test/configCases/html/svg-references/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..a69f0d7756b --- /dev/null +++ b/test/configCases/html/svg-references/__snapshots__/ConfigTest.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html svg-references exported tests should resolve external textPath and mpath references 1`] = ` +" + +SVG references + + + + external + local + + + + + + +" +`; diff --git a/test/configCases/html/svg-references/index.js b/test/configCases/html/svg-references/index.js new file mode 100644 index 00000000000..cd0df30cbc3 --- /dev/null +++ b/test/configCases/html/svg-references/index.js @@ -0,0 +1,11 @@ +import page from "./page.html"; + +it("should resolve external textPath and mpath references", () => { + expect(page).not.toContain("./refs.svg"); + expect(page).toMatch(//); + expect(page).toMatch(//); + // Fragment-only references stay untouched + expect(page).toContain('xlink:href="#local-path"'); + expect(page).toContain('xlink:href="#local-motion"'); + expect(page).toMatchSnapshot(); +}); diff --git a/test/configCases/html/svg-references/page.html b/test/configCases/html/svg-references/page.html new file mode 100644 index 00000000000..76c4a829ce4 --- /dev/null +++ b/test/configCases/html/svg-references/page.html @@ -0,0 +1,14 @@ + + +SVG references + + + + external + local + + + + + + diff --git a/test/configCases/html/svg-references/refs.svg b/test/configCases/html/svg-references/refs.svg new file mode 100644 index 00000000000..07e7602cc85 --- /dev/null +++ b/test/configCases/html/svg-references/refs.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/configCases/html/svg-references/webpack.config.js b/test/configCases/html/svg-references/webpack.config.js new file mode 100644 index 00000000000..2d275504b64 --- /dev/null +++ b/test/configCases/html/svg-references/webpack.config.js @@ -0,0 +1,10 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + devtool: false, + target: "web", + experiments: { + html: true + } +}; diff --git a/test/configCases/html/svg-script-href/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/svg-script-href/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..826e9bef04a --- /dev/null +++ b/test/configCases/html/svg-script-href/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,20 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html svg-script-href exported tests should bundle SVG + + + + + + + + +" +`; diff --git a/test/configCases/html/svg-script-href/__snapshots__/ConfigTest.snap b/test/configCases/html/svg-script-href/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..889d7fa4b48 --- /dev/null +++ b/test/configCases/html/svg-script-href/__snapshots__/ConfigTest.snap @@ -0,0 +1,20 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html svg-script-href exported tests should bundle SVG + + + + + + + + +" +`; diff --git a/test/configCases/html/svg-script-href/entry.js b/test/configCases/html/svg-script-href/entry.js new file mode 100644 index 00000000000..18da2840e09 --- /dev/null +++ b/test/configCases/html/svg-script-href/entry.js @@ -0,0 +1 @@ +module.exports = "svg xlink:href entry"; diff --git a/test/configCases/html/svg-script-href/entry2.js b/test/configCases/html/svg-script-href/entry2.js new file mode 100644 index 00000000000..2e821ed5a6e --- /dev/null +++ b/test/configCases/html/svg-script-href/entry2.js @@ -0,0 +1 @@ +module.exports = "svg href entry"; diff --git a/test/configCases/html/svg-script-href/index.js b/test/configCases/html/svg-script-href/index.js new file mode 100644 index 00000000000..a3481c74df1 --- /dev/null +++ b/test/configCases/html/svg-script-href/index.js @@ -0,0 +1,13 @@ +import page from "./page.html"; + +it("should bundle SVG + + + + + + + + diff --git a/test/configCases/html/svg-script-href/webpack.config.js b/test/configCases/html/svg-script-href/webpack.config.js new file mode 100644 index 00000000000..fa8ab72d430 --- /dev/null +++ b/test/configCases/html/svg-script-href/webpack.config.js @@ -0,0 +1,16 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + devtool: false, + target: "web", + output: { + chunkFilename: "[name].chunk.js" + }, + optimization: { + chunkIds: "named" + }, + experiments: { + html: true + } +}; diff --git a/test/configCases/html/template-content/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/template-content/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..ba23f4a9d33 --- /dev/null +++ b/test/configCases/html/template-content/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,22 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html template-content exported tests should rewrite asset URLs inside